From c8ce23e6528b959f4a33c387649eb7cd9a11eb0a Mon Sep 17 00:00:00 2001 From: "T. van der Zwan" Date: Tue, 19 Nov 2013 20:17:59 +0000 Subject: [PATCH 01/26] Renamed ColorTransform to RgbChannelTransform Former-commit-id: 390b832ba2d463710d4a063692f00b83a8c6c8ad --- include/hyperion/Hyperion.h | 10 +++---- ...ColorTransform.h => RgbChannelTransform.h} | 16 +++++------ libsrc/hyperion/Hyperion.cpp | 10 +++---- libsrc/hyperion/ImageProcessor.cpp | 2 -- libsrc/utils/CMakeLists.txt | 6 ++--- ...rTransform.cpp => RgbChannelTransform.cpp} | 27 ++++++++++--------- test/TestColorTransform.cpp | 11 ++++---- 7 files changed, 41 insertions(+), 41 deletions(-) rename include/utils/{ColorTransform.h => RgbChannelTransform.h} (81%) rename libsrc/utils/{ColorTransform.cpp => RgbChannelTransform.cpp} (60%) diff --git a/include/hyperion/Hyperion.h b/include/hyperion/Hyperion.h index 5026ef08..ade46e24 100644 --- a/include/hyperion/Hyperion.h +++ b/include/hyperion/Hyperion.h @@ -17,7 +17,7 @@ // Forward class declaration class HsvTransform; -class ColorTransform; +class RgbChannelTransform; /// /// The main class of Hyperion. This gives other 'users' access to the attached LedDevice through @@ -145,7 +145,7 @@ public: static ColorOrder createColorOrder(const Json::Value & deviceConfig); static LedString createLedString(const Json::Value & ledsConfig); static HsvTransform * createHsvTransform(const Json::Value & hsvConfig); - static ColorTransform * createColorTransform(const Json::Value & colorConfig); + static RgbChannelTransform * createColorTransform(const Json::Value & colorConfig); static LedDevice * createColorSmoothing(const Json::Value & smoothingConfig, LedDevice * ledDevice); private slots: @@ -173,11 +173,11 @@ private: /// The HSV Transform for applying Saturation and Value transforms HsvTransform * _hsvTransform; /// The RED-Channel (RGB) transform - ColorTransform * _redTransform; + RgbChannelTransform * _redTransform; /// The GREEN-Channel (RGB) transform - ColorTransform * _greenTransform; + RgbChannelTransform * _greenTransform; /// The BLUE-Channel (RGB) transform - ColorTransform * _blueTransform; + RgbChannelTransform * _blueTransform; /// Value with the desired color byte order ColorOrder _colorOrder; diff --git a/include/utils/ColorTransform.h b/include/utils/RgbChannelTransform.h similarity index 81% rename from include/utils/ColorTransform.h rename to include/utils/RgbChannelTransform.h index c89b3916..7f524d81 100644 --- a/include/utils/ColorTransform.h +++ b/include/utils/RgbChannelTransform.h @@ -12,21 +12,21 @@ /// 4) finally, in case of a weird choice of parameters, the output is clamped between [0:1] /// /// All configuration values are doubles and assume the color value to be between 0 and 1 -class ColorTransform +class RgbChannelTransform { public: /// Default constructor - ColorTransform(); + RgbChannelTransform(); /// Constructor - /// @param threshold - /// @param gamma - /// @param blacklevel - /// @param whitelevel - ColorTransform(double threshold, double gamma, double blacklevel, double whitelevel); + /// @param threshold The minimum threshold + /// @param gamma The gamma of the gamma-curve correction + /// @param blacklevel The minimum value for the RGB-Channel + /// @param whitelevel The maximum value for the RGB-Channel + RgbChannelTransform(double threshold, double gamma, double blacklevel, double whitelevel); /// Destructor - ~ColorTransform(); + ~RgbChannelTransform(); /// @return The current threshold value double getThreshold() const; diff --git a/libsrc/hyperion/Hyperion.cpp b/libsrc/hyperion/Hyperion.cpp index b7c71183..ce4bd9ab 100644 --- a/libsrc/hyperion/Hyperion.cpp +++ b/libsrc/hyperion/Hyperion.cpp @@ -24,7 +24,7 @@ #include "LinearColorSmoothing.h" -#include +#include #include LedDevice* Hyperion::createDevice(const Json::Value& deviceConfig) @@ -163,14 +163,14 @@ HsvTransform * Hyperion::createHsvTransform(const Json::Value & hsvConfig) return new HsvTransform(saturationGain, valueGain); } -ColorTransform* Hyperion::createColorTransform(const Json::Value& colorConfig) +RgbChannelTransform* Hyperion::createColorTransform(const Json::Value& colorConfig) { const double threshold = colorConfig.get("threshold", 0.0).asDouble(); const double gamma = colorConfig.get("gamma", 1.0).asDouble(); const double blacklevel = colorConfig.get("blacklevel", 0.0).asDouble(); const double whitelevel = colorConfig.get("whitelevel", 1.0).asDouble(); - ColorTransform* transform = new ColorTransform(threshold, gamma, blacklevel, whitelevel); + RgbChannelTransform* transform = new RgbChannelTransform(threshold, gamma, blacklevel, whitelevel); return transform; } @@ -322,7 +322,7 @@ void Hyperion::setColors(int priority, const std::vector& ledColors, c void Hyperion::setTransform(Hyperion::Transform transform, Hyperion::Color color, double value) { // select the transform of the requested color - ColorTransform * t = nullptr; + RgbChannelTransform * t = nullptr; switch (color) { case RED: @@ -396,7 +396,7 @@ void Hyperion::clearall() double Hyperion::getTransform(Hyperion::Transform transform, Hyperion::Color color) const { // select the transform of the requested color - ColorTransform * t = nullptr; + RgbChannelTransform * t = nullptr; switch (color) { case RED: diff --git a/libsrc/hyperion/ImageProcessor.cpp b/libsrc/hyperion/ImageProcessor.cpp index d3bfba3d..a0af2077 100644 --- a/libsrc/hyperion/ImageProcessor.cpp +++ b/libsrc/hyperion/ImageProcessor.cpp @@ -4,8 +4,6 @@ #include #include -#include - using namespace hyperion; ImageProcessor::ImageProcessor(const LedString& ledString, bool enableBlackBorderDetector) : diff --git a/libsrc/utils/CMakeLists.txt b/libsrc/utils/CMakeLists.txt index 18dfd98c..52094a61 100644 --- a/libsrc/utils/CMakeLists.txt +++ b/libsrc/utils/CMakeLists.txt @@ -11,11 +11,11 @@ add_library(hyperion-utils ${CURRENT_HEADER_DIR}/ColorRgba.h ${CURRENT_SOURCE_DIR}/ColorRgba.cpp ${CURRENT_HEADER_DIR}/Image.h - ${CURRENT_HEADER_DIR}/ColorTransform.h - ${CURRENT_HEADER_DIR}/HsvTransform.h - ${CURRENT_SOURCE_DIR}/ColorTransform.cpp + ${CURRENT_HEADER_DIR}/HsvTransform.h ${CURRENT_SOURCE_DIR}/HsvTransform.cpp + ${CURRENT_HEADER_DIR}/RgbChannelTransform.h + ${CURRENT_SOURCE_DIR}/RgbChannelTransform.cpp ${CURRENT_HEADER_DIR}/jsonschema/JsonFactory.h ${CURRENT_HEADER_DIR}/jsonschema/JsonSchemaChecker.h diff --git a/libsrc/utils/ColorTransform.cpp b/libsrc/utils/RgbChannelTransform.cpp similarity index 60% rename from libsrc/utils/ColorTransform.cpp rename to libsrc/utils/RgbChannelTransform.cpp index abe7bded..bdd4ec1b 100644 --- a/libsrc/utils/ColorTransform.cpp +++ b/libsrc/utils/RgbChannelTransform.cpp @@ -1,9 +1,10 @@ // STL includes #include -#include +// Utils includes +#include -ColorTransform::ColorTransform() : +RgbChannelTransform::RgbChannelTransform() : _threshold(0), _gamma(1.0), _blacklevel(0.0), @@ -12,7 +13,7 @@ ColorTransform::ColorTransform() : initializeMapping(); } -ColorTransform::ColorTransform(double threshold, double gamma, double blacklevel, double whitelevel) : +RgbChannelTransform::RgbChannelTransform(double threshold, double gamma, double blacklevel, double whitelevel) : _threshold(threshold), _gamma(gamma), _blacklevel(blacklevel), @@ -21,55 +22,55 @@ ColorTransform::ColorTransform(double threshold, double gamma, double blacklevel initializeMapping(); } -ColorTransform::~ColorTransform() +RgbChannelTransform::~RgbChannelTransform() { } -double ColorTransform::getThreshold() const +double RgbChannelTransform::getThreshold() const { return _threshold; } -void ColorTransform::setThreshold(double threshold) +void RgbChannelTransform::setThreshold(double threshold) { _threshold = threshold; initializeMapping(); } -double ColorTransform::getGamma() const +double RgbChannelTransform::getGamma() const { return _gamma; } -void ColorTransform::setGamma(double gamma) +void RgbChannelTransform::setGamma(double gamma) { _gamma = gamma; initializeMapping(); } -double ColorTransform::getBlacklevel() const +double RgbChannelTransform::getBlacklevel() const { return _blacklevel; } -void ColorTransform::setBlacklevel(double blacklevel) +void RgbChannelTransform::setBlacklevel(double blacklevel) { _blacklevel = blacklevel; initializeMapping(); } -double ColorTransform::getWhitelevel() const +double RgbChannelTransform::getWhitelevel() const { return _whitelevel; } -void ColorTransform::setWhitelevel(double whitelevel) +void RgbChannelTransform::setWhitelevel(double whitelevel) { _whitelevel = whitelevel; initializeMapping(); } -void ColorTransform::initializeMapping() +void RgbChannelTransform::initializeMapping() { // initialize the mapping as a linear array for (int i = 0; i < 256; ++i) diff --git a/test/TestColorTransform.cpp b/test/TestColorTransform.cpp index b52c941c..21af454b 100644 --- a/test/TestColorTransform.cpp +++ b/test/TestColorTransform.cpp @@ -2,13 +2,14 @@ #include #include -#include +// Utils includes +#include int main() { { std::cout << "Testing linear transform" << std::endl; - ColorTransform t; + RgbChannelTransform t; for (int i = 0; i < 256; ++i) { uint8_t input = i; @@ -29,7 +30,7 @@ int main() { std::cout << "Testing threshold" << std::endl; - ColorTransform t(.10, 1.0, 0.0, 1.0); + RgbChannelTransform t(.10, 1.0, 0.0, 1.0); for (int i = 0; i < 256; ++i) { uint8_t input = i; @@ -50,7 +51,7 @@ int main() { std::cout << "Testing blacklevel and whitelevel" << std::endl; - ColorTransform t(0, 1.0, 0.2, 0.8); + RgbChannelTransform t(0, 1.0, 0.2, 0.8); for (int i = 0; i < 256; ++i) { uint8_t input = i; @@ -71,7 +72,7 @@ int main() { std::cout << "Testing gamma" << std::endl; - ColorTransform t(0, 2.0, 0.0, 1.0); + RgbChannelTransform t(0, 2.0, 0.0, 1.0); for (int i = 0; i < 256; ++i) { uint8_t input = i; From ba2939f9ec4706edbab18fe2c73472a0b43ffad8 Mon Sep 17 00:00:00 2001 From: "T. van der Zwan" Date: Wed, 20 Nov 2013 08:25:49 +0000 Subject: [PATCH 02/26] Added multi colortransform (not working yet) Former-commit-id: 691a0a9d83e57e00d305cf18718cab05156c2dce --- include/hyperion/Hyperion.h | 30 +++---- libsrc/hyperion/CMakeLists.txt | 3 + libsrc/hyperion/Hyperion.cpp | 82 ++++++++++-------- libsrc/hyperion/MultiColorTransform.cpp | 105 ++++++++++++++++++++++++ libsrc/hyperion/MultiColorTransform.h | 73 ++++++++++++++++ 5 files changed, 241 insertions(+), 52 deletions(-) create mode 100644 libsrc/hyperion/MultiColorTransform.cpp create mode 100644 libsrc/hyperion/MultiColorTransform.h diff --git a/include/hyperion/Hyperion.h b/include/hyperion/Hyperion.h index ade46e24..6f726241 100644 --- a/include/hyperion/Hyperion.h +++ b/include/hyperion/Hyperion.h @@ -18,6 +18,7 @@ // Forward class declaration class HsvTransform; class RgbChannelTransform; +class MultiColorTransform; /// /// The main class of Hyperion. This gives other 'users' access to the attached LedDevice through @@ -33,7 +34,7 @@ public: /// /// RGB-Color channel enumeration /// - enum Color + enum RgbChannel { RED, GREEN, BLUE, INVALID }; @@ -96,7 +97,7 @@ public: /// Transform::WHITELEVEL) /// @param[in] value The new value for the given transform /// - void setTransform(Transform transform, Color color, double value); + void setTransform(Transform transform, RgbChannel color, double value); /// /// Clears the given priority channel. This will switch the led-colors to the colors of the next @@ -121,7 +122,7 @@ public: /// /// @return The value of the specified color transform /// - double getTransform(Transform transform, Color color) const; + double getTransform(Transform transform, RgbChannel color) const; /// /// Returns a list of active priorities @@ -144,8 +145,11 @@ public: static LedDevice * createDevice(const Json::Value & deviceConfig); static ColorOrder createColorOrder(const Json::Value & deviceConfig); static LedString createLedString(const Json::Value & ledsConfig); + + static MultiColorTransform * createLedColorsTransform(const unsigned ledCnt, const Json::Value & colorTransformConfig); static HsvTransform * createHsvTransform(const Json::Value & hsvConfig); - static RgbChannelTransform * createColorTransform(const Json::Value & colorConfig); + static RgbChannelTransform * createColorTransform(const Json::Value& colorConfig); + static LedDevice * createColorSmoothing(const Json::Value & smoothingConfig, LedDevice * ledDevice); private slots: @@ -156,28 +160,14 @@ private slots: void update(); private: - /// - /// Applies all color transmforms to the given list of colors. The transformation is performed - /// in place. - /// - /// @param colors The colors to be transformed - /// - void applyTransform(std::vector& colors) const; - /// The specifiation of the led frame construction and picture integration LedString _ledString; /// The priority muxer PriorityMuxer _muxer; - /// The HSV Transform for applying Saturation and Value transforms - HsvTransform * _hsvTransform; - /// The RED-Channel (RGB) transform - RgbChannelTransform * _redTransform; - /// The GREEN-Channel (RGB) transform - RgbChannelTransform * _greenTransform; - /// The BLUE-Channel (RGB) transform - RgbChannelTransform * _blueTransform; + /// The transformation from raw colors to led colors + MultiColorTransform * _raw2ledTransform; /// Value with the desired color byte order ColorOrder _colorOrder; diff --git a/libsrc/hyperion/CMakeLists.txt b/libsrc/hyperion/CMakeLists.txt index 937a8906..4ad8df23 100644 --- a/libsrc/hyperion/CMakeLists.txt +++ b/libsrc/hyperion/CMakeLists.txt @@ -24,6 +24,8 @@ SET(Hyperion_HEADERS ${CURRENT_HEADER_DIR}/BlackBorderDetector.h ${CURRENT_HEADER_DIR}/BlackBorderProcessor.h + ${CURRENT_SOURCE_DIR}/MultiColorTransform.h + ${CURRENT_SOURCE_DIR}/device/LedSpiDevice.h ${CURRENT_SOURCE_DIR}/device/LedRs232Device.h ${CURRENT_SOURCE_DIR}/device/LedDeviceTest.h @@ -45,6 +47,7 @@ SET(Hyperion_SOURCES ${CURRENT_SOURCE_DIR}/BlackBorderDetector.cpp ${CURRENT_SOURCE_DIR}/BlackBorderProcessor.cpp ${CURRENT_SOURCE_DIR}/ImageToLedsMap.cpp + ${CURRENT_SOURCE_DIR}/MultiColorTransform.cpp ${CURRENT_SOURCE_DIR}/LinearColorSmoothing.cpp ${CURRENT_SOURCE_DIR}/device/LedSpiDevice.cpp diff --git a/libsrc/hyperion/Hyperion.cpp b/libsrc/hyperion/Hyperion.cpp index ce4bd9ab..757ef10c 100644 --- a/libsrc/hyperion/Hyperion.cpp +++ b/libsrc/hyperion/Hyperion.cpp @@ -22,11 +22,9 @@ #include "device/LedDeviceLightpack.h" #include "device/LedDeviceMultiLightpack.h" +#include "MultiColorTransform.h" #include "LinearColorSmoothing.h" -#include -#include - LedDevice* Hyperion::createDevice(const Json::Value& deviceConfig) { std::cout << "Device configuration: " << deviceConfig << std::endl; @@ -155,6 +153,33 @@ Hyperion::ColorOrder Hyperion::createColorOrder(const Json::Value &deviceConfig) return ORDER_RGB; } +MultiColorTransform * Hyperion::createLedColorsTransform(const unsigned ledCnt, const Json::Value & colorConfig) +{ + MultiColorTransform * transform = new MultiColorTransform(); + if (!colorConfig.isArray()) + { + // Old style color transformation config (just one for all leds) + const std::string id = "default"; + + RgbChannelTransform * redTransform = createColorTransform(colorConfig["red"]); + RgbChannelTransform * greenTransform = createColorTransform(colorConfig["green"]); + RgbChannelTransform * blueTransform = createColorTransform(colorConfig["blue"]); + + HsvTransform * hsvTransform = createHsvTransform("hsv"); + transform->addTransform(id, *redTransform, *greenTransform, *blueTransform, *hsvTransform); + transform->setTransformForLed(id, 0, ledCnt-1); + + delete redTransform; + delete greenTransform; + delete blueTransform; + } + else + { + + } + return transform; +} + HsvTransform * Hyperion::createHsvTransform(const Json::Value & hsvConfig) { const double saturationGain = hsvConfig.get("saturationGain", 1.0).asDouble(); @@ -246,10 +271,7 @@ LedDevice * Hyperion::createColorSmoothing(const Json::Value & smoothingConfig, 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"])), + _raw2ledTransform(createLedColorsTransform(_ledString.leds().size(), jsonConfig["color"])), _colorOrder(createColorOrder(jsonConfig["device"])), _device(createDevice(jsonConfig["device"])), _timer() @@ -278,13 +300,8 @@ Hyperion::~Hyperion() // Delete the Led-String delete _device; - // delete he hsv transform - delete _hsvTransform; - - // Delete the color-transform - delete _blueTransform; - delete _greenTransform; - delete _redTransform; + // delete the color transform + delete _raw2ledTransform; } unsigned Hyperion::getLedCount() const @@ -319,20 +336,23 @@ void Hyperion::setColors(int priority, const std::vector& ledColors, c } } -void Hyperion::setTransform(Hyperion::Transform transform, Hyperion::Color color, double value) +void Hyperion::setTransform(Hyperion::Transform transform, Hyperion::RgbChannel color, double value) { + ColorTransform* colorTransform = _raw2ledTransform->getTransform("default"); + assert(colorTransform != nullptr); + // select the transform of the requested color RgbChannelTransform * t = nullptr; switch (color) { case RED: - t = _redTransform; + t = &(colorTransform->_rgbRedTransform); break; case GREEN: - t = _greenTransform; + t = &(colorTransform->_rgbGreenTransform); break; case BLUE: - t = _blueTransform; + t = &(colorTransform->_rgbBlueTransform); break; default: break; @@ -342,10 +362,10 @@ void Hyperion::setTransform(Hyperion::Transform transform, Hyperion::Color color switch (transform) { case SATURATION_GAIN: - _hsvTransform->setSaturationGain(value); + colorTransform->_hsvTransform.setSaturationGain(value); break; case VALUE_GAIN: - _hsvTransform->setValueGain(value); + colorTransform->_hsvTransform.setValueGain(value); break; case THRESHOLD: assert (t != nullptr); @@ -393,20 +413,23 @@ void Hyperion::clearall() update(); } -double Hyperion::getTransform(Hyperion::Transform transform, Hyperion::Color color) const +double Hyperion::getTransform(Hyperion::Transform transform, Hyperion::RgbChannel color) const { + ColorTransform * colorTransform = _raw2ledTransform->getTransform("default"); + assert(colorTransform != nullptr); + // select the transform of the requested color RgbChannelTransform * t = nullptr; switch (color) { case RED: - t = _redTransform; + t = &(colorTransform->_rgbRedTransform); break; case GREEN: - t = _greenTransform; + t = &(colorTransform->_rgbGreenTransform); break; case BLUE: - t = _blueTransform; + t = &(colorTransform->_rgbBlueTransform); break; default: break; @@ -416,9 +439,9 @@ double Hyperion::getTransform(Hyperion::Transform transform, Hyperion::Color col switch (transform) { case SATURATION_GAIN: - return _hsvTransform->getSaturationGain(); + return colorTransform->_hsvTransform.getSaturationGain(); case VALUE_GAIN: - return _hsvTransform->getValueGain(); + return colorTransform->_hsvTransform.getValueGain(); case THRESHOLD: assert (t != nullptr); return t->getThreshold(); @@ -458,14 +481,9 @@ void Hyperion::update() const PriorityMuxer::InputInfo & priorityInfo = _muxer.getInputInfo(priority); // Apply the transform to each led and color-channel - std::vector ledColors(priorityInfo.ledColors); + std::vector ledColors = _raw2ledTransform->applyTransform(priorityInfo.ledColors); for (ColorRgb& 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); - // correct the color byte order switch (_colorOrder) { diff --git a/libsrc/hyperion/MultiColorTransform.cpp b/libsrc/hyperion/MultiColorTransform.cpp new file mode 100644 index 00000000..99b81b27 --- /dev/null +++ b/libsrc/hyperion/MultiColorTransform.cpp @@ -0,0 +1,105 @@ + +// STL includes +#include + +// Hyperion includes +#include "MultiColorTransform.h" + +MultiColorTransform::MultiColorTransform() +{ +} + +MultiColorTransform::~MultiColorTransform() +{ + // Clean up all the transforms + for (ColorTransform * transform : _transform) + { + delete transform; + } +} + +void MultiColorTransform::addTransform(const std::string & id, + const RgbChannelTransform & redTransform, + const RgbChannelTransform & greenTransform, + const RgbChannelTransform & blueTransform, + const HsvTransform & hsvTransform) +{ + ColorTransform * transform = new ColorTransform(); + transform->_id = id; + + transform->_rgbRedTransform = redTransform; + transform->_rgbGreenTransform = greenTransform; + transform->_rgbBlueTransform = blueTransform; + + transform->_hsvTransform = hsvTransform; + + _transform.push_back(transform); +} + +void MultiColorTransform::setTransformForLed(const std::string& id, const unsigned startLed, const unsigned endLed) +{ + assert(startLed <= endLed); + + // Make sure that there are at least enough led transforms to match the given indices + if (_ledTransforms.size() < endLed+1) + { + _ledTransforms.resize(endLed+1, nullptr); + } + + // Get the identified transform (don't care if is nullptr) + ColorTransform * transform = getTransform(id); + for (unsigned iLed=startLed; iLed<=endLed; ++iLed) + { + _ledTransforms[iLed] = transform; + } +} + +std::vector MultiColorTransform::getTransformIds() +{ + // Create the list on the fly + std::vector transformIds; + for (ColorTransform* transform : _transform) + { + transformIds.push_back(transform->_id); + } + return transformIds; +} + +ColorTransform* MultiColorTransform::getTransform(const std::string& id) +{ + // Iterate through the unique transforms until we find the one with the given id + for (ColorTransform* transform : _transform) + { + if (transform->_id == id) + { + return transform; + } + } + + // The ColorTransform was not found + return nullptr; +} + +std::vector MultiColorTransform::applyTransform(const std::vector& rawColors) +{ + // Create a copy, as we will do the rest of the transformation in place + std::vector ledColors(rawColors); + + const size_t itCnt = std::min(_transform.size(), rawColors.size()); + for (size_t i=0; i_hsvTransform.transform(color.red, color.green, color.blue); + color.red = transform->_rgbRedTransform.transform(color.red); + color.green = transform->_rgbGreenTransform.transform(color.green); + color.blue = transform->_rgbBlueTransform.transform(color.blue); + } + return ledColors; +} diff --git a/libsrc/hyperion/MultiColorTransform.h b/libsrc/hyperion/MultiColorTransform.h new file mode 100644 index 00000000..1fcb4849 --- /dev/null +++ b/libsrc/hyperion/MultiColorTransform.h @@ -0,0 +1,73 @@ +#pragma once + +// STL includes +#include + +// Utils includes +#include + +#include +#include + +struct ColorTransform +{ + std::string _id; + /// The RED-Channel (RGB) transform + RgbChannelTransform _rgbRedTransform; + /// The GREEN-Channel (RGB) transform + RgbChannelTransform _rgbGreenTransform; + /// The BLUE-Channel (RGB) transform + RgbChannelTransform _rgbBlueTransform; + /// The HSV Transform for applying Saturation and Value transforms + HsvTransform _hsvTransform; +}; + +/// +/// The LedColorTransform is responsible for performing color transformation from 'raw' colors +/// received as input to colors mapped to match the color-properties of the leds. +/// +class MultiColorTransform +{ +public: + MultiColorTransform(); + ~MultiColorTransform(); + + void addTransform(const std::string & id, + const RgbChannelTransform & redTransform, + const RgbChannelTransform & greenTransform, + const RgbChannelTransform & blueTransform, + const HsvTransform & hsvTransform); + + void setTransformForLed(const std::string& id, const unsigned startLed, const unsigned endLed); + + /// + /// Returns the identifier of all the unique ColorTransform + /// + /// @return The list with unique id's of the ColorTransforms + std::vector getTransformIds(); + + /// + /// Returns the pointer to the ColorTransform with the given id + /// + /// @param id The identifier of the ColorTransform + /// + /// @return The ColorTransform with the given id (or nullptr if it does not exist) + /// + ColorTransform* getTransform(const std::string& id); + + /// + /// Performs the color transoformation from raw-color to led-color + /// + /// @param rawColors The list with raw colors + /// + /// @return The list with led-colors + /// + std::vector applyTransform(const std::vector& rawColors); + +private: + /// List with unique ColorTransforms + std::vector _transform; + + /// List with a pointer to the ColorTransform for each individual led + std::vector _ledTransforms; +}; From 747e0dbfaeabc1e9aaba1716cba7b1225192abc2 Mon Sep 17 00:00:00 2001 From: "T. van der Zwan" Date: Wed, 20 Nov 2013 09:05:21 +0000 Subject: [PATCH 03/26] Added build-x86 to ignore Former-commit-id: 7e901bcbbe26033902b446f4c85680d8017ccfdb --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 28305334..6a5b347a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /*.user /build +/build-x86 From a52215ac8a62d5d4d7952bf1cea9d1d0e9080c5a Mon Sep 17 00:00:00 2001 From: "T. van der Zwan" Date: Wed, 20 Nov 2013 09:05:49 +0000 Subject: [PATCH 04/26] Added configuration file for x86 platform (output device switched to 'test') Former-commit-id: bea158c81f3d13f6761cbe3d59ab28502830e787 --- config/hyperion_x86.config.json | 400 ++++++++++++++++++++++++++++++++ 1 file changed, 400 insertions(+) create mode 100644 config/hyperion_x86.config.json diff --git a/config/hyperion_x86.config.json b/config/hyperion_x86.config.json new file mode 100644 index 00000000..08b0ce8e --- /dev/null +++ b/config/hyperion_x86.config.json @@ -0,0 +1,400 @@ +// Automatically generated configuration file for 'Hyperion daemon' +// Generated by: HyperCon (The Hyperion deamon configuration file builder + +{ + /// Device configuration contains the following fields: + /// * 'name' : The user friendly name of the device (only used for display purposes) + /// * 'type' : The type of the device or leds (known types for now are 'ws2801', 'ldp8806', + /// 'lpd6803', 'sedu', 'adalight', 'lightpack', 'test' and 'none') + /// * 'output' : The output specification depends on selected device. This can for example be the + /// device specifier, device serial number, or the output file name + /// * 'rate' : The baudrate of the output to the device + /// * 'colorOrder' : The order of the color bytes ('rgb', 'rbg', 'bgr', etc.). + "device" : + { + "name" : "MyPi", + "type" : "test", + "output" : "./hyperiond.test.out", + "rate" : 500000, + "colorOrder" : "rgb" + }, + + /// Color manipulation configuration used to tune the output colors to specific surroundings. Contains the following fields: + /// * 'hsv' : The manipulation in the Hue-Saturation-Value color domain with the following tuning parameters: + /// - 'saturationGain' The gain adjustement of the saturation + /// - 'valueGain' The gain adjustement of the value + /// * 'red'/'green'/'blue' : The manipulation in the Red-Green-Blue color domain with the following tuning parameters for each channel: + /// - 'threshold' The minimum required input value for the channel to be on (else zero) + /// - 'gamma' The gamma-curve correction factor + /// - 'blacklevel' The lowest possible value (when the channel is black) + /// - 'whitelevel' The highest possible value (when the channel is white) + /// * 'smoothing' : Smoothing of the colors in the time-domain with the following tuning parameters: + /// - 'type' The type of smoothing algorithm ('linear' or 'none') + /// - 'time_ms' The time constant for smoothing algorithm in milliseconds + /// - 'updateFrequency' The update frequency of the leds in Hz + "color" : + { + "hsv" : + { + "saturationGain" : 1.0000, + "valueGain" : 1.5000 + }, + "red" : + { + "threshold" : 0.1000, + "gamma" : 2.0000, + "blacklevel" : 0.0000, + "whitelevel" : 0.8000 + }, + "green" : + { + "threshold" : 0.1000, + "gamma" : 2.0000, + "blacklevel" : 0.0000, + "whitelevel" : 1.0000 + }, + "blue" : + { + "threshold" : 0.1000, + "gamma" : 2.0000, + "blacklevel" : 0.0000, + "whitelevel" : 1.0000 + }, + "smoothing" : + { + "type" : "none", + "time_ms" : 200, + "updateFrequency" : 20.0000 + } + }, + + /// The configuration for each individual led. This contains the specification of the area + /// averaged of an input image for each led to determine its color. Each item in the list + /// contains the following fields: + /// * index: The index of the led. This determines its location in the string of leds; zero + /// being the first led. + /// * hscan: The fractional part of the image along the horizontal used for the averaging + /// (minimum and maximum inclusive) + /// * vscan: The fractional part of the image along the vertical used for the averaging + /// (minimum and maximum inclusive) + "leds" : + [ + { + "index" : 0, + "hscan" : { "minimum" : 0.4375, "maximum" : 0.5000 }, + "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } + }, + { + "index" : 1, + "hscan" : { "minimum" : 0.3750, "maximum" : 0.4375 }, + "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } + }, + { + "index" : 2, + "hscan" : { "minimum" : 0.3125, "maximum" : 0.3750 }, + "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } + }, + { + "index" : 3, + "hscan" : { "minimum" : 0.2500, "maximum" : 0.3125 }, + "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } + }, + { + "index" : 4, + "hscan" : { "minimum" : 0.1875, "maximum" : 0.2500 }, + "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } + }, + { + "index" : 5, + "hscan" : { "minimum" : 0.1250, "maximum" : 0.1875 }, + "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } + }, + { + "index" : 6, + "hscan" : { "minimum" : 0.0625, "maximum" : 0.1250 }, + "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } + }, + { + "index" : 7, + "hscan" : { "minimum" : 0.0000, "maximum" : 0.0625 }, + "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } + }, + { + "index" : 8, + "hscan" : { "minimum" : 0.0000, "maximum" : 0.0500 }, + "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } + }, + { + "index" : 9, + "hscan" : { "minimum" : 0.0000, "maximum" : 0.0500 }, + "vscan" : { "minimum" : 0.8571, "maximum" : 1.0000 } + }, + { + "index" : 10, + "hscan" : { "minimum" : 0.0000, "maximum" : 0.0500 }, + "vscan" : { "minimum" : 0.7143, "maximum" : 0.8571 } + }, + { + "index" : 11, + "hscan" : { "minimum" : 0.0000, "maximum" : 0.0500 }, + "vscan" : { "minimum" : 0.5714, "maximum" : 0.7143 } + }, + { + "index" : 12, + "hscan" : { "minimum" : 0.0000, "maximum" : 0.0500 }, + "vscan" : { "minimum" : 0.4286, "maximum" : 0.5714 } + }, + { + "index" : 13, + "hscan" : { "minimum" : 0.0000, "maximum" : 0.0500 }, + "vscan" : { "minimum" : 0.2857, "maximum" : 0.4286 } + }, + { + "index" : 14, + "hscan" : { "minimum" : 0.0000, "maximum" : 0.0500 }, + "vscan" : { "minimum" : 0.1429, "maximum" : 0.2857 } + }, + { + "index" : 15, + "hscan" : { "minimum" : 0.0000, "maximum" : 0.0500 }, + "vscan" : { "minimum" : 0.0000, "maximum" : 0.1429 } + }, + { + "index" : 16, + "hscan" : { "minimum" : 0.0000, "maximum" : 0.0500 }, + "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } + }, + { + "index" : 17, + "hscan" : { "minimum" : 0.0000, "maximum" : 0.0625 }, + "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } + }, + { + "index" : 18, + "hscan" : { "minimum" : 0.0625, "maximum" : 0.1250 }, + "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } + }, + { + "index" : 19, + "hscan" : { "minimum" : 0.1250, "maximum" : 0.1875 }, + "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } + }, + { + "index" : 20, + "hscan" : { "minimum" : 0.1875, "maximum" : 0.2500 }, + "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } + }, + { + "index" : 21, + "hscan" : { "minimum" : 0.2500, "maximum" : 0.3125 }, + "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } + }, + { + "index" : 22, + "hscan" : { "minimum" : 0.3125, "maximum" : 0.3750 }, + "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } + }, + { + "index" : 23, + "hscan" : { "minimum" : 0.3750, "maximum" : 0.4375 }, + "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } + }, + { + "index" : 24, + "hscan" : { "minimum" : 0.4375, "maximum" : 0.5000 }, + "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } + }, + { + "index" : 25, + "hscan" : { "minimum" : 0.5000, "maximum" : 0.5625 }, + "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } + }, + { + "index" : 26, + "hscan" : { "minimum" : 0.5625, "maximum" : 0.6250 }, + "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } + }, + { + "index" : 27, + "hscan" : { "minimum" : 0.6250, "maximum" : 0.6875 }, + "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } + }, + { + "index" : 28, + "hscan" : { "minimum" : 0.6875, "maximum" : 0.7500 }, + "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } + }, + { + "index" : 29, + "hscan" : { "minimum" : 0.7500, "maximum" : 0.8125 }, + "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } + }, + { + "index" : 30, + "hscan" : { "minimum" : 0.8125, "maximum" : 0.8750 }, + "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } + }, + { + "index" : 31, + "hscan" : { "minimum" : 0.8750, "maximum" : 0.9375 }, + "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } + }, + { + "index" : 32, + "hscan" : { "minimum" : 0.9375, "maximum" : 1.0000 }, + "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } + }, + { + "index" : 33, + "hscan" : { "minimum" : 0.9500, "maximum" : 1.0000 }, + "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } + }, + { + "index" : 34, + "hscan" : { "minimum" : 0.9500, "maximum" : 1.0000 }, + "vscan" : { "minimum" : 0.0000, "maximum" : 0.1429 } + }, + { + "index" : 35, + "hscan" : { "minimum" : 0.9500, "maximum" : 1.0000 }, + "vscan" : { "minimum" : 0.1429, "maximum" : 0.2857 } + }, + { + "index" : 36, + "hscan" : { "minimum" : 0.9500, "maximum" : 1.0000 }, + "vscan" : { "minimum" : 0.2857, "maximum" : 0.4286 } + }, + { + "index" : 37, + "hscan" : { "minimum" : 0.9500, "maximum" : 1.0000 }, + "vscan" : { "minimum" : 0.4286, "maximum" : 0.5714 } + }, + { + "index" : 38, + "hscan" : { "minimum" : 0.9500, "maximum" : 1.0000 }, + "vscan" : { "minimum" : 0.5714, "maximum" : 0.7143 } + }, + { + "index" : 39, + "hscan" : { "minimum" : 0.9500, "maximum" : 1.0000 }, + "vscan" : { "minimum" : 0.7143, "maximum" : 0.8571 } + }, + { + "index" : 40, + "hscan" : { "minimum" : 0.9500, "maximum" : 1.0000 }, + "vscan" : { "minimum" : 0.8571, "maximum" : 1.0000 } + }, + { + "index" : 41, + "hscan" : { "minimum" : 0.9500, "maximum" : 1.0000 }, + "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } + }, + { + "index" : 42, + "hscan" : { "minimum" : 0.9375, "maximum" : 1.0000 }, + "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } + }, + { + "index" : 43, + "hscan" : { "minimum" : 0.8750, "maximum" : 0.9375 }, + "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } + }, + { + "index" : 44, + "hscan" : { "minimum" : 0.8125, "maximum" : 0.8750 }, + "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } + }, + { + "index" : 45, + "hscan" : { "minimum" : 0.7500, "maximum" : 0.8125 }, + "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } + }, + { + "index" : 46, + "hscan" : { "minimum" : 0.6875, "maximum" : 0.7500 }, + "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } + }, + { + "index" : 47, + "hscan" : { "minimum" : 0.6250, "maximum" : 0.6875 }, + "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } + }, + { + "index" : 48, + "hscan" : { "minimum" : 0.5625, "maximum" : 0.6250 }, + "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } + }, + { + "index" : 49, + "hscan" : { "minimum" : 0.5000, "maximum" : 0.5625 }, + "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } + } + ], + + /// The black border configuration, contains the following items: + /// * enable : true if the detector should be activated + "blackborderdetector" : + { + "enable" : true + }, + + /// The boot-sequence configuration, contains the following items: + /// * type : The type of the boot-sequence ('rainbow', 'knight_rider', 'none') + /// * duration_ms : The length of the boot-sequence [ms] + "bootsequence" : + { + "type" : "Rainbow", + "duration_ms" : 3000 + }, + + /// The configuration for the frame-grabber, contains the following items: + /// * width : The width of the grabbed frames [pixels] + /// * height : The height of the grabbed frames [pixels] + /// * frequency_Hz : The frequency of the frame grab [Hz] + "framegrabber" : + { + "width" : 64, + "height" : 64, + "frequency_Hz" : 10.0 + }, + + /// The configuration of the XBMC connection used to enable and disable the frame-grabber. Contains the following fields: + /// * xbmcAddress : The IP address of the XBMC-host + /// * xbmcTcpPort : The TCP-port of the XBMC-server + /// * grabVideo : Flag indicating that the frame-grabber is on(true) during video playback + /// * grabPictures : Flag indicating that the frame-grabber is on(true) during picture show + /// * grabAudio : Flag indicating that the frame-grabber is on(true) during audio playback + /// * grabMenu : Flag indicating that the frame-grabber is on(true) in the XBMC menu + "xbmcVideoChecker" : + { + "xbmcAddress" : "127.0.0.1", + "xbmcTcpPort" : 9090, + "grabVideo" : true, + "grabPictures" : true, + "grabAudio" : true, + "grabMenu" : false + }, + + /// The configuration of the Json server which enables the json remote interface + /// * port : Port at which the json server is started + "jsonServer" : + { + "port" : 19444 + }, + + /// The configuration of the Proto server which enables the protobuffer remote interface + /// * port : Port at which the protobuffer server is started + "protoServer" : + { + "port" : 19445 + }, + + /// The configuration of the boblight server which enables the boblight remote interface + /// * port : Port at which the boblight server is started +// "boblightServer" : +// { +// "port" : 19333 +// }, + + "end-of-json" : "end-of-json" +} From d78cbe55d6b8551da174afd0f18aab2cea855bab Mon Sep 17 00:00:00 2001 From: "T. van der Zwan" Date: Wed, 20 Nov 2013 09:06:12 +0000 Subject: [PATCH 05/26] Fixed BlackBorder test after changes to blackborder detector Former-commit-id: fc09afcbe23227cd44a72c23ddd0c4ee484b7478 --- test/TestBlackBorderProcessor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/TestBlackBorderProcessor.cpp b/test/TestBlackBorderProcessor.cpp index beb018f0..f5317f05 100644 --- a/test/TestBlackBorderProcessor.cpp +++ b/test/TestBlackBorderProcessor.cpp @@ -148,7 +148,7 @@ int main() // Switch back (in one shot) to no border assert(processor.process(noBorderImage)); - assert(processor.getCurrentBorder().type == BlackBorder::none); + assert(processor.getCurrentBorder().verticalSize == 0 && processor.getCurrentBorder().horizontalSize == 0); return 0; } From eaec09f02998f9f37e0fcdcaf640c75d446048b7 Mon Sep 17 00:00:00 2001 From: "T. van der Zwan" Date: Wed, 20 Nov 2013 09:06:41 +0000 Subject: [PATCH 06/26] Fixed MultiColorTransform creation for 'hsv' Former-commit-id: 6963e2d022c38f31fcb5371ac5bd9057c4eaf088 --- libsrc/hyperion/Hyperion.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsrc/hyperion/Hyperion.cpp b/libsrc/hyperion/Hyperion.cpp index 757ef10c..64b1be63 100644 --- a/libsrc/hyperion/Hyperion.cpp +++ b/libsrc/hyperion/Hyperion.cpp @@ -165,7 +165,7 @@ MultiColorTransform * Hyperion::createLedColorsTransform(const unsigned ledCnt, RgbChannelTransform * greenTransform = createColorTransform(colorConfig["green"]); RgbChannelTransform * blueTransform = createColorTransform(colorConfig["blue"]); - HsvTransform * hsvTransform = createHsvTransform("hsv"); + HsvTransform * hsvTransform = createHsvTransform(colorConfig["hsv"]); transform->addTransform(id, *redTransform, *greenTransform, *blueTransform, *hsvTransform); transform->setTransformForLed(id, 0, ledCnt-1); From ddc7bdd3315cb08658608c80c4a9fe95349e037c Mon Sep 17 00:00:00 2001 From: "T. van der Zwan" Date: Wed, 20 Nov 2013 09:07:01 +0000 Subject: [PATCH 07/26] Fixed use of 'enum' as type is schema Former-commit-id: 017cfe70198b37a05b9a852b30d783e239bf604b --- libsrc/utils/jsonschema/JsonSchemaChecker.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libsrc/utils/jsonschema/JsonSchemaChecker.cpp b/libsrc/utils/jsonschema/JsonSchemaChecker.cpp index 2ace25a5..4cfa0935 100644 --- a/libsrc/utils/jsonschema/JsonSchemaChecker.cpp +++ b/libsrc/utils/jsonschema/JsonSchemaChecker.cpp @@ -162,6 +162,8 @@ void JsonSchemaChecker::checkType(const Json::Value & value, const Json::Value & wrongType = !value.isArray(); else if (type == "null") wrongType = !value.isNull(); + else if (type == "enum") + wrongType = !value.isString(); else if (type == "any") wrongType = false; else From e43778b08b3af53080be2688836c41d32795f2df Mon Sep 17 00:00:00 2001 From: "T. van der Zwan" Date: Fri, 22 Nov 2013 10:44:41 +0000 Subject: [PATCH 08/26] Added small test to parse some parts of the json file Former-commit-id: 2ecdb7d12289d63a5fde051262162d01d768c328 --- test/CMakeLists.txt | 4 ++++ test/TestQRegExp.cpp | 50 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 test/TestQRegExp.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 5e2844e3..22957155 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -41,5 +41,9 @@ add_executable(test_blackborderprocessor target_link_libraries(test_blackborderprocessor hyperion) +add_executable(test_qregexp TestQRegExp.cpp) +target_link_libraries(test_qregexp + ${QT_LIBRARIES}) + add_executable(spidev_test spidev_test.c) add_executable(gpio2spi switchPinCtrl.c) diff --git a/test/TestQRegExp.cpp b/test/TestQRegExp.cpp new file mode 100644 index 00000000..efc74227 --- /dev/null +++ b/test/TestQRegExp.cpp @@ -0,0 +1,50 @@ + +// STL includes +#include + +// QT includes +#include +#include +#include + +int main() +{ + QString testString = "1-9, 11, 12,13,16-17"; + + QRegExp overallExp("([0-9]+(\\-[0-9]+)?)(,[ ]*([0-9]+(\\-[0-9]+)?))*"); + { + + std::cout << "[1] Match found: " << (overallExp.exactMatch("5")?"true":"false") << std::endl; + std::cout << "[1] Match found: " << (overallExp.exactMatch("4-")?"true":"false") << std::endl; + std::cout << "[1] Match found: " << (overallExp.exactMatch("-4")?"true":"false") << std::endl; + std::cout << "[1] Match found: " << (overallExp.exactMatch("3-9")?"true":"false") << std::endl; + std::cout << "[1] Match found: " << (overallExp.exactMatch("1-90")?"true":"false") << std::endl; + std::cout << "[1] Match found: " << (overallExp.exactMatch("1-90,100")?"true":"false") << std::endl; + std::cout << "[1] Match found: " << (overallExp.exactMatch("1-90, 100")?"true":"false") << std::endl; + std::cout << "[1] Match found: " << (overallExp.exactMatch("1-90, 100-200")?"true":"false") << std::endl; + std::cout << "[1] Match found: " << (overallExp.exactMatch("1-90, 100-200, 100")?"true":"false") << std::endl; + } + { + if (!overallExp.exactMatch(testString)) { + std::cout << "No correct match" << std::endl; + return -1; + } + QStringList splitString = testString.split(QChar(',')); + for (int i=0; i " << startInd << "-" << endInd << std::endl; + } + else + { + int index = splitString[i].toInt(); + std::cout << "==> " << index << std::endl; + } + } + } + + return 0; +} From 42b322a0118ea5845b4bcddb779484e9730ab722 Mon Sep 17 00:00:00 2001 From: "T. van der Zwan" Date: Fri, 22 Nov 2013 10:46:40 +0000 Subject: [PATCH 09/26] Updated x86 config file with more color transforms Former-commit-id: 062ab180f30f3b1c3c236e6c3d0286da82e991ab --- CMakeLists.txt | 2 + config/hyperion_x86.config.json | 132 ++++++++++++++++++++------------ 2 files changed, 87 insertions(+), 47 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index eaf2690b..cc92e72c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,6 +37,7 @@ include_directories(${CMAKE_SOURCE_DIR}/include) # Prefer static linking over dynamic set(CMAKE_FIND_LIBRARY_SUFFIXES ".a;.so") +#set(CMAKE_BUILD_TYPE "Debug") set(CMAKE_BUILD_TYPE "Release") # enable C++11 @@ -60,6 +61,7 @@ link_directories(${CMAKE_FIND_ROOT_PATH}/lib/arm-linux-gnueabihf) configure_file(bin/install_hyperion.sh ${LIBRARY_OUTPUT_PATH} @ONLY) configure_file(config/hyperion.config.json ${LIBRARY_OUTPUT_PATH} @ONLY) +configure_file(config/hyperion_x86.config.json ${LIBRARY_OUTPUT_PATH} @ONLY) # Add the source/lib directories add_subdirectory(dependencies) diff --git a/config/hyperion_x86.config.json b/config/hyperion_x86.config.json index 08b0ce8e..807177e4 100644 --- a/config/hyperion_x86.config.json +++ b/config/hyperion_x86.config.json @@ -2,7 +2,7 @@ // Generated by: HyperCon (The Hyperion deamon configuration file builder { - /// Device configuration contains the following fields: + /// Device configuration contains the following fields: /// * 'name' : The user friendly name of the device (only used for display purposes) /// * 'type' : The type of the device or leds (known types for now are 'ws2801', 'ldp8806', /// 'lpd6803', 'sedu', 'adalight', 'lightpack', 'test' and 'none') @@ -34,32 +34,70 @@ /// - 'updateFrequency' The update frequency of the leds in Hz "color" : { - "hsv" : - { - "saturationGain" : 1.0000, - "valueGain" : 1.5000 - }, - "red" : - { - "threshold" : 0.1000, - "gamma" : 2.0000, - "blacklevel" : 0.0000, - "whitelevel" : 0.8000 - }, - "green" : - { - "threshold" : 0.1000, - "gamma" : 2.0000, - "blacklevel" : 0.0000, - "whitelevel" : 1.0000 - }, - "blue" : - { - "threshold" : 0.1000, - "gamma" : 2.0000, - "blacklevel" : 0.0000, - "whitelevel" : 1.0000 - }, + "transform" : + [ + { + "id" : "device_1", + "leds" : "0,1,2,3-10, 12-32, 33, 34, 35-49", + "hsv" : + { + "saturationGain" : 1.0000, + "valueGain" : 1.5000 + }, + "red" : + { + "threshold" : 0.1000, + "gamma" : 2.0000, + "blacklevel" : 0.0000, + "whitelevel" : 0.8000 + }, + "green" : + { + "threshold" : 0.1000, + "gamma" : 2.0000, + "blacklevel" : 0.0000, + "whitelevel" : 1.0000 + }, + "blue" : + { + "threshold" : 0.1000, + "gamma" : 2.0000, + "blacklevel" : 0.0000, + "whitelevel" : 1.0000 + } + }, + { + "id" : "device_2", + "leds" : "11", + "hsv" : + { + "saturationGain" : 1.0000, + "valueGain" : 1.5000 + }, + "red" : + { + "threshold" : 0.1000, + "gamma" : 2.0000, + "blacklevel" : 0.0000, + "whitelevel" : 0.8000 + }, + "green" : + { + "threshold" : 0.1000, + "gamma" : 2.0000, + "blacklevel" : 0.0000, + "whitelevel" : 1.0000 + }, + "blue" : + { + "threshold" : 0.1000, + "gamma" : 2.0000, + "blacklevel" : 0.0000, + "whitelevel" : 1.0000 + } + } + ], + "smoothing" : { "type" : "none", @@ -68,16 +106,16 @@ } }, - /// The configuration for each individual led. This contains the specification of the area - /// averaged of an input image for each led to determine its color. Each item in the list + /// The configuration for each individual led. This contains the specification of the area + /// averaged of an input image for each led to determine its color. Each item in the list /// contains the following fields: - /// * index: The index of the led. This determines its location in the string of leds; zero + /// * index: The index of the led. This determines its location in the string of leds; zero /// being the first led. - /// * hscan: The fractional part of the image along the horizontal used for the averaging + /// * hscan: The fractional part of the image along the horizontal used for the averaging /// (minimum and maximum inclusive) - /// * vscan: The fractional part of the image along the vertical used for the averaging + /// * vscan: The fractional part of the image along the vertical used for the averaging /// (minimum and maximum inclusive) - "leds" : + "leds" : [ { "index" : 0, @@ -331,15 +369,15 @@ } ], - /// The black border configuration, contains the following items: + /// The black border configuration, contains the following items: /// * enable : true if the detector should be activated "blackborderdetector" : { "enable" : true }, - /// The boot-sequence configuration, contains the following items: - /// * type : The type of the boot-sequence ('rainbow', 'knight_rider', 'none') + /// The boot-sequence configuration, contains the following items: + /// * type : The type of the boot-sequence ('rainbow', 'knight_rider', 'none') /// * duration_ms : The length of the boot-sequence [ms] "bootsequence" : { @@ -347,7 +385,7 @@ "duration_ms" : 3000 }, - /// The configuration for the frame-grabber, contains the following items: + /// The configuration for the frame-grabber, contains the following items: /// * width : The width of the grabbed frames [pixels] /// * height : The height of the grabbed frames [pixels] /// * frequency_Hz : The frequency of the frame grab [Hz] @@ -358,22 +396,22 @@ "frequency_Hz" : 10.0 }, - /// The configuration of the XBMC connection used to enable and disable the frame-grabber. Contains the following fields: + /// The configuration of the XBMC connection used to enable and disable the frame-grabber. Contains the following fields: /// * xbmcAddress : The IP address of the XBMC-host /// * xbmcTcpPort : The TCP-port of the XBMC-server /// * grabVideo : Flag indicating that the frame-grabber is on(true) during video playback /// * grabPictures : Flag indicating that the frame-grabber is on(true) during picture show /// * grabAudio : Flag indicating that the frame-grabber is on(true) during audio playback /// * grabMenu : Flag indicating that the frame-grabber is on(true) in the XBMC menu - "xbmcVideoChecker" : - { - "xbmcAddress" : "127.0.0.1", - "xbmcTcpPort" : 9090, - "grabVideo" : true, - "grabPictures" : true, - "grabAudio" : true, - "grabMenu" : false - }, +// "xbmcVideoChecker" : +// { +// "xbmcAddress" : "127.0.0.1", +// "xbmcTcpPort" : 9090, +// "grabVideo" : true, +// "grabPictures" : true, +// "grabAudio" : true, +// "grabMenu" : false +// }, /// The configuration of the Json server which enables the json remote interface /// * port : Port at which the json server is started From 958feabf5bc377387314ac2c25ed71a0eb147cbf Mon Sep 17 00:00:00 2001 From: "T. van der Zwan" Date: Fri, 22 Nov 2013 10:47:05 +0000 Subject: [PATCH 10/26] Added double as possible type Former-commit-id: 0caa8d0cb81db4d6592db296cb1f775e91efce50 --- libsrc/utils/jsonschema/JsonSchemaChecker.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libsrc/utils/jsonschema/JsonSchemaChecker.cpp b/libsrc/utils/jsonschema/JsonSchemaChecker.cpp index 4cfa0935..da0c51d8 100644 --- a/libsrc/utils/jsonschema/JsonSchemaChecker.cpp +++ b/libsrc/utils/jsonschema/JsonSchemaChecker.cpp @@ -154,6 +154,8 @@ void JsonSchemaChecker::checkType(const Json::Value & value, const Json::Value & wrongType = !value.isNumeric(); else if (type == "integer") wrongType = !value.isIntegral(); + else if (type == "double") + wrongType = !value.isDouble(); else if (type == "boolean") wrongType = !value.isBool(); else if (type == "object") From 826b964bf6aefc87751a22c4218109898087a8d8 Mon Sep 17 00:00:00 2001 From: "T. van der Zwan" Date: Fri, 22 Nov 2013 10:48:10 +0000 Subject: [PATCH 11/26] Implemented the multi-color transform including in hyperion-remote Former-commit-id: ebdb0688b47d51bd6dccf6dafd580d3ce9ed80a7 --- config/hyperion.config.json | 82 +++---- include/hyperion/ColorTransform.h | 26 +++ include/hyperion/Hyperion.h | 31 +-- libsrc/hyperion/CMakeLists.txt | 1 + libsrc/hyperion/Hyperion.cpp | 209 ++++++++---------- libsrc/hyperion/MultiColorTransform.cpp | 48 ++-- libsrc/hyperion/MultiColorTransform.h | 37 ++-- libsrc/jsonserver/JsonClientConnection.cpp | 103 +++++---- .../jsonserver/schema/schema-transform.json | 136 ++++++------ src/hyperion-remote/JsonConnection.cpp | 7 +- src/hyperion-remote/JsonConnection.h | 2 + src/hyperion-remote/hyperion-remote.cpp | 5 + 12 files changed, 357 insertions(+), 330 deletions(-) create mode 100644 include/hyperion/ColorTransform.h diff --git a/config/hyperion.config.json b/config/hyperion.config.json index bba5ce09..fa6a7535 100644 --- a/config/hyperion.config.json +++ b/config/hyperion.config.json @@ -2,7 +2,7 @@ // Generated by: HyperCon (The Hyperion deamon configuration file builder { - /// Device configuration contains the following fields: + /// Device configuration contains the following fields: /// * 'name' : The user friendly name of the device (only used for display purposes) /// * 'type' : The type of the device or leds (known types for now are 'ws2801', 'ldp8806', /// 'lpd6803', 'sedu', 'adalight', 'lightpack', 'test' and 'none') @@ -34,32 +34,38 @@ /// - 'updateFrequency' The update frequency of the leds in Hz "color" : { - "hsv" : - { - "saturationGain" : 1.0000, - "valueGain" : 1.5000 - }, - "red" : - { - "threshold" : 0.1000, - "gamma" : 2.0000, - "blacklevel" : 0.0000, - "whitelevel" : 0.8000 - }, - "green" : - { - "threshold" : 0.1000, - "gamma" : 2.0000, - "blacklevel" : 0.0000, - "whitelevel" : 1.0000 - }, - "blue" : - { - "threshold" : 0.1000, - "gamma" : 2.0000, - "blacklevel" : 0.0000, - "whitelevel" : 1.0000 - }, + "transforms" : + [ + { + "id" : "default", + "hsv" : + { + "saturationGain" : 1.0000, + "valueGain" : 1.5000 + }, + "red" : + { + "threshold" : 0.1000, + "gamma" : 2.0000, + "blacklevel" : 0.0000, + "whitelevel" : 0.8000 + }, + "green" : + { + "threshold" : 0.1000, + "gamma" : 2.0000, + "blacklevel" : 0.0000, + "whitelevel" : 1.0000 + }, + "blue" : + { + "threshold" : 0.1000, + "gamma" : 2.0000, + "blacklevel" : 0.0000, + "whitelevel" : 1.0000 + }, + } + ], "smoothing" : { "type" : "none", @@ -68,16 +74,16 @@ } }, - /// The configuration for each individual led. This contains the specification of the area - /// averaged of an input image for each led to determine its color. Each item in the list + /// The configuration for each individual led. This contains the specification of the area + /// averaged of an input image for each led to determine its color. Each item in the list /// contains the following fields: - /// * index: The index of the led. This determines its location in the string of leds; zero + /// * index: The index of the led. This determines its location in the string of leds; zero /// being the first led. - /// * hscan: The fractional part of the image along the horizontal used for the averaging + /// * hscan: The fractional part of the image along the horizontal used for the averaging /// (minimum and maximum inclusive) - /// * vscan: The fractional part of the image along the vertical used for the averaging + /// * vscan: The fractional part of the image along the vertical used for the averaging /// (minimum and maximum inclusive) - "leds" : + "leds" : [ { "index" : 0, @@ -331,15 +337,15 @@ } ], - /// The black border configuration, contains the following items: + /// The black border configuration, contains the following items: /// * enable : true if the detector should be activated "blackborderdetector" : { "enable" : true }, - /// The boot-sequence configuration, contains the following items: - /// * type : The type of the boot-sequence ('rainbow', 'knight_rider', 'none') + /// The boot-sequence configuration, contains the following items: + /// * type : The type of the boot-sequence ('rainbow', 'knight_rider', 'none') /// * duration_ms : The length of the boot-sequence [ms] "bootsequence" : { @@ -347,7 +353,7 @@ "duration_ms" : 3000 }, - /// The configuration for the frame-grabber, contains the following items: + /// The configuration for the frame-grabber, contains the following items: /// * width : The width of the grabbed frames [pixels] /// * height : The height of the grabbed frames [pixels] /// * frequency_Hz : The frequency of the frame grab [Hz] @@ -358,7 +364,7 @@ "frequency_Hz" : 10.0 }, - /// The configuration of the XBMC connection used to enable and disable the frame-grabber. Contains the following fields: + /// The configuration of the XBMC connection used to enable and disable the frame-grabber. Contains the following fields: /// * xbmcAddress : The IP address of the XBMC-host /// * xbmcTcpPort : The TCP-port of the XBMC-server /// * grabVideo : Flag indicating that the frame-grabber is on(true) during video playback diff --git a/include/hyperion/ColorTransform.h b/include/hyperion/ColorTransform.h new file mode 100644 index 00000000..50cbedba --- /dev/null +++ b/include/hyperion/ColorTransform.h @@ -0,0 +1,26 @@ +#pragma once + +// STL includes +#include + +// Utils includes +#include +#include + +class ColorTransform +{ +public: + + /// Unique identifier for this color transform + std::string _id; + + /// The RED-Channel (RGB) transform + RgbChannelTransform _rgbRedTransform; + /// The GREEN-Channel (RGB) transform + RgbChannelTransform _rgbGreenTransform; + /// The BLUE-Channel (RGB) transform + RgbChannelTransform _rgbBlueTransform; + + /// The HSV Transform for applying Saturation and Value transforms + HsvTransform _hsvTransform; +}; diff --git a/include/hyperion/Hyperion.h b/include/hyperion/Hyperion.h index 6f726241..605ce6a1 100644 --- a/include/hyperion/Hyperion.h +++ b/include/hyperion/Hyperion.h @@ -16,6 +16,7 @@ #include // Forward class declaration +class ColorTransform; class HsvTransform; class RgbChannelTransform; class MultiColorTransform; @@ -89,15 +90,16 @@ public: void setColors(int priority, const std::vector &ledColors, const int timeout_ms); /// - /// Sets/Updates a part of the color transformation. + /// Returns the list with unique transform identifiers + /// @return The list with transform identifiers /// - /// @param[in] transform The type of transform to configure - /// @param[in] color The color channel to which the transform applies (only applicable for - /// Transform::THRESHOLD, Transform::GAMMA, Transform::BLACKLEVEL, - /// Transform::WHITELEVEL) - /// @param[in] value The new value for the given transform + const std::vector & getTransformIds() const; + /// - void setTransform(Transform transform, RgbChannel color, double value); + /// Returns the ColorTransform with the given identifier + /// @return The transform with the given identifier (or nullptr if the identifier does not exist) + /// + ColorTransform * getTransform(const std::string& id); /// /// Clears the given priority channel. This will switch the led-colors to the colors of the next @@ -112,18 +114,6 @@ public: /// void clearall(); - /// - /// Returns the value of a specific color transform - /// - /// @param[in] transform The type of transform - /// @param[in] color The color channel to which the transform applies (only applicable for - /// Transform::THRESHOLD, Transform::GAMMA, Transform::BLACKLEVEL, - /// Transform::WHITELEVEL) - /// - /// @return The value of the specified color transform - /// - double getTransform(Transform transform, RgbChannel color) const; - /// /// Returns a list of active priorities /// @@ -147,8 +137,9 @@ public: static LedString createLedString(const Json::Value & ledsConfig); static MultiColorTransform * createLedColorsTransform(const unsigned ledCnt, const Json::Value & colorTransformConfig); + static ColorTransform * createColorTransform(const Json::Value & transformConfig); static HsvTransform * createHsvTransform(const Json::Value & hsvConfig); - static RgbChannelTransform * createColorTransform(const Json::Value& colorConfig); + static RgbChannelTransform * createRgbChannelTransform(const Json::Value& colorConfig); static LedDevice * createColorSmoothing(const Json::Value & smoothingConfig, LedDevice * ledDevice); diff --git a/libsrc/hyperion/CMakeLists.txt b/libsrc/hyperion/CMakeLists.txt index 03d9df24..c984aa62 100644 --- a/libsrc/hyperion/CMakeLists.txt +++ b/libsrc/hyperion/CMakeLists.txt @@ -77,6 +77,7 @@ add_library(hyperion ${Hyperion_SOURCES} ${Hyperion_RESOURCES_RCC} ) +message("{QT_LIBRARIES} = ${QT_LIBRARIES}") target_link_libraries(hyperion hyperion-utils diff --git a/libsrc/hyperion/Hyperion.cpp b/libsrc/hyperion/Hyperion.cpp index 64b1be63..472ae96f 100644 --- a/libsrc/hyperion/Hyperion.cpp +++ b/libsrc/hyperion/Hyperion.cpp @@ -4,6 +4,9 @@ // QT includes #include +#include +#include +#include // JsonSchema include #include @@ -153,29 +156,94 @@ Hyperion::ColorOrder Hyperion::createColorOrder(const Json::Value &deviceConfig) return ORDER_RGB; } +ColorTransform * Hyperion::createColorTransform(const Json::Value & transformConfig) +{ + const std::string id = transformConfig.get("id", "default").asString(); + + RgbChannelTransform * redTransform = createRgbChannelTransform(transformConfig["red"]); + RgbChannelTransform * greenTransform = createRgbChannelTransform(transformConfig["green"]); + RgbChannelTransform * blueTransform = createRgbChannelTransform(transformConfig["blue"]); + + HsvTransform * hsvTransform = createHsvTransform(transformConfig["hsv"]); + + ColorTransform * transform = new ColorTransform(); + transform->_id = id; + transform->_rgbRedTransform = *redTransform; + transform->_rgbGreenTransform = *greenTransform; + transform->_rgbBlueTransform = *blueTransform; + transform->_hsvTransform = *hsvTransform; + + // Cleanup the allocated individual transforms + delete redTransform; + delete greenTransform; + delete blueTransform; + delete hsvTransform; + + return transform; +} + MultiColorTransform * Hyperion::createLedColorsTransform(const unsigned ledCnt, const Json::Value & colorConfig) { - MultiColorTransform * transform = new MultiColorTransform(); - if (!colorConfig.isArray()) + // Create the result, the transforms are added to this + MultiColorTransform * transform = new MultiColorTransform(ledCnt); + + const Json::Value transformConfig = colorConfig.get("transform", Json::nullValue); + if (transformConfig.isNull()) { // Old style color transformation config (just one for all leds) - const std::string id = "default"; - - RgbChannelTransform * redTransform = createColorTransform(colorConfig["red"]); - RgbChannelTransform * greenTransform = createColorTransform(colorConfig["green"]); - RgbChannelTransform * blueTransform = createColorTransform(colorConfig["blue"]); - - HsvTransform * hsvTransform = createHsvTransform(colorConfig["hsv"]); - transform->addTransform(id, *redTransform, *greenTransform, *blueTransform, *hsvTransform); - transform->setTransformForLed(id, 0, ledCnt-1); - - delete redTransform; - delete greenTransform; - delete blueTransform; + ColorTransform * colorTransform = createColorTransform(colorConfig); + transform->addTransform(colorTransform); + transform->setTransformForLed(colorTransform->_id, 0, ledCnt-1); + } + else if (!transformConfig.isArray()) + { + ColorTransform * colorTransform = createColorTransform(transformConfig); + transform->addTransform(colorTransform); + transform->setTransformForLed(colorTransform->_id, 0, ledCnt-1); } else { + const QRegExp overallExp("([0-9]+(\\-[0-9]+)?)(,[ ]*([0-9]+(\\-[0-9]+)?))*"); + for (Json::UInt i = 0; i < transformConfig.size(); ++i) + { + const Json::Value & config = transformConfig[i]; + ColorTransform * colorTransform = createColorTransform(config); + transform->addTransform(colorTransform); + + const QString ledIndicesStr = config.get("leds", "").asCString(); + if (!overallExp.exactMatch(ledIndicesStr)) + { + std::cerr << "Given led indices " << i << " not correct format: " << ledIndicesStr.toStdString() << std::endl; + continue; + } + + std::cout << "ColorTransform '" << colorTransform->_id << "' => ["; + + const QStringList ledIndexList = ledIndicesStr.split(","); + for (int i=0; i 0) + { + std::cout << ", "; + } + if (ledIndexList[i].contains("-")) + { + QStringList ledIndices = ledIndexList[i].split("-"); + int startInd = ledIndices[0].toInt(); + int endInd = ledIndices[1].toInt(); + + transform->setTransformForLed(colorTransform->_id, startInd, endInd); + std::cout << startInd << "-" << endInd; + } + else + { + int index = ledIndexList[i].toInt(); + transform->setTransformForLed(colorTransform->_id, index, index); + std::cout << index; + } + } + std::cout << "]" << std::endl; + } } return transform; } @@ -188,7 +256,7 @@ HsvTransform * Hyperion::createHsvTransform(const Json::Value & hsvConfig) return new HsvTransform(saturationGain, valueGain); } -RgbChannelTransform* Hyperion::createColorTransform(const Json::Value& colorConfig) +RgbChannelTransform* Hyperion::createRgbChannelTransform(const Json::Value& colorConfig) { const double threshold = colorConfig.get("threshold", 0.0).asDouble(); const double gamma = colorConfig.get("gamma", 1.0).asDouble(); @@ -276,6 +344,10 @@ Hyperion::Hyperion(const Json::Value &jsonConfig) : _device(createDevice(jsonConfig["device"])), _timer() { + if (!_raw2ledTransform->verifyTransforms()) + { + throw std::runtime_error("Color transformation incorrectly set"); + } // initialize the image processor factory ImageProcessorFactory::getInstance().init(_ledString, jsonConfig["blackborderdetector"].get("enable", true).asBool()); @@ -336,59 +408,14 @@ void Hyperion::setColors(int priority, const std::vector& ledColors, c } } -void Hyperion::setTransform(Hyperion::Transform transform, Hyperion::RgbChannel color, double value) +const std::vector & Hyperion::getTransformIds() const { - ColorTransform* colorTransform = _raw2ledTransform->getTransform("default"); - assert(colorTransform != nullptr); + return _raw2ledTransform->getTransformIds(); +} - // select the transform of the requested color - RgbChannelTransform * t = nullptr; - switch (color) - { - case RED: - t = &(colorTransform->_rgbRedTransform); - break; - case GREEN: - t = &(colorTransform->_rgbGreenTransform); - break; - case BLUE: - t = &(colorTransform->_rgbBlueTransform); - break; - default: - break; - } - - // set transform value - switch (transform) - { - case SATURATION_GAIN: - colorTransform->_hsvTransform.setSaturationGain(value); - break; - case VALUE_GAIN: - colorTransform->_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(); +ColorTransform * Hyperion::getTransform(const std::string& id) +{ + return _raw2ledTransform->getTransform(id); } void Hyperion::clear(int priority) @@ -413,54 +440,6 @@ void Hyperion::clearall() update(); } -double Hyperion::getTransform(Hyperion::Transform transform, Hyperion::RgbChannel color) const -{ - ColorTransform * colorTransform = _raw2ledTransform->getTransform("default"); - assert(colorTransform != nullptr); - - // select the transform of the requested color - RgbChannelTransform * t = nullptr; - switch (color) - { - case RED: - t = &(colorTransform->_rgbRedTransform); - break; - case GREEN: - t = &(colorTransform->_rgbGreenTransform); - break; - case BLUE: - t = &(colorTransform->_rgbBlueTransform); - break; - default: - break; - } - - // set transform value - switch (transform) - { - case SATURATION_GAIN: - return colorTransform->_hsvTransform.getSaturationGain(); - case VALUE_GAIN: - return colorTransform->_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); - } - - return 999.0; -} - QList Hyperion::getActivePriorities() const { return _muxer.getPriorities(); diff --git a/libsrc/hyperion/MultiColorTransform.cpp b/libsrc/hyperion/MultiColorTransform.cpp index 99b81b27..d15f020e 100644 --- a/libsrc/hyperion/MultiColorTransform.cpp +++ b/libsrc/hyperion/MultiColorTransform.cpp @@ -5,7 +5,8 @@ // Hyperion includes #include "MultiColorTransform.h" -MultiColorTransform::MultiColorTransform() +MultiColorTransform::MultiColorTransform(const unsigned ledCnt) : + _ledTransforms(ledCnt, nullptr) { } @@ -18,33 +19,16 @@ MultiColorTransform::~MultiColorTransform() } } -void MultiColorTransform::addTransform(const std::string & id, - const RgbChannelTransform & redTransform, - const RgbChannelTransform & greenTransform, - const RgbChannelTransform & blueTransform, - const HsvTransform & hsvTransform) +void MultiColorTransform::addTransform(ColorTransform * transform) { - ColorTransform * transform = new ColorTransform(); - transform->_id = id; - - transform->_rgbRedTransform = redTransform; - transform->_rgbGreenTransform = greenTransform; - transform->_rgbBlueTransform = blueTransform; - - transform->_hsvTransform = hsvTransform; - + _transformIds.push_back(transform->_id); _transform.push_back(transform); } void MultiColorTransform::setTransformForLed(const std::string& id, const unsigned startLed, const unsigned endLed) { assert(startLed <= endLed); - - // Make sure that there are at least enough led transforms to match the given indices - if (_ledTransforms.size() < endLed+1) - { - _ledTransforms.resize(endLed+1, nullptr); - } + assert(endLed < _ledTransforms.size()); // Get the identified transform (don't care if is nullptr) ColorTransform * transform = getTransform(id); @@ -54,15 +38,23 @@ void MultiColorTransform::setTransformForLed(const std::string& id, const unsign } } -std::vector MultiColorTransform::getTransformIds() +bool MultiColorTransform::verifyTransforms() const { - // Create the list on the fly - std::vector transformIds; - for (ColorTransform* transform : _transform) + bool allLedsSet = true; + for (unsigned iLed=0; iLed<_ledTransforms.size(); ++iLed) { - transformIds.push_back(transform->_id); + if (_ledTransforms[iLed] == nullptr) + { + std::cerr << "No transform set for " << iLed << std::endl; + allLedsSet = false; + } } - return transformIds; + return allLedsSet; +} + +const std::vector & MultiColorTransform::getTransformIds() +{ + return _transformIds; } ColorTransform* MultiColorTransform::getTransform(const std::string& id) @@ -85,7 +77,7 @@ std::vector MultiColorTransform::applyTransform(const std::vector ledColors(rawColors); - const size_t itCnt = std::min(_transform.size(), rawColors.size()); + const size_t itCnt = std::min(_ledTransforms.size(), rawColors.size()); for (size_t i=0; i -#include -#include - -struct ColorTransform -{ - std::string _id; - /// The RED-Channel (RGB) transform - RgbChannelTransform _rgbRedTransform; - /// The GREEN-Channel (RGB) transform - RgbChannelTransform _rgbGreenTransform; - /// The BLUE-Channel (RGB) transform - RgbChannelTransform _rgbBlueTransform; - /// The HSV Transform for applying Saturation and Value transforms - HsvTransform _hsvTransform; -}; +// Hyperion includes +#include /// /// The LedColorTransform is responsible for performing color transformation from 'raw' colors @@ -29,22 +16,25 @@ struct ColorTransform class MultiColorTransform { public: - MultiColorTransform(); + MultiColorTransform(const unsigned ledCnt); ~MultiColorTransform(); - void addTransform(const std::string & id, - const RgbChannelTransform & redTransform, - const RgbChannelTransform & greenTransform, - const RgbChannelTransform & blueTransform, - const HsvTransform & hsvTransform); + /** + * Adds a new ColorTransform to this MultiColorTransform + * + * @param transform The new ColorTransform (ownership is transfered) + */ + void addTransform(ColorTransform * transform); void setTransformForLed(const std::string& id, const unsigned startLed, const unsigned endLed); + bool verifyTransforms() const; + /// /// Returns the identifier of all the unique ColorTransform /// /// @return The list with unique id's of the ColorTransforms - std::vector getTransformIds(); + const std::vector & getTransformIds(); /// /// Returns the pointer to the ColorTransform with the given id @@ -65,6 +55,9 @@ public: std::vector applyTransform(const std::vector& rawColors); private: + /// List with transform ids + std::vector _transformIds; + /// List with unique ColorTransforms std::vector _transform; diff --git a/libsrc/jsonserver/JsonClientConnection.cpp b/libsrc/jsonserver/JsonClientConnection.cpp index 1c99b13d..cd5894db 100644 --- a/libsrc/jsonserver/JsonClientConnection.cpp +++ b/libsrc/jsonserver/JsonClientConnection.cpp @@ -12,9 +12,10 @@ #include // hyperion util includes -#include "hyperion/ImageProcessorFactory.h" -#include "hyperion/ImageProcessor.h" -#include "utils/ColorRgb.h" +#include +#include +#include +#include // project includes #include "JsonClientConnection.h" @@ -173,25 +174,39 @@ 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)); - threshold.append(_hyperion->getTransform(Hyperion::THRESHOLD, Hyperion::BLUE)); - Json::Value & gamma = transform["gamma"]; - gamma.append(_hyperion->getTransform(Hyperion::GAMMA, Hyperion::RED)); - gamma.append(_hyperion->getTransform(Hyperion::GAMMA, Hyperion::GREEN)); - gamma.append(_hyperion->getTransform(Hyperion::GAMMA, Hyperion::BLUE)); - Json::Value & blacklevel = transform["blacklevel"]; - blacklevel.append(_hyperion->getTransform(Hyperion::BLACKLEVEL, Hyperion::RED)); - blacklevel.append(_hyperion->getTransform(Hyperion::BLACKLEVEL, Hyperion::GREEN)); - blacklevel.append(_hyperion->getTransform(Hyperion::BLACKLEVEL, Hyperion::BLUE)); - Json::Value & whitelevel = transform["whitelevel"]; - whitelevel.append(_hyperion->getTransform(Hyperion::WHITELEVEL, Hyperion::RED)); - whitelevel.append(_hyperion->getTransform(Hyperion::WHITELEVEL, Hyperion::GREEN)); - whitelevel.append(_hyperion->getTransform(Hyperion::WHITELEVEL, Hyperion::BLUE)); + Json::Value & transformArray = info["transform"]; + for (const std::string& transformId : _hyperion->getTransformIds()) + { + const ColorTransform * colorTransform = _hyperion->getTransform(transformId); + if (colorTransform == nullptr) + { + std::cerr << "Incorrect color transform id: " << transformId << std::endl; + continue; + } + + Json::Value & transform = transformArray.append(Json::Value()); + transform["id"] = transformId; + + transform["saturationGain"] = colorTransform->_hsvTransform.getSaturationGain(); + transform["valueGain"] = colorTransform->_hsvTransform.getValueGain(); + + Json::Value & threshold = transform["threshold"]; + threshold.append(colorTransform->_rgbRedTransform.getThreshold()); + threshold.append(colorTransform->_rgbGreenTransform.getThreshold()); + threshold.append(colorTransform->_rgbBlueTransform.getThreshold()); + Json::Value & gamma = transform["gamma"]; + gamma.append(colorTransform->_rgbRedTransform.getGamma()); + gamma.append(colorTransform->_rgbGreenTransform.getGamma()); + gamma.append(colorTransform->_rgbBlueTransform.getGamma()); + Json::Value & blacklevel = transform["blacklevel"]; + blacklevel.append(colorTransform->_rgbRedTransform.getBlacklevel()); + blacklevel.append(colorTransform->_rgbGreenTransform.getBlacklevel()); + blacklevel.append(colorTransform->_rgbBlueTransform.getBlacklevel()); + Json::Value & whitelevel = transform["whitelevel"]; + whitelevel.append(colorTransform->_rgbRedTransform.getWhitelevel()); + whitelevel.append(colorTransform->_rgbGreenTransform.getWhitelevel()); + whitelevel.append(colorTransform->_rgbBlueTransform.getWhitelevel()); + } // send the result sendMessage(result); @@ -222,46 +237,54 @@ void JsonClientConnection::handleTransformCommand(const Json::Value &message) { const Json::Value & transform = message["transform"]; + const std::string transformId = transform.get("id", _hyperion->getTransformIds().front()).asString(); + ColorTransform * colorTransform = _hyperion->getTransform(transformId); + if (colorTransform == nullptr) + { + //sendErrorReply(std::string("Incorrect transform identifier: ") + transformId); + return; + } + if (transform.isMember("saturationGain")) { - _hyperion->setTransform(Hyperion::SATURATION_GAIN, Hyperion::INVALID, transform["saturationGain"].asDouble()); + colorTransform->_hsvTransform.setSaturationGain(transform["saturationGain"].asDouble()); } if (transform.isMember("valueGain")) { - _hyperion->setTransform(Hyperion::VALUE_GAIN, Hyperion::INVALID, transform["valueGain"].asDouble()); + colorTransform->_hsvTransform.setValueGain(transform["valueGain"].asDouble()); } if (transform.isMember("threshold")) { - const Json::Value & threshold = transform["threshold"]; - _hyperion->setTransform(Hyperion::THRESHOLD, Hyperion::RED, threshold[0u].asDouble()); - _hyperion->setTransform(Hyperion::THRESHOLD, Hyperion::GREEN, threshold[1u].asDouble()); - _hyperion->setTransform(Hyperion::THRESHOLD, Hyperion::BLUE, threshold[2u].asDouble()); + const Json::Value & values = transform["threshold"]; + colorTransform->_rgbRedTransform .setThreshold(values[0u].asDouble()); + colorTransform->_rgbGreenTransform.setThreshold(values[1u].asDouble()); + colorTransform->_rgbBlueTransform .setThreshold(values[2u].asDouble()); } if (transform.isMember("gamma")) { - const Json::Value & threshold = transform["gamma"]; - _hyperion->setTransform(Hyperion::GAMMA, Hyperion::RED, threshold[0u].asDouble()); - _hyperion->setTransform(Hyperion::GAMMA, Hyperion::GREEN, threshold[1u].asDouble()); - _hyperion->setTransform(Hyperion::GAMMA, Hyperion::BLUE, threshold[2u].asDouble()); + const Json::Value & values = transform["gamma"]; + colorTransform->_rgbRedTransform .setGamma(values[0u].asDouble()); + colorTransform->_rgbGreenTransform.setGamma(values[1u].asDouble()); + colorTransform->_rgbBlueTransform .setGamma(values[2u].asDouble()); } if (transform.isMember("blacklevel")) { - const Json::Value & threshold = transform["blacklevel"]; - _hyperion->setTransform(Hyperion::BLACKLEVEL, Hyperion::RED, threshold[0u].asDouble()); - _hyperion->setTransform(Hyperion::BLACKLEVEL, Hyperion::GREEN, threshold[1u].asDouble()); - _hyperion->setTransform(Hyperion::BLACKLEVEL, Hyperion::BLUE, threshold[2u].asDouble()); + const Json::Value & values = transform["blacklevel"]; + colorTransform->_rgbRedTransform .setBlacklevel(values[0u].asDouble()); + colorTransform->_rgbGreenTransform.setBlacklevel(values[1u].asDouble()); + colorTransform->_rgbBlueTransform .setBlacklevel(values[2u].asDouble()); } if (transform.isMember("whitelevel")) { - const Json::Value & threshold = transform["whitelevel"]; - _hyperion->setTransform(Hyperion::WHITELEVEL, Hyperion::RED, threshold[0u].asDouble()); - _hyperion->setTransform(Hyperion::WHITELEVEL, Hyperion::GREEN, threshold[1u].asDouble()); - _hyperion->setTransform(Hyperion::WHITELEVEL, Hyperion::BLUE, threshold[2u].asDouble()); + const Json::Value & values = transform["whitelevel"]; + colorTransform->_rgbRedTransform .setWhitelevel(values[0u].asDouble()); + colorTransform->_rgbGreenTransform.setWhitelevel(values[1u].asDouble()); + colorTransform->_rgbBlueTransform .setWhitelevel(values[2u].asDouble()); } sendSuccessReply(); diff --git a/libsrc/jsonserver/schema/schema-transform.json b/libsrc/jsonserver/schema/schema-transform.json index 26fdf787..a135e12f 100644 --- a/libsrc/jsonserver/schema/schema-transform.json +++ b/libsrc/jsonserver/schema/schema-transform.json @@ -1,68 +1,72 @@ { - "type":"object", - "required":true, - "properties":{ - "command": { - "type" : "string", - "required" : true, - "enum" : ["transform"] - }, - "transform": { - "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, - "items" : { - "type": "double", - "minimum": 0.0, - "maximum": 1.0 - }, - "minItems": 3, - "maxItems": 3 - }, - "gamma": { - "type": "array", - "required": false, - "items" : { - "type": "double", - "minimum": 0.0 - }, - "minItems": 3, - "maxItems": 3 - }, - "blacklevel": { - "type": "array", - "required": false, - "items" : { - "type": "double" - }, - "minItems": 3, - "maxItems": 3 - }, - "whitelevel": { - "type": "array", - "required": false, - "items" : { - "type": "double" - }, - "minItems": 3, - "maxItems": 3 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false + "type":"object", + "required":true, + "properties":{ + "command": { + "type" : "string", + "required" : true, + "enum" : ["transform"] + }, + "transform": { + "type": "object", + "required": true, + "properties": { + "id" : { + "type" : "string", + "required" : false + }, + "saturationGain" : { + "type" : "double", + "required" : false, + "minimum" : 0.0 + }, + "valueGain" : { + "type" : "double", + "required" : false, + "minimum" : 0.0 + }, + "threshold": { + "type": "array", + "required": false, + "items" : { + "type": "double", + "minimum": 0.0, + "maximum": 1.0 + }, + "minItems": 3, + "maxItems": 3 + }, + "gamma": { + "type": "array", + "required": false, + "items" : { + "type": "double", + "minimum": 0.0 + }, + "minItems": 3, + "maxItems": 3 + }, + "blacklevel": { + "type": "array", + "required": false, + "items" : { + "type": "double" + }, + "minItems": 3, + "maxItems": 3 + }, + "whitelevel": { + "type": "array", + "required": false, + "items" : { + "type": "double" + }, + "minItems": 3, + "maxItems": 3 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } diff --git a/src/hyperion-remote/JsonConnection.cpp b/src/hyperion-remote/JsonConnection.cpp index d742daaa..641c7681 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(double * saturation, double * value, ColorTransformValues *threshold, ColorTransformValues *gamma, ColorTransformValues *blacklevel, ColorTransformValues *whitelevel) +void JsonConnection::setTransform(std::string * transformId, double * saturation, double * value, ColorTransformValues *threshold, ColorTransformValues *gamma, ColorTransformValues *blacklevel, ColorTransformValues *whitelevel) { std::cout << "Set color transforms" << std::endl; @@ -169,6 +169,11 @@ void JsonConnection::setTransform(double * saturation, double * value, ColorTran command["command"] = "transform"; Json::Value & transform = command["transform"]; + if (transformId != nullptr) + { + transform["id"] = *transformId; + } + if (saturation != nullptr) { transform["saturationGain"] = *saturation; diff --git a/src/hyperion-remote/JsonConnection.h b/src/hyperion-remote/JsonConnection.h index 33fc5b4a..6bd358b5 100644 --- a/src/hyperion-remote/JsonConnection.h +++ b/src/hyperion-remote/JsonConnection.h @@ -76,6 +76,7 @@ public: /// /// @note Note that providing a NULL will leave the settings on the server unchanged /// + /// @param transformId The identifier of the transform to set /// @param saturation The HSV saturation gain /// @param value The HSV value gain /// @param threshold The threshold @@ -84,6 +85,7 @@ public: /// @param whitelevel The whitelevel /// void setTransform( + std::string * transformId, double * saturation, double * value, ColorTransformValues * threshold, diff --git a/src/hyperion-remote/hyperion-remote.cpp b/src/hyperion-remote/hyperion-remote.cpp index bbe4f408..b290b4f2 100644 --- a/src/hyperion-remote/hyperion-remote.cpp +++ b/src/hyperion-remote/hyperion-remote.cpp @@ -45,6 +45,7 @@ int main(int argc, char * argv[]) 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"); + StringParameter & argId = parameters.add ('q', "qualifier" , "Identifier(qualifier) of the transform to set"); 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)"); @@ -83,6 +84,7 @@ 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 << " " << argId.usageLine() << std::endl; std::cerr << " " << argSaturation.usageLine() << std::endl; std::cerr << " " << argValue.usageLine() << std::endl; std::cerr << " " << argThreshold.usageLine() << std::endl; @@ -119,9 +121,11 @@ int main(int argc, char * argv[]) } else if (colorTransform) { + std::string transId; double saturation, value; ColorTransformValues threshold, gamma, blacklevel, whitelevel; + if (argId.isSet()) transId = argId.getValue(); if (argSaturation.isSet()) saturation = argSaturation.getValue(); if (argValue.isSet()) value = argValue.getValue(); if (argThreshold.isSet()) threshold = argThreshold.getValue(); @@ -130,6 +134,7 @@ int main(int argc, char * argv[]) if (argWhitelevel.isSet()) whitelevel = argWhitelevel.getValue(); connection.setTransform( + argId.isSet() ? &transId : nullptr, argSaturation.isSet() ? &saturation : nullptr, argValue.isSet() ? &value : nullptr, argThreshold.isSet() ? &threshold : nullptr, From 08b4980ac5649bf173a44c31beb8db2e2d7422c6 Mon Sep 17 00:00:00 2001 From: "T. van der Zwan" Date: Fri, 22 Nov 2013 13:51:19 +0000 Subject: [PATCH 12/26] Removed unused code lines Former-commit-id: 199de062788fd0fb391f20ac7fc530cde23bf338 --- CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cc92e72c..b30dc4d9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,8 +53,6 @@ find_package(Protobuf REQUIRED) find_package(libusb-1.0 REQUIRED) find_package(Threads REQUIRED) -#SET(QT_DONT_USE_QTGUI TRUE) -#SET(QT_USE_QTCONSOLE TRUE) include(${QT_USE_FILE}) add_definitions(${QT_DEFINITIONS}) link_directories(${CMAKE_FIND_ROOT_PATH}/lib/arm-linux-gnueabihf) From 996beae750ecc03f742412c561c29cb4118b6082 Mon Sep 17 00:00:00 2001 From: "T. van der Zwan" Date: Fri, 22 Nov 2013 13:51:39 +0000 Subject: [PATCH 13/26] Added multi transforms for testing purpose Former-commit-id: d821e759e3f3f583132a5f21c397b2652337cd28 --- config/hyperion.config.json | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/config/hyperion.config.json b/config/hyperion.config.json index fa6a7535..1c5963ce 100644 --- a/config/hyperion.config.json +++ b/config/hyperion.config.json @@ -34,10 +34,11 @@ /// - 'updateFrequency' The update frequency of the leds in Hz "color" : { - "transforms" : + "transform" : [ { - "id" : "default", + "id" : "device_1", + "leds" : "0-24", "hsv" : { "saturationGain" : 1.0000, @@ -63,12 +64,42 @@ "gamma" : 2.0000, "blacklevel" : 0.0000, "whitelevel" : 1.0000 + } + }, + { + "id" : "device_2", + "leds" : "25-49", + "hsv" : + { + "saturationGain" : 1.0000, + "valueGain" : 1.5000 }, + "red" : + { + "threshold" : 0.1000, + "gamma" : 2.0000, + "blacklevel" : 0.0000, + "whitelevel" : 0.8000 + }, + "green" : + { + "threshold" : 0.1000, + "gamma" : 2.0000, + "blacklevel" : 0.0000, + "whitelevel" : 1.0000 + }, + "blue" : + { + "threshold" : 0.1000, + "gamma" : 2.0000, + "blacklevel" : 0.0000, + "whitelevel" : 1.0000 + } } ], "smoothing" : { - "type" : "none", + "type" : "linear", "time_ms" : 200, "updateFrequency" : 20.0000 } From ba54f3d8ce218d29fd8dac462f2d77e24cc9cac6 Mon Sep 17 00:00:00 2001 From: "T. van der Zwan" Date: Fri, 22 Nov 2013 13:51:54 +0000 Subject: [PATCH 14/26] Removed unused lines Former-commit-id: 8e4a62de33bd0622614884c771d8e74177016121 --- libsrc/hyperion/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/libsrc/hyperion/CMakeLists.txt b/libsrc/hyperion/CMakeLists.txt index c984aa62..03d9df24 100644 --- a/libsrc/hyperion/CMakeLists.txt +++ b/libsrc/hyperion/CMakeLists.txt @@ -77,7 +77,6 @@ add_library(hyperion ${Hyperion_SOURCES} ${Hyperion_RESOURCES_RCC} ) -message("{QT_LIBRARIES} = ${QT_LIBRARIES}") target_link_libraries(hyperion hyperion-utils From 336647b95b0296fb69445aaca14794f0752e3ca5 Mon Sep 17 00:00:00 2001 From: "T. van der Zwan" Date: Mon, 25 Nov 2013 12:16:16 +0100 Subject: [PATCH 15/26] Added multi-color specifications to the model of HyperCon (not GUI yet) Former-commit-id: 3b3f38f1514bd2925e043333555461b3af1f7d88 --- .../hyperion/hypercon/ConfigurationFile.java | 107 +++++++++++++++--- .../org/hyperion/hypercon/gui/ColorPanel.java | 5 +- .../hyperion/hypercon/spec/ColorConfig.java | 98 +++------------- .../hypercon/spec/TransformConfig.java | 101 +++++++++++++++++ 4 files changed, 210 insertions(+), 101 deletions(-) create mode 100644 src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/TransformConfig.java diff --git a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/ConfigurationFile.java b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/ConfigurationFile.java index 285978f8..1abd46c1 100644 --- a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/ConfigurationFile.java +++ b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/ConfigurationFile.java @@ -8,19 +8,19 @@ import java.io.OutputStream; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; import java.util.Properties; -import java.util.zip.GZIPInputStream; -import java.util.zip.GZIPOutputStream; +import java.util.Vector; public class ConfigurationFile { - private final Properties pProps = new Properties(); + private final Properties mProps = new Properties(); public void load(String pFilename) { - pProps.clear(); + mProps.clear(); // try (InputStream in = new InflaterInputStream(new FileInputStream(pFilename))){ - try (InputStream in = new GZIPInputStream(new FileInputStream(pFilename))){ -// try (InputStream in = new FileInputStream(pFilename)) { - pProps.load(in); +// try (InputStream in = new GZIPInputStream(new FileInputStream(pFilename))){ + try (InputStream in = new FileInputStream(pFilename)) { + mProps.load(in); } catch (Throwable t) { // TODO Auto-generated catch block t.printStackTrace(); @@ -29,15 +29,18 @@ public class ConfigurationFile { public void save(String pFilename) { // try (OutputStream out = new DeflaterOutputStream(new FileOutputStream(pFilename))) { - try (OutputStream out = new GZIPOutputStream(new FileOutputStream(pFilename))) { -// try (OutputStream out = (new FileOutputStream(pFilename))) { - pProps.store(out, "Pesistent settings file for HyperCon"); +// try (OutputStream out = new GZIPOutputStream(new FileOutputStream(pFilename))) { + try (OutputStream out = (new FileOutputStream(pFilename))) { + mProps.store(out, "Pesistent settings file for HyperCon"); } catch (IOException e) { e.printStackTrace(); } } public void store(Object pObj) { + store(pObj, pObj.getClass().getSimpleName(), ""); + } + public void store(Object pObj, String preamble, String postamble) { String className = pObj.getClass().getSimpleName(); // Retrieve the member variables Field[] fields = pObj.getClass().getDeclaredFields(); @@ -48,27 +51,99 @@ public class ConfigurationFile { continue; } - String key = className + "." + field.getName(); + String key = preamble + "." + field.getName() + postamble; try { Object value = field.get(pObj); if (value.getClass().isEnum()) { - pProps.setProperty(key, ((Enum)value).name()); + mProps.setProperty(key, ((Enum)value).name()); + } else if (value.getClass().isAssignableFrom(Vector.class)) { + @SuppressWarnings("unchecked") + Vector v = (Vector) value; + for (int i=0; i vector; + try { + vector = (Vector)field.get(pObj); + } catch (Throwable t) { + t.printStackTrace(); + break; + } + // Clear existing elements from the vector + vector.clear(); + + // Iterate through the properties to find the indices of the vector + int i=0; + while (true) { + String curIndexKey = pPreamble + field.getName() + "[" + i + "]"; + Properties elemProps = new Properties(); + // Find all the elements for the current vector index + for (Object keyObj : pProps.keySet()) { + String keyStr = (String)keyObj; + if (keyStr.startsWith(curIndexKey)) { + // Remove the name and dot + elemProps.put(keyStr.substring(curIndexKey.length()+1), pProps.get(keyStr)); + } + } + if (elemProps.isEmpty()) { + // Found no more elements for the vector + break; + } + + // Construct new instance of vectors generic type + ParameterizedType vectorElementType = (ParameterizedType) field.getGenericType(); + Class vectorElementClass = (Class) vectorElementType.getActualTypeArguments()[0]; + // Find the constructor with no arguments and create a new instance + Object newElement = null; + try { + newElement = vectorElementClass.getConstructor().newInstance(); + } catch (Throwable t) { + System.err.println("Failed to find empty default constructor for " + vectorElementClass.getName()); + break; + } + if (newElement == null) { + System.err.println("Failed to construct instance for " + vectorElementClass.getName()); + break; + } + + // Restore the instance members from the collected properties + restore(newElement, elemProps, ""); + + // Add the instance to the vector + vector.addElement(newElement); + + ++i; + } + + continue; + } + + String key = pPreamble + field.getName(); String value = pProps.getProperty(key); if (value == null) { System.out.println("Persistent settings does not contain value for " + key); @@ -97,6 +172,6 @@ public class ConfigurationFile { @Override public String toString() { - return pProps.toString(); + return mProps.toString(); } } diff --git a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/ColorPanel.java b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/ColorPanel.java index 531aad0f..52c16b16 100644 --- a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/ColorPanel.java +++ b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/ColorPanel.java @@ -16,6 +16,7 @@ import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.hyperion.hypercon.spec.ColorConfig; +import org.hyperion.hypercon.spec.TransformConfig; /** * Configuration panel for the ColorConfig. @@ -24,7 +25,7 @@ import org.hyperion.hypercon.spec.ColorConfig; */ public class ColorPanel extends JPanel { - private final ColorConfig mColorConfig; + private final TransformConfig mColorConfig; private JPanel mRgbTransformPanel; private JLabel mThresholdLabel; @@ -56,7 +57,7 @@ public class ColorPanel extends JPanel { public ColorPanel(ColorConfig pColorConfig) { super(); - mColorConfig = pColorConfig; + mColorConfig = pColorConfig.mTransforms.get(0); initialise(); } diff --git a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/ColorConfig.java b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/ColorConfig.java index ea4c2279..c9baf768 100644 --- a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/ColorConfig.java +++ b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/ColorConfig.java @@ -1,42 +1,19 @@ package org.hyperion.hypercon.spec; +import java.util.List; import java.util.Locale; +import java.util.Vector; /** * The color tuning parameters of the different color channels (both in RGB space as in HSV space) */ public class ColorConfig { - /** The saturation gain (in HSV space) */ - public double mSaturationGain = 1.0; - /** The value gain (in HSV space) */ - public double mValueGain = 1.5; - /** The minimum required RED-value (in RGB space) */ - public double mRedThreshold = 0.1; - /** The gamma-curve correct for the RED-value (in RGB space) */ - public double mRedGamma = 2.0; - /** The black-level of the RED-value (in RGB space) */ - public double mRedBlacklevel = 0.0; - /** The white-level of the RED-value (in RGB space) */ - public double mRedWhitelevel = 0.8; - - /** The minimum required GREEN-value (in RGB space) */ - public double mGreenThreshold = 0.1; - /** The gamma-curve correct for the GREEN-value (in RGB space) */ - public double mGreenGamma = 2.0; - /** The black-level of the GREEN-value (in RGB space) */ - public double mGreenBlacklevel = 0.0; - /** The white-level of the GREEN-value (in RGB space) */ - public double mGreenWhitelevel = 1.0; - - /** The minimum required BLUE-value (in RGB space) */ - public double mBlueThreshold = 0.1; - /** The gamma-curve correct for the BLUE-value (in RGB space) */ - public double mBlueGamma = 2.0; - /** The black-level of the BLUE-value (in RGB space) */ - public double mBlueBlacklevel = 0.0; - /** The white-level of the BLUE-value (in RGB space) */ - public double mBlueWhitelevel = 1.0; + /** List with color transformations */ + public List mTransforms = new Vector<>(); + { + mTransforms.add(new TransformConfig()); + } public boolean mSmoothingEnabled = false; /** The type of smoothing algorithm */ @@ -70,65 +47,20 @@ public class ColorConfig { strBuf.append("\t\"color\" :\n"); strBuf.append("\t{\n"); - strBuf.append(hsvToJsonString() + ",\n"); - strBuf.append(rgbToJsonString() + ",\n"); + + strBuf.append("\t\t\"transform\" :\n"); + strBuf.append("\t\t[\n"); + for (TransformConfig transform : mTransforms) { + strBuf.append(transform.toJsonString()); + } + strBuf.append("\t\t]\n"); + strBuf.append(smoothingToString() + "\n"); strBuf.append("\t}"); return strBuf.toString(); } - /** - * Creates the JSON string of the HSV-subconfiguration as used in the Hyperion deamon configfile - * - * @return The JSON string of the HSV-config - */ - private String hsvToJsonString() { - StringBuffer strBuf = new StringBuffer(); - strBuf.append("\t\t\"hsv\" :\n"); - strBuf.append("\t\t{\n"); - strBuf.append(String.format(Locale.ROOT, "\t\t\t\"saturationGain\" : %.4f,\n", mSaturationGain)); - strBuf.append(String.format(Locale.ROOT, "\t\t\t\"valueGain\" : %.4f\n", mValueGain)); - - strBuf.append("\t\t}"); - return strBuf.toString(); - } - - /** - * Creates the JSON string of the RGB-subconfiguration as used in the Hyperion deamon configfile - * - * @return The JSON string of the RGB-config - */ - private String rgbToJsonString() { - StringBuffer strBuf = new StringBuffer(); - - strBuf.append("\t\t\"red\" :\n"); - strBuf.append("\t\t{\n"); - strBuf.append(String.format(Locale.ROOT, "\t\t\t\"threshold\" : %.4f,\n", mRedThreshold)); - strBuf.append(String.format(Locale.ROOT, "\t\t\t\"gamma\" : %.4f,\n", mRedGamma)); - strBuf.append(String.format(Locale.ROOT, "\t\t\t\"blacklevel\" : %.4f,\n", mRedBlacklevel)); - strBuf.append(String.format(Locale.ROOT, "\t\t\t\"whitelevel\" : %.4f\n", mRedWhitelevel)); - strBuf.append("\t\t},\n"); - - strBuf.append("\t\t\"green\" :\n"); - strBuf.append("\t\t{\n"); - strBuf.append(String.format(Locale.ROOT, "\t\t\t\"threshold\" : %.4f,\n", mGreenThreshold)); - strBuf.append(String.format(Locale.ROOT, "\t\t\t\"gamma\" : %.4f,\n", mGreenGamma)); - strBuf.append(String.format(Locale.ROOT, "\t\t\t\"blacklevel\" : %.4f,\n", mGreenBlacklevel)); - strBuf.append(String.format(Locale.ROOT, "\t\t\t\"whitelevel\" : %.4f\n", mGreenWhitelevel)); - strBuf.append("\t\t},\n"); - - strBuf.append("\t\t\"blue\" :\n"); - strBuf.append("\t\t{\n"); - strBuf.append(String.format(Locale.ROOT, "\t\t\t\"threshold\" : %.4f,\n", mBlueThreshold)); - strBuf.append(String.format(Locale.ROOT, "\t\t\t\"gamma\" : %.4f,\n", mBlueGamma)); - strBuf.append(String.format(Locale.ROOT, "\t\t\t\"blacklevel\" : %.4f,\n", mBlueBlacklevel)); - strBuf.append(String.format(Locale.ROOT, "\t\t\t\"whitelevel\" : %.4f\n", mBlueWhitelevel)); - strBuf.append("\t\t}"); - - return strBuf.toString(); - } - /** * Creates the JSON string of the smoothing subconfiguration as used in the Hyperion deamon configfile * diff --git a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/TransformConfig.java b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/TransformConfig.java new file mode 100644 index 00000000..e3ddf221 --- /dev/null +++ b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/TransformConfig.java @@ -0,0 +1,101 @@ +package org.hyperion.hypercon.spec; + +import java.util.Locale; + +public class TransformConfig { + /** The identifier of this ColorTransform configuration */ + public String mId = ""; + + /** The saturation gain (in HSV space) */ + public double mSaturationGain = 1.0; + /** The value gain (in HSV space) */ + public double mValueGain = 1.5; + + /** The minimum required RED-value (in RGB space) */ + public double mRedThreshold = 0.1; + /** The gamma-curve correct for the RED-value (in RGB space) */ + public double mRedGamma = 2.0; + /** The black-level of the RED-value (in RGB space) */ + public double mRedBlacklevel = 0.0; + /** The white-level of the RED-value (in RGB space) */ + public double mRedWhitelevel = 0.8; + + /** The minimum required GREEN-value (in RGB space) */ + public double mGreenThreshold = 0.1; + /** The gamma-curve correct for the GREEN-value (in RGB space) */ + public double mGreenGamma = 2.0; + /** The black-level of the GREEN-value (in RGB space) */ + public double mGreenBlacklevel = 0.0; + /** The white-level of the GREEN-value (in RGB space) */ + public double mGreenWhitelevel = 1.0; + + /** The minimum required BLUE-value (in RGB space) */ + public double mBlueThreshold = 0.1; + /** The gamma-curve correct for the BLUE-value (in RGB space) */ + public double mBlueGamma = 2.0; + /** The black-level of the BLUE-value (in RGB space) */ + public double mBlueBlacklevel = 0.0; + /** The white-level of the BLUE-value (in RGB space) */ + public double mBlueWhitelevel = 1.0; + + public String toJsonString() { + StringBuffer strBuf = new StringBuffer(); + + strBuf.append("\t\t{\n"); + strBuf.append(hsvToJsonString() + ",\n"); + strBuf.append(rgbToJsonString() + ",\n"); + strBuf.append("\t\t}"); + + return strBuf.toString(); + } + /** + * Creates the JSON string of the HSV-subconfiguration as used in the Hyperion deamon configfile + * + * @return The JSON string of the HSV-config + */ + private String hsvToJsonString() { + StringBuffer strBuf = new StringBuffer(); + strBuf.append("\t\t\t\"hsv\" :\n"); + strBuf.append("\t\t\t{\n"); + strBuf.append(String.format(Locale.ROOT, "\t\t\t\t\"saturationGain\" : %.4f,\n", mSaturationGain)); + strBuf.append(String.format(Locale.ROOT, "\t\t\t\t\"valueGain\" : %.4f\n", mValueGain)); + + strBuf.append("\t\t\t}"); + return strBuf.toString(); + } + + /** + * Creates the JSON string of the RGB-subconfiguration as used in the Hyperion deamon configfile + * + * @return The JSON string of the RGB-config + */ + private String rgbToJsonString() { + StringBuffer strBuf = new StringBuffer(); + + strBuf.append("\t\t\t\"red\" :\n"); + strBuf.append("\t\t\t{\n"); + strBuf.append(String.format(Locale.ROOT, "\t\t\t\t\"threshold\" : %.4f,\n", mRedThreshold)); + strBuf.append(String.format(Locale.ROOT, "\t\t\t\t\"gamma\" : %.4f,\n", mRedGamma)); + strBuf.append(String.format(Locale.ROOT, "\t\t\t\t\"blacklevel\" : %.4f,\n", mRedBlacklevel)); + strBuf.append(String.format(Locale.ROOT, "\t\t\t\t\"whitelevel\" : %.4f\n", mRedWhitelevel)); + strBuf.append("\t\t},\n"); + + strBuf.append("\t\t\t\"green\" :\n"); + strBuf.append("\t\t\t{\n"); + strBuf.append(String.format(Locale.ROOT, "\t\t\t\t\"threshold\" : %.4f,\n", mGreenThreshold)); + strBuf.append(String.format(Locale.ROOT, "\t\t\t\t\"gamma\" : %.4f,\n", mGreenGamma)); + strBuf.append(String.format(Locale.ROOT, "\t\t\t\t\"blacklevel\" : %.4f,\n", mGreenBlacklevel)); + strBuf.append(String.format(Locale.ROOT, "\t\t\t\t\"whitelevel\" : %.4f\n", mGreenWhitelevel)); + strBuf.append("\t\t\t},\n"); + + strBuf.append("\t\t\t\"blue\" :\n"); + strBuf.append("\t\t\t{\n"); + strBuf.append(String.format(Locale.ROOT, "\t\t\t\t\"threshold\" : %.4f,\n", mBlueThreshold)); + strBuf.append(String.format(Locale.ROOT, "\t\t\t\t\"gamma\" : %.4f,\n", mBlueGamma)); + strBuf.append(String.format(Locale.ROOT, "\t\t\t\t\"blacklevel\" : %.4f,\n", mBlueBlacklevel)); + strBuf.append(String.format(Locale.ROOT, "\t\t\t\t\"whitelevel\" : %.4f\n", mBlueWhitelevel)); + strBuf.append("\t\t\t}"); + + return strBuf.toString(); + } +} From 602afa14f6822565a590f220c0e4b42e4e0432b9 Mon Sep 17 00:00:00 2001 From: "T. van der Zwan" Date: Mon, 25 Nov 2013 12:26:20 +0100 Subject: [PATCH 16/26] Fixed json generation of multi-colortransform Former-commit-id: ecd587d1e623e34b8841d634b2217ef350e25210 --- .../hyperion/hypercon/spec/ColorConfig.java | 10 +++- .../hypercon/spec/TransformConfig.java | 58 +++++++++---------- 2 files changed, 37 insertions(+), 31 deletions(-) diff --git a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/ColorConfig.java b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/ColorConfig.java index c9baf768..b8bab28c 100644 --- a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/ColorConfig.java +++ b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/ColorConfig.java @@ -50,10 +50,16 @@ public class ColorConfig { strBuf.append("\t\t\"transform\" :\n"); strBuf.append("\t\t[\n"); - for (TransformConfig transform : mTransforms) { + for (int i=0; i Date: Tue, 26 Nov 2013 12:24:48 +0100 Subject: [PATCH 17/26] Added the multi color transform to the gui. Former-commit-id: 96ff1aa60098871a8597f716c0788cc749e149ef --- ...lorPanel.java => ColorTransformPanel.java} | 51 ++++++- .../hyperion/hypercon/gui/ColorsPanel.java | 130 ++++++++++++++++++ .../hyperion/hypercon/gui/ConfigPanel.java | 2 +- .../hyperion/hypercon/spec/ColorConfig.java | 24 +++- .../hypercon/spec/TransformConfig.java | 12 +- 5 files changed, 205 insertions(+), 14 deletions(-) rename src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/{ColorPanel.java => ColorTransformPanel.java} (83%) create mode 100644 src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/ColorsPanel.java diff --git a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/ColorPanel.java b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/ColorTransformPanel.java similarity index 83% rename from src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/ColorPanel.java rename to src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/ColorTransformPanel.java index 52c16b16..e7dd3278 100644 --- a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/ColorPanel.java +++ b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/ColorTransformPanel.java @@ -1,5 +1,6 @@ package org.hyperion.hypercon.gui; +import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.GridLayout; import java.beans.Transient; @@ -11,11 +12,13 @@ import javax.swing.GroupLayout; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JSpinner; +import javax.swing.JTextField; import javax.swing.SpinnerNumberModel; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; -import org.hyperion.hypercon.spec.ColorConfig; import org.hyperion.hypercon.spec.TransformConfig; /** @@ -23,10 +26,14 @@ import org.hyperion.hypercon.spec.TransformConfig; * * NB This has not been integrated in the GUI jet! */ -public class ColorPanel extends JPanel { +public class ColorTransformPanel extends JPanel { private final TransformConfig mColorConfig; + private JPanel mIndexPanel; + private JLabel mIndexLabel; + private JTextField mIndexField; + private JPanel mRgbTransformPanel; private JLabel mThresholdLabel; private JLabel mGammaLabel; @@ -54,10 +61,10 @@ public class ColorPanel extends JPanel { private JLabel mValueAdjustLabel; private JSpinner mValueAdjustSpinner; - public ColorPanel(ColorConfig pColorConfig) { + public ColorTransformPanel(TransformConfig pTransformConfig) { super(); - mColorConfig = pColorConfig.mTransforms.get(0); + mColorConfig = pTransformConfig; initialise(); } @@ -71,12 +78,32 @@ public class ColorPanel extends JPanel { } private void initialise() { - setBorder(BorderFactory.createTitledBorder("Color transform")); + setBorder(BorderFactory.createTitledBorder("Transform [" + mColorConfig.mId + "]")); setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + add(getIndexPanel()); + add(Box.createVerticalStrut(10)); add(getRgbPanel()); add(Box.createVerticalStrut(10)); add(getHsvPanel()); + add(Box.createVerticalGlue()); + } + + private JPanel getIndexPanel() { + if (mIndexPanel == null) { + mIndexPanel = new JPanel(); + mIndexPanel.setMaximumSize(new Dimension(1024, 25)); + mIndexPanel.setLayout(new BorderLayout(10,10)); + + mIndexLabel = new JLabel("Indices:"); + mIndexPanel.add(mIndexLabel, BorderLayout.WEST); + + mIndexField = new JTextField(mColorConfig.mLedIndexString); + mIndexField.setToolTipText("Comma seperated indices or index ranges (eg '1-10, 13, 14, 17-19')"); + mIndexField.getDocument().addDocumentListener(mDocumentListener); + mIndexPanel.add(mIndexField, BorderLayout.CENTER); + } + return mIndexPanel; } private JPanel getRgbPanel() { @@ -217,4 +244,18 @@ public class ColorPanel extends JPanel { mColorConfig.mValueGain = (Double)mValueAdjustSpinner.getValue(); } }; + private final DocumentListener mDocumentListener = new DocumentListener() { + @Override + public void removeUpdate(DocumentEvent e) { + mColorConfig.mLedIndexString = mIndexField.getText(); + } + @Override + public void insertUpdate(DocumentEvent e) { + mColorConfig.mLedIndexString = mIndexField.getText(); + } + @Override + public void changedUpdate(DocumentEvent e) { + mColorConfig.mLedIndexString = mIndexField.getText(); + } + }; } diff --git a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/ColorsPanel.java b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/ColorsPanel.java new file mode 100644 index 00000000..29f50840 --- /dev/null +++ b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/ColorsPanel.java @@ -0,0 +1,130 @@ +package org.hyperion.hypercon.gui; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.HashMap; +import java.util.Map; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.DefaultComboBoxModel; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JOptionPane; +import javax.swing.JPanel; + +import org.hyperion.hypercon.spec.ColorConfig; +import org.hyperion.hypercon.spec.TransformConfig; + +public class ColorsPanel extends JPanel { + + private final ColorConfig mColorConfig; + private final DefaultComboBoxModel mTransformsModel; + + private JPanel mControlPanel; + private JComboBox mTransformCombo; + private JButton mAddTransformButton; + private JButton mDelTransformButton; + + private JPanel mTransformPanel; + + private final Map mTransformPanels = new HashMap<>(); + + + public ColorsPanel(ColorConfig pColorConfig) { + super(); + + mColorConfig = pColorConfig; + mTransformsModel = new DefaultComboBoxModel(mColorConfig.mTransforms); + + initialise(); + } + + private void initialise() { + setLayout(new BorderLayout(10,10)); + setBorder(BorderFactory.createTitledBorder("Colors")); + + add(getControlPanel(), BorderLayout.NORTH); + + mTransformPanel = new JPanel(); + mTransformPanel.setLayout(new BorderLayout()); + add(mTransformPanel, BorderLayout.CENTER); + + for (TransformConfig config : mColorConfig.mTransforms) { + mTransformPanels.put(config, new ColorTransformPanel(config)); + } + ColorTransformPanel currentPanel = mTransformPanels.get(mColorConfig.mTransforms.get(0)); + mTransformPanel.add(currentPanel, BorderLayout.CENTER); + } + + private JPanel getControlPanel() { + if (mControlPanel == null) { + mControlPanel = new JPanel(); + mControlPanel.setLayout(new BoxLayout(mControlPanel, BoxLayout.LINE_AXIS)); + + mTransformCombo = new JComboBox<>(mTransformsModel); + mTransformCombo.addActionListener(mComboListener); + mControlPanel.add(mTransformCombo); + + mAddTransformButton = new JButton(mAddAction); + mControlPanel.add(mAddTransformButton); + + mDelTransformButton = new JButton(mDelAction); + mDelTransformButton.setEnabled(mTransformCombo.getItemCount() > 1); + mControlPanel.add(mDelTransformButton); + } + return mControlPanel; + } + + private final Action mAddAction = new AbstractAction("Add") { + @Override + public void actionPerformed(ActionEvent e) { + String newId = JOptionPane.showInputDialog("Give an identifier for the new color-transform:"); + if (newId == null || newId.isEmpty()) { + // No proper value given + return; + } + + TransformConfig config = new TransformConfig(); + config.mId = newId; + + ColorTransformPanel panel = new ColorTransformPanel(config); + mTransformPanels.put(config, panel); + + mTransformsModel.addElement(config); + mTransformsModel.setSelectedItem(config); + + mDelTransformButton.setEnabled(true); + } + }; + private final Action mDelAction = new AbstractAction("Del") { + @Override + public void actionPerformed(ActionEvent e) { + TransformConfig config = (TransformConfig) mTransformCombo.getSelectedItem(); + mTransformPanels.remove(config); + mTransformsModel.removeElement(config); + + mDelTransformButton.setEnabled(mTransformCombo.getItemCount() > 1); + } + }; + + private final ActionListener mComboListener = new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + TransformConfig selConfig = (TransformConfig) mTransformsModel.getSelectedItem(); + if (selConfig == null) { + // Something went wrong here, there should always be a selection! + return; + } + + ColorTransformPanel panel = mTransformPanels.get(selConfig); + mTransformPanel.removeAll(); + mTransformPanel.add(panel, BorderLayout.CENTER); + mTransformPanel.revalidate(); + mTransformPanel.repaint(); + } + }; +} diff --git a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/ConfigPanel.java b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/ConfigPanel.java index 6c56bc44..84e1636d 100644 --- a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/ConfigPanel.java +++ b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/ConfigPanel.java @@ -174,7 +174,7 @@ public class ConfigPanel extends JPanel { mProcessPanel.add(new BootSequencePanel(ledString.mMiscConfig)); mProcessPanel.add(new FrameGrabberPanel(ledString.mMiscConfig)); mProcessPanel.add(new ColorSmoothingPanel(ledString.mColorConfig)); - mProcessPanel.add(new ColorPanel(ledString.mColorConfig)); + mProcessPanel.add(new ColorsPanel(ledString.mColorConfig)); mProcessPanel.add(Box.createVerticalGlue()); } return mProcessPanel; diff --git a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/ColorConfig.java b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/ColorConfig.java index b8bab28c..824c3f1d 100644 --- a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/ColorConfig.java +++ b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/ColorConfig.java @@ -1,6 +1,5 @@ package org.hyperion.hypercon.spec; -import java.util.List; import java.util.Locale; import java.util.Vector; @@ -10,7 +9,7 @@ import java.util.Vector; public class ColorConfig { /** List with color transformations */ - public List mTransforms = new Vector<>(); + public Vector mTransforms = new Vector<>(); { mTransforms.add(new TransformConfig()); } @@ -31,16 +30,27 @@ public class ColorConfig { public String toJsonString() { StringBuffer strBuf = new StringBuffer(); - strBuf.append("\t/// Color manipulation configuration used to tune the output colors to specific surroundings. Contains the following fields:\n"); - strBuf.append("\t/// * 'hsv' : The manipulation in the Hue-Saturation-Value color domain with the following tuning parameters:\n"); + strBuf.append("\t/// Color manipulation configuration used to tune the output colors to specific surroundings. \n"); + strBuf.append("\t/// The configuration contains a list of color-transforms. Each transform contains the \n"); + strBuf.append("\t/// following fields:\n"); + strBuf.append("\t/// * 'id' : The unique identifier of the color transformation (eg 'device_1')"); + strBuf.append("\t/// * 'leds' : The indices (or index ranges) of the leds to which this color transform applies\n"); + strBuf.append("\t/// (eg '0-5, 9, 11, 12-17'). The indices are zero based."); + strBuf.append("\t/// * 'hsv' : The manipulation in the Hue-Saturation-Value color domain with the following \n"); + strBuf.append("\t/// tuning parameters:\n"); strBuf.append("\t/// - 'saturationGain' The gain adjustement of the saturation\n"); strBuf.append("\t/// - 'valueGain' The gain adjustement of the value\n"); - strBuf.append("\t/// * 'red'/'green'/'blue' : The manipulation in the Red-Green-Blue color domain with the following tuning parameters for each channel:\n"); - strBuf.append("\t/// - 'threshold' The minimum required input value for the channel to be on (else zero)\n"); + strBuf.append("\t/// * 'red'/'green'/'blue' : The manipulation in the Red-Green-Blue color domain with the \n"); + strBuf.append("\t/// following tuning parameters for each channel:\n"); + strBuf.append("\t/// - 'threshold' The minimum required input value for the channel to be on \n"); + strBuf.append("\t/// (else zero)\n"); strBuf.append("\t/// - 'gamma' The gamma-curve correction factor\n"); strBuf.append("\t/// - 'blacklevel' The lowest possible value (when the channel is black)\n"); strBuf.append("\t/// - 'whitelevel' The highest possible value (when the channel is white)\n"); - strBuf.append("\t/// * 'smoothing' : Smoothing of the colors in the time-domain with the following tuning parameters:\n"); + strBuf.append("\t///"); + strBuf.append("\t/// Next to the list with color transforms there is also a smoothing option."); + strBuf.append("\t/// * 'smoothing' : Smoothing of the colors in the time-domain with the following tuning \n"); + strBuf.append("\t/// parameters:\n"); strBuf.append("\t/// - 'type' The type of smoothing algorithm ('linear' or 'none')\n"); strBuf.append("\t/// - 'time_ms' The time constant for smoothing algorithm in milliseconds\n"); strBuf.append("\t/// - 'updateFrequency' The update frequency of the leds in Hz\n"); diff --git a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/TransformConfig.java b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/TransformConfig.java index 5877f10f..a5893210 100644 --- a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/TransformConfig.java +++ b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/TransformConfig.java @@ -4,7 +4,10 @@ import java.util.Locale; public class TransformConfig { /** The identifier of this ColorTransform configuration */ - public String mId = ""; + public String mId = "default"; + + /** The indices to which this transform applies */ + public String mLedIndexString = "0-49"; /** The saturation gain (in HSV space) */ public double mSaturationGain = 1.0; @@ -42,6 +45,8 @@ public class TransformConfig { StringBuffer strBuf = new StringBuffer(); strBuf.append("\t\t\t{\n"); + strBuf.append("\t\t\t\t\"id\" : \"" + mId + "\",\n"); + strBuf.append("\t\t\t\t\"leds\" : \"" + mLedIndexString + "\",\n"); strBuf.append(hsvToJsonString() + ",\n"); strBuf.append(rgbToJsonString() + "\n"); strBuf.append("\t\t\t}"); @@ -98,4 +103,9 @@ public class TransformConfig { return strBuf.toString(); } + + @Override + public String toString() { + return mId; + } } From dc4a41c64a5b9fe6ebd104c2db85514f3e97c867 Mon Sep 17 00:00:00 2001 From: "T. van der Zwan" Date: Wed, 27 Nov 2013 12:00:05 +0100 Subject: [PATCH 18/26] Added test dialog for dividing leds over transforms Former-commit-id: 0d4549d32c40df3fcbd0c308b249566721445b13 --- .../hypercon/gui/LedDivideDialog.java | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/LedDivideDialog.java diff --git a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/LedDivideDialog.java b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/LedDivideDialog.java new file mode 100644 index 00000000..d0072df1 --- /dev/null +++ b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/LedDivideDialog.java @@ -0,0 +1,48 @@ +package org.hyperion.hypercon.gui; + +import java.awt.GridLayout; + +import javax.swing.ButtonGroup; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JRadioButton; + +public class LedDivideDialog extends JFrame { + + private final int mLedCount; + private final int mTransformCount; + + private JPanel mContentPanel; + + public LedDivideDialog(int pLedCnt, int pTransformCnt) { + super(); + + mLedCount = pLedCnt; + mTransformCount = pTransformCnt; + + initialise(); + } + + private void initialise() { + mContentPanel = new JPanel(); + mContentPanel.setLayout(new GridLayout(mLedCount, mTransformCount, 5, 5)); + + for (int iLed=0; iLed Date: Sat, 30 Nov 2013 16:53:14 +0100 Subject: [PATCH 19/26] Switch of the preference for static libraries Former-commit-id: 6b9cf3a9c437fef211a8d8772cf70a9f5788402b --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 16f73167..7e8ad3ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,7 +33,7 @@ include_directories(${CMAKE_SOURCE_DIR}/dependencies/include) include_directories(${CMAKE_SOURCE_DIR}/include) # Prefer static linking over dynamic -set(CMAKE_FIND_LIBRARY_SUFFIXES ".a;.so") +#set(CMAKE_FIND_LIBRARY_SUFFIXES ".a;.so") set(CMAKE_BUILD_TYPE "Release") From 9602017263e77ed1e22f377a20aea2d1ab98baac Mon Sep 17 00:00:00 2001 From: "T. van der Zwan" Date: Tue, 3 Dec 2013 21:29:37 +0100 Subject: [PATCH 20/26] Fixed incorrect cast of update frequency Former-commit-id: 0d426d020e8a911be3a54adde1a6e99cc074d8bb --- deploy/HyperCon.jar.REMOVED.git-id | 2 +- .../src/org/hyperion/hypercon/gui/ColorSmoothingPanel.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/HyperCon.jar.REMOVED.git-id b/deploy/HyperCon.jar.REMOVED.git-id index ece2c44a..90278ffd 100644 --- a/deploy/HyperCon.jar.REMOVED.git-id +++ b/deploy/HyperCon.jar.REMOVED.git-id @@ -1 +1 @@ -944c2292ca55b16a31bdf1d89e0e7849d6879995 \ No newline at end of file +a63791e2794fa0c76a2a5da3d0ce7b3e4f4089ff \ No newline at end of file diff --git a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/ColorSmoothingPanel.java b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/ColorSmoothingPanel.java index 0d3bdc62..376c6391 100644 --- a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/ColorSmoothingPanel.java +++ b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/ColorSmoothingPanel.java @@ -135,7 +135,7 @@ public class ColorSmoothingPanel extends JPanel { @Override public void stateChanged(ChangeEvent e) { mColorConfig.mSmoothingTime_ms = (Integer)mTimeSpinner.getValue(); - mColorConfig.mSmoothingUpdateFrequency_Hz = (Integer)mUpdateFrequencySpinner.getValue(); + mColorConfig.mSmoothingUpdateFrequency_Hz = (Double)mUpdateFrequencySpinner.getValue(); } }; } From ea1b8315c4de56d1859b768494e2845fe4a4f25d Mon Sep 17 00:00:00 2001 From: johan Date: Tue, 3 Dec 2013 22:56:46 +0100 Subject: [PATCH 21/26] Added a notify that color transforms have changed Former-commit-id: 28eb82f4e6f646ffcb6ed1b013a9ec60de53e657 --- include/hyperion/Hyperion.h | 3 +++ libsrc/hyperion/Hyperion.cpp | 5 +++++ libsrc/jsonserver/JsonClientConnection.cpp | 3 +++ 3 files changed, 11 insertions(+) diff --git a/include/hyperion/Hyperion.h b/include/hyperion/Hyperion.h index 605ce6a1..2514cdcf 100644 --- a/include/hyperion/Hyperion.h +++ b/include/hyperion/Hyperion.h @@ -101,6 +101,9 @@ public: /// ColorTransform * getTransform(const std::string& id); + /// Tell Hyperion that the transforms have changed and the leds need to be updated + void transformsUpdated(); + /// /// Clears the given priority channel. This will switch the led-colors to the colors of the next /// lower priority channel (or off if no more channels are set) diff --git a/libsrc/hyperion/Hyperion.cpp b/libsrc/hyperion/Hyperion.cpp index 472ae96f..e977f660 100644 --- a/libsrc/hyperion/Hyperion.cpp +++ b/libsrc/hyperion/Hyperion.cpp @@ -418,6 +418,11 @@ ColorTransform * Hyperion::getTransform(const std::string& id) return _raw2ledTransform->getTransform(id); } +void Hyperion::transformsUpdated() +{ + update(); +} + void Hyperion::clear(int priority) { if (_muxer.hasPriority(priority)) diff --git a/libsrc/jsonserver/JsonClientConnection.cpp b/libsrc/jsonserver/JsonClientConnection.cpp index cd5894db..44bcec3c 100644 --- a/libsrc/jsonserver/JsonClientConnection.cpp +++ b/libsrc/jsonserver/JsonClientConnection.cpp @@ -287,6 +287,9 @@ void JsonClientConnection::handleTransformCommand(const Json::Value &message) colorTransform->_rgbBlueTransform .setWhitelevel(values[2u].asDouble()); } + // commit the changes + _hyperion->transformsUpdated(); + sendSuccessReply(); } From 787dd2152ade77b7393d4d3cf0f2d430466b25c7 Mon Sep 17 00:00:00 2001 From: "T. van der Zwan" Date: Wed, 4 Dec 2013 14:58:00 +0000 Subject: [PATCH 22/26] Added dispmanx2png to deploy Former-commit-id: f05d868d8ec12564cc374ff0547d7cf13f20a744 --- deploy/dispmanx2png | Bin 0 -> 73582 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100755 deploy/dispmanx2png diff --git a/deploy/dispmanx2png b/deploy/dispmanx2png new file mode 100755 index 0000000000000000000000000000000000000000..b81b1774ff4f1c890029c51a69117615e4287375 GIT binary patch literal 73582 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

Y&97VZ2L5-V=NpCs@ESi;$J5sH8E?SS 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 Date: Wed, 4 Dec 2013 16:48:02 +0000 Subject: [PATCH 23/26] Added grab count and flags to filename Former-commit-id: 7abd1934b380cafcb93f861d715bc3981c1132de --- test/dispmanx2png/dispmanx2png.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/test/dispmanx2png/dispmanx2png.cpp b/test/dispmanx2png/dispmanx2png.cpp index 6464a1f9..f4237e25 100644 --- a/test/dispmanx2png/dispmanx2png.cpp +++ b/test/dispmanx2png/dispmanx2png.cpp @@ -27,6 +27,7 @@ int main(int argc, char** argv) signal(SIGINT, signal_handler); int grabFlags = 0; + int grabCount = -1; try { // create the option parser and initialize all parameters @@ -35,6 +36,8 @@ int main(int argc, char** argv) QString flagDescr = QString("Set the grab flags of the dispmanx frame grabber [default: 0x%1]").arg(grabFlags, 8, 16, QChar('0')); StringParameter & argFlags = parameters.add ('f', "flags", flagDescr.toAscii().constData()); + IntParameter & argCount = parameters.add ('n', "count", "Number of images to capture (default infinite)"); + argCount.setDefault(grabCount); SwitchParameter<> & argList = parameters.add >('l', "list", "List the possible flags"); SwitchParameter<> & argHelp = parameters.add >('h', "help", "Show this help message and exit"); @@ -90,6 +93,8 @@ int main(int argc, char** argv) return -1; } } + + grabCount = argCount.getValue(); } catch (const std::runtime_error & e) { @@ -106,7 +111,7 @@ int main(int argc, char** argv) QImage qImage(64, 64, QImage::Format_ARGB32); Image imageRgba(64, 64); - while(running) + for (int i=0; i Date: Wed, 4 Dec 2013 16:55:15 +0000 Subject: [PATCH 24/26] removed old grabber Former-commit-id: 49c5ab91d6c33535532bdd199d9646aa8e8f114a --- deploy/dispmanx2png | Bin 73582 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100755 deploy/dispmanx2png diff --git a/deploy/dispmanx2png b/deploy/dispmanx2png deleted file mode 100755 index b81b1774ff4f1c890029c51a69117615e4287375..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 73582 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

Y&97VZ2L5-V=NpCs@ESi;$J5sH8E?SS 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 Date: Wed, 4 Dec 2013 16:55:47 +0000 Subject: [PATCH 25/26] Added new version of dispmanx2png Former-commit-id: 7a09caaab1f8b187b2070cf0ce3dd2b3a5fd8aaa --- deploy/dispmanx2png | Bin 0 -> 73582 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100755 deploy/dispmanx2png diff --git a/deploy/dispmanx2png b/deploy/dispmanx2png new file mode 100755 index 0000000000000000000000000000000000000000..b81b1774ff4f1c890029c51a69117615e4287375 GIT binary patch literal 73582 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

Y&97VZ2L5-V=NpCs@ESi;$J5sH8E?SS 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 Date: Thu, 5 Dec 2013 12:57:32 +0000 Subject: [PATCH 26/26] Fixed the grabber to produce a limited number of flag based png-files with unique names, Former-commit-id: dd37349042efa4642761b63d9e57ae39986a8386 --- deploy/dispmanx2png | Bin 73582 -> 80963 bytes test/dispmanx2png/dispmanx2png.cpp | 5 +++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/deploy/dispmanx2png b/deploy/dispmanx2png index b81b1774ff4f1c890029c51a69117615e4287375..deff39744ef94596e7c1c29af451276ae09c93b9 100755 GIT binary patch literal 80963 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 WN6wYk8|%GU92O_Ou}esI%KJYjhu;nW literal 73582 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

Y&97VZ2L5-V=NpCs@ESi;$J5sH8E?SS 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 imageRgba(64, 64); - for (int i=0; i