diff --git a/assets/webconfig/i18n/en.json b/assets/webconfig/i18n/en.json index 445eb0fb..9f56ef26 100644 --- a/assets/webconfig/i18n/en.json +++ b/assets/webconfig/i18n/en.json @@ -253,6 +253,8 @@ "edt_conf_bb_unknownFrameCnt_title": "Unknown frames", "edt_conf_bge_heading_title": "Background Effect/Color", "edt_conf_bobls_heading_title": "Boblight Server", + "edt_conf_color_accuracyLevel_expl": "Level how accurate dominat colors are evaluated. A higher level creates more accurate results, but also requries more processing power. Should to be combined with reduced pixel processing.", + "edt_conf_color_accuracyLevel_title": "Accuracy level", "edt_conf_color_backlightColored_expl": "Add some color to your backlight.", "edt_conf_color_backlightColored_title": "Colored backlight", "edt_conf_color_backlightThreshold_expl": "The minimum amount of brightness (backlight). Disabled during effects, colors and in status \"Off\"", @@ -281,7 +283,7 @@ "edt_conf_color_heading_title": "Color Calibration", "edt_conf_color_id_expl": "User given name", "edt_conf_color_id_title": "ID", - "edt_conf_color_imageToLedMappingType_expl": "Overwrites the LED area assignment of your LED layout if it's not \"multicolor\"", + "edt_conf_color_imageToLedMappingType_expl": "Overwrites the LED area assignment of your LED layout if it's not \"Mean Color Simple\"", "edt_conf_color_imageToLedMappingType_title": "LED area assignment", "edt_conf_color_leds_expl": "Assign this adjustment to all LEDs (*) or just some (0-24).", "edt_conf_color_leds_title": "LED index", @@ -293,6 +295,8 @@ "edt_conf_color_saturationGain_title": "Saturation gain", "edt_conf_color_brightnessGain_expl": "Adjusts the brightness of colors. 1.0 means no change, over 1.0 increases brightness, under 1.0 decreases brightness.", "edt_conf_color_brightnessGain_title": "Brightness gain", + "edt_conf_color_reducedPixelSetFactorFactor_expl": "Evaluate only a set of pixels per LED area defined, Low ~25%, Medium ~10%, High ~6%", + "edt_conf_color_reducedPixelSetFactorFactor_title": "Reduced pixel processing", "edt_conf_color_white_expl": "The calibrated white value.", "edt_conf_color_white_title": "White", "edt_conf_color_yellow_expl": "The calibrated yellow value.", @@ -322,6 +326,8 @@ "edt_conf_enum_color": "Color", "edt_conf_enum_custom": "Custom", "edt_conf_enum_decay": "Decay", + "edt_conf_enum_delay": "Delay only", + "edt_conf_enum_disabled": "Disabled", "edt_conf_enum_dl_error": "Error", "edt_conf_enum_dl_informational": "Informational", "edt_conf_enum_dl_nodebug": "No Debug output", @@ -330,9 +336,12 @@ "edt_conf_enum_dl_verbose1": "Verbosity level 1", "edt_conf_enum_dl_verbose2": "Verbosity level 2", "edt_conf_enum_dl_verbose3": "Verbosity level 3", + "edt_conf_enum_dominant_color": "Dominant Color - per LED", + "edt_conf_enum_dominant_color_advanced": "Dominant Color Advanced - per LED", "edt_conf_enum_effect": "Effect", "edt_conf_enum_gbr": "GBR", "edt_conf_enum_grb": "GRB", + "edt_conf_enum_high": "High", "edt_conf_enum_hsv": "HSV", "edt_conf_enum_left_right": "Left to right", "edt_conf_enum_linear": "Linear", @@ -340,7 +349,10 @@ "edt_conf_enum_logsilent": "Silent", "edt_conf_enum_logverbose": "Verbose", "edt_conf_enum_logwarn": "Warning", - "edt_conf_enum_multicolor_mean": "Multicolor", + "edt_conf_enum_low": "Low", + "edt_conf_enum_medium": "Medium", + "edt_conf_enum_multicolor_mean": "Mean Color Simple - per LED", + "edt_conf_enum_multicolor_mean_squared": "Mean Color Squared - per LED", "edt_conf_enum_please_select": "Please Select", "edt_conf_enum_rbg": "RBG", "edt_conf_enum_rgb": "RGB", @@ -350,7 +362,7 @@ "edt_conf_enum_transeffect_sudden": "Sudden", "edt_conf_enum_udp_ddp": "DDP", "edt_conf_enum_udp_raw": "RAW", - "edt_conf_enum_unicolor_mean": "Unicolor", + "edt_conf_enum_unicolor_mean": "Mean Color Image - applied to all LEDs", "edt_conf_fbs_heading_title": "Flatbuffers Server", "edt_conf_fbs_timeout_expl": "If no data is received for the given period, the component will be (soft) disabled.", "edt_conf_fbs_timeout_title": "Timeout", @@ -974,8 +986,11 @@ "remote_losthint": "Note: All changes will be lost after a restart.", "remote_maptype_intro": "Usually the LED layout defines which LED covers a specific picture area. You can change it here: $1.", "remote_maptype_label": "Mapping type", - "remote_maptype_label_multicolor_mean": "Multicolor", - "remote_maptype_label_unicolor_mean": "Unicolor", + "remote_maptype_label_dominant_color": "Dominant Color", + "remote_maptype_label_dominant_color_advanced": "Dominant Color Advanced", + "remote_maptype_label_multicolor_mean": "Mean Color Simple", + "remote_maptype_label_multicolor_mean_squared": "Mean Color Squared", + "remote_maptype_label_unicolor_mean": "Mean Color Image", "remote_optgroup_syseffets": "System Effects", "remote_optgroup_templates_custom": "User Templates", "remote_optgroup_templates_system": "System Templates", diff --git a/include/hyperion/GrabberWrapper.h b/include/hyperion/GrabberWrapper.h index 99cfa4c6..fd58449c 100644 --- a/include/hyperion/GrabberWrapper.h +++ b/include/hyperion/GrabberWrapper.h @@ -147,10 +147,7 @@ private slots: void handleSourceRequest(hyperion::Components component, int hyperionInd, bool listen); /// - /// @brief Update Update capture rate - /// @param type interval between frames in milliseconds - /// - void updateTimer(int interval); + protected: @@ -168,6 +165,11 @@ protected: /// virtual bool close() { return true; } + /// @brief Update Update capture rate + /// @param type interval between frames in milliseconds + /// + void updateTimer(int interval); + QString _grabberName; diff --git a/include/hyperion/ImageProcessor.h b/include/hyperion/ImageProcessor.h index bc192668..84f4e26c 100644 --- a/include/hyperion/ImageProcessor.h +++ b/include/hyperion/ImageProcessor.h @@ -1,6 +1,7 @@ #pragma once #include +#include // Utils includes #include @@ -46,7 +47,7 @@ public: /// @param[in] width The new width of the buffer-image /// @param[in] height The new height of the buffer-image /// - void setSize(unsigned width, unsigned height); + void setSize(int width, int height); /// /// @brief Update the led string (eg on settings change) @@ -56,6 +57,19 @@ public: /// Returns state of black border detector bool blackBorderDetectorEnabled() const; + /// + /// Factor to reduce the number of pixels evaluated during processing + /// + /// @param[in] count Use every "count" pixel + void setReducedPixelSetFactorFactor(int count); + + /// + /// Set the accuracy used during processing + /// (only for selected types) + /// + /// @param[in] level The accuracy level (0-4) + void setAccuracyLevel(int level); + /// Returns the current _userMappingType, this may not be the current applied type! int getUserLedMappingType() const { return _userMappingType; } @@ -98,30 +112,45 @@ public: } /// - /// Processes the image to a list of led colors. This will update the size of the buffer-image - /// if required and call the image-to-leds mapping to determine the mean color per led. + /// Processes the image to a list of LED colors. This will update the size of the buffer-image + /// if required and call the image-to-LEDs mapping to determine the color per LED. /// - /// @param[in] image The image to translate to led values + /// @param[in] image The image to translate to LED values /// - /// @return The color value per led + /// @return The color value per LED /// template std::vector process(const Image& image) { std::vector colors; + if (image.width()>0 && image.height()>0) { // Ensure that the buffer-image is the proper size setSize(image); + assert(!_imageToLedColors.isNull()); + // Check black border detection verifyBorder(image); // Create a result vector and call the 'in place' function switch (_mappingType) { - case 1: colors = _imageToLeds->getUniLedColor(image); break; - default: colors = _imageToLeds->getMeanLedColor(image); + case 1: + colors = _imageToLedColors->getUniLedColor(image); + break; + case 2: + colors = _imageToLedColors->getMeanLedColorSqrt(image); + break; + case 3: + colors = _imageToLedColors->getDominantLedColor(image); + break; + case 4: + colors = _imageToLedColors->getDominantLedColorAdv(image); + break; + default: + colors = _imageToLedColors->getMeanLedColor(image); } } else @@ -136,8 +165,8 @@ public: /// /// Determines the led colors of the image in the buffer. /// - /// @param[in] image The image to translate to led values - /// @param[out] ledColors The color value per led + /// @param[in] image The image to translate to LED values + /// @param[out] ledColors The color value per LED /// template void process(const Image& image, std::vector& ledColors) @@ -153,8 +182,20 @@ public: // Determine the mean or uni colors of each led (using the existing mapping) switch (_mappingType) { - case 1: _imageToLeds->getUniLedColor(image, ledColors); break; - default: _imageToLeds->getMeanLedColor(image, ledColors); + case 1: + _imageToLedColors->getUniLedColor(image, ledColors); + break; + case 2: + _imageToLedColors->getMeanLedColorSqrt(image, ledColors); + break; + case 3: + _imageToLedColors->getDominantLedColor(image, ledColors); + break; + case 4: + _imageToLedColors->getDominantLedColorAdv(image, ledColors); + break; + default: + _imageToLedColors->getMeanLedColor(image, ledColors); } } else @@ -164,9 +205,9 @@ public: } /// - /// Get the hscan and vscan parameters for a single led + /// Get the hscan and vscan parameters for a single LED /// - /// @param[in] led Index of the led + /// @param[in] led Index of the LED /// @param[out] hscanBegin begin of the hscan /// @param[out] hscanEnd end of the hscan /// @param[out] vscanBegin begin of the hscan @@ -175,6 +216,13 @@ public: bool getScanParameters(size_t led, double & hscanBegin, double & hscanEnd, double & vscanBegin, double & vscanEnd) const; private: + + void registerProcessingUnit( + int width, + int height, + int horizontalBorder, + int verticalBorder); + /// /// Performs black-border detection (if enabled) on the given image /// @@ -183,34 +231,25 @@ private: template void verifyBorder(const Image & image) { - if (!_borderProcessor->enabled() && ( _imageToLeds->horizontalBorder()!=0 || _imageToLeds->verticalBorder()!=0 )) + if (!_borderProcessor->enabled() && ( _imageToLedColors->horizontalBorder()!=0 || _imageToLedColors->verticalBorder()!=0 )) { Debug(_log, "Reset border"); _borderProcessor->process(image); - delete _imageToLeds; - _imageToLeds = new hyperion::ImageToLedsMap(image.width(), image.height(), 0, 0, _ledString.leds()); + registerProcessingUnit(image.width(), image.height(), 0, 0); } if(_borderProcessor->enabled() && _borderProcessor->process(image)) { const hyperion::BlackBorder border = _borderProcessor->getCurrentBorder(); - // Clean up the old mapping - delete _imageToLeds; - if (border.unknown) { - // Construct a new buffer and mapping - _imageToLeds = new hyperion::ImageToLedsMap(image.width(), image.height(), 0, 0, _ledString.leds()); + registerProcessingUnit(image.width(), image.height(), 0, 0); } else { - // Construct a new buffer and mapping - _imageToLeds = new hyperion::ImageToLedsMap(image.width(), image.height(), border.horizontalSize, border.verticalSize, _ledString.leds()); + registerProcessingUnit(image.width(), image.height(), border.horizontalSize, border.verticalSize); } - - //Debug(Logger::getInstance("BLACKBORDER"), "CURRENT BORDER TYPE: unknown=%d hor.size=%d vert.size=%d", - // border.unknown, border.horizontalSize, border.verticalSize ); } } @@ -218,6 +257,7 @@ private slots: void handleSettingsUpdate(settings::type type, const QJsonDocument& config); private: + Logger * _log; /// The Led-string specification LedString _ledString; @@ -226,15 +266,18 @@ private: hyperion::BlackBorderProcessor * _borderProcessor; /// The mapping of image-pixels to LEDs - hyperion::ImageToLedsMap* _imageToLeds; + QSharedPointer _imageToLedColors; - /// Type of image 2 led mapping + /// Type of image to LED mapping int _mappingType; /// Type of last requested user type int _userMappingType; /// Type of last requested hard type int _hardMappingType; + int _accuraryLevel; + int _reducedPixelSetFactorFactor; + /// Hyperion instance pointer Hyperion* _hyperion; }; diff --git a/include/hyperion/ImageToLedsMap.h b/include/hyperion/ImageToLedsMap.h index 779082ab..17662f28 100644 --- a/include/hyperion/ImageToLedsMap.h +++ b/include/hyperion/ImageToLedsMap.h @@ -1,72 +1,90 @@ - -#pragma once +#ifndef IMAGETOLEDSMAP_H +#define IMAGETOLEDSMAP_H // STL includes #include +#include #include +#include // hyperion-utils includes #include #include +#include +#include // hyperion includes #include namespace hyperion { - /// - /// The ImageToLedsMap holds a mapping of indices into an image to leds. It can be used to - /// calculate the average (or mean) color per led for a specific region. + /// The ImageToLedsMap holds a mapping of indices into an image to LEDs. It can be used to + /// calculate the average (aka mean) or dominant color per LED for a given region. /// - class ImageToLedsMap + class ImageToLedsMap : public QObject { + Q_OBJECT + public: /// - /// Constructs an mapping from the absolute indices in an image to each led based on the border - /// definition given in the list of leds. The map holds absolute indices to any given image, + /// Constructs an mapping from the absolute indices in an image to each LED based on the border + /// definition given in the list of LEDs. The map holds absolute indices to any given image, /// provided that it is row-oriented. /// The mapping is created purely on size (width and height). The given borders are excluded /// from indexing. /// + /// @param[in] log Logger /// @param[in] width The width of the indexed image /// @param[in] height The width of the indexed image /// @param[in] horizontalBorder The size of the horizontal border (0=no border) /// @param[in] verticalBorder The size of the vertical border (0=no border) /// @param[in] leds The list with led specifications + /// @param[in] reducedProcessingFactor Factor to reduce the number of pixels evaluated during processing + /// @param[in] accuraryLevel The accuracy used during processing (only for selected types) /// ImageToLedsMap( - const unsigned width, - const unsigned height, - const unsigned horizontalBorder, - const unsigned verticalBorder, - const std::vector & leds); + Logger* log, + int width, + int height, + int horizontalBorder, + int verticalBorder, + const std::vector & leds, + int reducedProcessingFactor = 0, + int accuraryLevel = 0); /// /// Returns the width of the indexed image /// /// @return The width of the indexed image [pixels] /// - unsigned width() const; + int width() const; /// /// Returns the height of the indexed image /// /// @return The height of the indexed image [pixels] /// - unsigned height() const; + int height() const; - unsigned horizontalBorder() const { return _horizontalBorder; } - unsigned verticalBorder() const { return _verticalBorder; } + int horizontalBorder() const { return _horizontalBorder; } + int verticalBorder() const { return _verticalBorder; } /// - /// Determines the mean color for each led using the mapping the image given + /// Set the accuracy used during processing + /// (only for selected types) + /// + /// @param[in] level The accuracy level (0-4) + void setAccuracyLevel (int level); + + /// + /// Determines the mean color for each LED using the LED area mapping given /// at construction. /// /// @param[in] image The image from which to extract the led colors /// - /// @return ledColors The vector containing the output + /// @return The vector containing the output /// template std::vector getMeanLedColor(const Image & image) const @@ -77,20 +95,18 @@ namespace hyperion } /// - /// Determines the mean color for each led using the mapping the image given + /// Determines the mean color for each LED using the LED area mapping given /// at construction. /// - /// @param[in] image The image from which to extract the led colors + /// @param[in] image The image from which to extract the LED colors /// @param[out] ledColors The vector containing the output /// template void getMeanLedColor(const Image & image, std::vector & ledColors) const { - // Sanity check for the number of leds - //assert(_colorsMap.size() == ledColors.size()); if(_colorsMap.size() != ledColors.size()) { - Debug(Logger::getInstance("HYPERION"), "ImageToLedsMap: colorsMap.size != ledColors.size -> %d != %d", _colorsMap.size(), ledColors.size()); + Debug(_log, "ImageToLedsMap: colorsMap.size != ledColors.size -> %d != %d", _colorsMap.size(), ledColors.size()); return; } @@ -104,12 +120,52 @@ namespace hyperion } /// - /// Determines the uni color for each led using the mapping the image given + /// Determines the mean color squared for each LED using the LED area mapping given /// at construction. /// /// @param[in] image The image from which to extract the led colors /// - /// @return ledColors The vector containing the output + /// @return The vector containing the output + /// + template + std::vector getMeanLedColorSqrt(const Image & image) const + { + std::vector colors(_colorsMap.size(), ColorRgb{0,0,0}); + getMeanLedColorSqrt(image, colors); + return colors; + } + + /// + /// Determines the mean color squared for each LED using the LED area mapping given + /// at construction. + /// + /// @param[in] image The image from which to extract the LED colors + /// @param[out] ledColors The vector containing the output + /// + template + void getMeanLedColorSqrt(const Image & image, std::vector & ledColors) const + { + if(_colorsMap.size() != ledColors.size()) + { + Debug(_log, "ImageToLedsMap: colorsMap.size != ledColors.size -> %d != %d", _colorsMap.size(), ledColors.size()); + return; + } + + // Iterate each led and compute the mean + auto led = ledColors.begin(); + for (auto colors = _colorsMap.begin(); colors != _colorsMap.end(); ++colors, ++led) + { + const ColorRgb color = calcMeanColorSqrt(image, *colors); + *led = color; + } + } + + /// + /// Determines the mean color of the image and assigns it to all LEDs + /// + /// @param[in] image The image from which to extract the led color + /// + /// @return The vector containing the output /// template std::vector getUniLedColor(const Image & image) const @@ -120,57 +176,145 @@ namespace hyperion } /// - /// Determines the uni color for each led using the mapping the image given - /// at construction. + /// Determines the mean color of the image and assigns it to all LEDs /// - /// @param[in] image The image from which to extract the led colors + /// @param[in] image The image from which to extract the LED colors /// @param[out] ledColors The vector containing the output /// template void getUniLedColor(const Image & image, std::vector & ledColors) const { - // Sanity check for the number of leds - // assert(_colorsMap.size() == ledColors.size()); if(_colorsMap.size() != ledColors.size()) { - Debug(Logger::getInstance("HYPERION"), "ImageToLedsMap: colorsMap.size != ledColors.size -> %d != %d", _colorsMap.size(), ledColors.size()); + Debug(_log, "ImageToLedsMap: colorsMap.size != ledColors.size -> %d != %d", _colorsMap.size(), ledColors.size()); return; } - // calculate uni color const ColorRgb color = calcMeanColor(image); + //Update all LEDs with same color std::fill(ledColors.begin(),ledColors.end(), color); } - private: - /// The width of the indexed image - const unsigned _width; - /// The height of the indexed image - const unsigned _height; - - const unsigned _horizontalBorder; - - const unsigned _verticalBorder; - - /// The absolute indices into the image for each led - std::vector> _colorsMap; + /// + /// Determines the dominant color for each LED using the LED area mapping given + /// at construction. + /// + /// @param[in] image The image from which to extract the LED color + /// + /// @return The vector containing the output + /// + template + std::vector getDominantLedColor(const Image & image) const + { + std::vector colors(_colorsMap.size(), ColorRgb{0,0,0}); + getDominantLedColor(image, colors); + return colors; + } /// - /// Calculates the 'mean color' of the given list. This is the mean over each color-channel + /// Determines the dominant color for each LED using the LED area mapping given + /// at construction. + /// + /// @param[in] image The image from which to extract the LED colors + /// @param[out] ledColors The vector containing the output + /// + template + void getDominantLedColor(const Image & image, std::vector & ledColors) const + { + // Sanity check for the number of LEDs + if(_colorsMap.size() != ledColors.size()) + { + Debug(_log, "ImageToLedsMap: colorsMap.size != ledColors.size -> %d != %d", _colorsMap.size(), ledColors.size()); + return; + } + + // Iterate each led and compute the dominant color + auto led = ledColors.begin(); + for (auto colors = _colorsMap.begin(); colors != _colorsMap.end(); ++colors, ++led) + { + const ColorRgb color = calculateDominantColor(image, *colors); + *led = color; + } + } + + /// + /// Determines the dominant color using a k-means algorithm for each LED using the LED area mapping given + /// at construction. + /// + /// @param[in] image The image from which to extract the LED color + /// + /// @return The vector containing the output + /// + template + std::vector getDominantLedColorAdv(const Image & image) const + { + std::vector colors(_colorsMap.size(), ColorRgb{0,0,0}); + getDominantLedColorAdv(image, colors); + return colors; + } + + /// + /// Determines the dominant color using a k-means algorithm for each LED using the LED area mapping given + /// at construction. + /// + /// @param[in] image The image from which to extract the LED colors + /// @param[out] ledColors The vector containing the output + /// + template + void getDominantLedColorAdv(const Image & image, std::vector & ledColors) const + { + // Sanity check for the number of LEDs + if(_colorsMap.size() != ledColors.size()) + { + Debug(_log, "ImageToLedsMap: colorsMap.size != ledColors.size -> %d != %d", _colorsMap.size(), ledColors.size()); + return; + } + + // Iterate each led and compute the dominant color + auto led = ledColors.begin(); + for (auto colors = _colorsMap.begin(); colors != _colorsMap.end(); ++colors, ++led) + { + const ColorRgb color = calculateDominantColorAdv(image, *colors); + *led = color; + } + } + + private: + + Logger* _log; + + /// The width of the indexed image + const int _width; + /// The height of the indexed image + const int _height; + + const int _horizontalBorder; + const int _verticalBorder; + + /// Evaluate every "count" pixel + int _nextPixelCount; + + /// Number of clusters used during dominant color advanced processing (k-means) + int _clusterCount; + + /// The absolute indices into the image for each led + std::vector> _colorsMap; + + /// + /// Calculates the 'mean color' over the given image. This is the mean over each color-channel /// (red, green, blue) /// /// @param[in] image The image a section from which an average color must be computed - /// @param[in] colors The list with colors + /// @param[in] pixels The list of pixel indices for the given image to be evaluated/// /// /// @return The mean of the given list of colors (or black when empty) /// template - ColorRgb calcMeanColor(const Image & image, const std::vector & colors) const + ColorRgb calcMeanColor(const Image & image, const std::vector & pixels) const { - const auto colorVecSize = colors.size(); - - if (colorVecSize == 0) + const auto pixelNum = pixels.size(); + if (pixelNum == 0) { return ColorRgb::BLACK; } @@ -179,20 +323,20 @@ namespace hyperion uint_fast32_t cummRed = 0; uint_fast32_t cummGreen = 0; uint_fast32_t cummBlue = 0; - const auto& imgData = image.memptr(); - for (const unsigned colorOffset : colors) + const auto& imgData = image.memptr(); + for (const int pixelOffset : pixels) { - const auto& pixel = imgData[colorOffset]; + const auto& pixel = imgData[pixelOffset]; cummRed += pixel.red; cummGreen += pixel.green; cummBlue += pixel.blue; } // Compute the average of each color channel - const uint8_t avgRed = uint8_t(cummRed/colorVecSize); - const uint8_t avgGreen = uint8_t(cummGreen/colorVecSize); - const uint8_t avgBlue = uint8_t(cummBlue/colorVecSize); + const uint8_t avgRed = uint8_t(cummRed/pixelNum); + const uint8_t avgGreen = uint8_t(cummGreen/pixelNum); + const uint8_t avgBlue = uint8_t(cummBlue/pixelNum); // Return the computed color return {avgRed, avgGreen, avgBlue}; @@ -213,11 +357,11 @@ namespace hyperion uint_fast32_t cummRed = 0; uint_fast32_t cummGreen = 0; uint_fast32_t cummBlue = 0; - const unsigned imageSize = image.width() * image.height(); + const unsigned pixelNum = image.width() * image.height(); const auto& imgData = image.memptr(); - for (unsigned idx=0; idx + ColorRgb calcMeanColorSqrt(const Image & image, const std::vector & pixels) const + { + const auto pixelNum = pixels.size(); + if (pixelNum == 0) + { + return ColorRgb::BLACK; + } + + // Accumulate the squared sum of each separate color channel + uint_fast32_t cummRed = 0; + uint_fast32_t cummGreen = 0; + uint_fast32_t cummBlue = 0; + + const auto& imgData = image.memptr(); + + for (const int colorOffset : pixels) + { + const auto& pixel = imgData[colorOffset]; + + cummRed += pixel.red * pixel.red; + cummGreen += pixel.green * pixel.green; + cummBlue += pixel.blue * pixel.blue; + } + + // Compute the average of each color channel + const uint8_t avgRed = uint8_t(std::min(std::lround(sqrt(static_cast(cummRed/pixelNum))), 255L)); + const uint8_t avgGreen = uint8_t(std::min(std::lround(sqrt(static_cast(cummGreen/pixelNum))), 255L)); + const uint8_t avgBlue = uint8_t(std::min(std::lround(sqrt(static_cast(cummBlue/pixelNum))), 255L)); + + // Return the computed color + return {avgRed, avgGreen, avgBlue}; + } + + /// + /// Calculates the 'mean color' squared over the given image. This is the mean over each color-channel + /// (red, green, blue) + /// + /// @param[in] image The image a section from which an average color must be computed + /// + /// @return The mean of the given list of colors (or black when empty) + /// + template + ColorRgb calcMeanColorSqrt(const Image & image) const + { + // Accumulate the squared sum of each separate color channel + uint_fast32_t cummRed = 0; + uint_fast32_t cummGreen = 0; + uint_fast32_t cummBlue = 0; + + const unsigned pixelNum = image.width() * image.height(); + const auto& imgData = image.memptr(); + + for (int idx=0; idx(cummRed/pixelNum)))); + const uint8_t avgGreen = uint8_t(std::lround(sqrt(static_cast(cummGreen/pixelNum)))); + const uint8_t avgBlue = uint8_t(std::lround(sqrt(static_cast(cummBlue/pixelNum)))); + + // Return the computed color + return {avgRed, avgGreen, avgBlue}; + } + + /// + /// Calculates the 'dominant color' of an image area defined by a list of pixel indices + /// + /// @param[in] image The image for which a dominant color is to be computed + /// @param[in] pixels The list of pixel indices for the given image to be evaluated + /// + /// @return The image area's dominant color or black, if no pixel indices provided + /// + template + ColorRgb calculateDominantColor(const Image & image, const std::vector & pixels) const + { + ColorRgb dominantColor {ColorRgb::BLACK}; + + const auto pixelNum = pixels.size(); + if (pixelNum > 0) + { + const auto& imgData = image.memptr(); + + QMap colorDistributionMap; + int count = 0; + for (const int pixelOffset : pixels) + { + QRgb color = imgData[pixelOffset].rgb(); + if (colorDistributionMap.contains(color)) { + colorDistributionMap[color] = colorDistributionMap[color] + 1; + } + else { + colorDistributionMap[color] = 1; + } + + int colorsFound = colorDistributionMap[color]; + if (colorsFound > count) { + dominantColor.setRgb(color); + count = colorsFound; + } + } + } + return dominantColor; + } + + /// + /// Calculates the 'dominant color' of an image + /// + /// @param[in] image The image for which a dominant color is to be computed + /// + /// @return The image's dominant color + /// + template + ColorRgb calculateDominantColor(const Image & image) const + { + const unsigned pixelNum = image.width() * image.height(); + + std::vector pixels(pixelNum); + std::iota(pixels.begin(), pixels.end(), 0); + + return calculateDominantColor(image, pixels); + } + + template + struct ColorCluster { + + ColorCluster():count(0) {} + ColorCluster(Pixel_T color):count(0),color(color) {} + + Pixel_T color; + Pixel_T newColor; + int count; + }; + + const ColorRgb DEFAULT_CLUSTER_COLORS[5] { + {ColorRgb::BLACK}, + {ColorRgb::GREEN}, + {ColorRgb::WHITE}, + {ColorRgb::RED}, + {ColorRgb::YELLOW} + }; + + /// + /// Calculates the 'dominant color' of an image area defined by a list of pixel indices + /// using a k-means algorithm (https://robocraft.ru/computervision/1063) + /// + /// @param[in] image The image for which a dominant color is to be computed + /// @param[in] pixels The list of pixel indices for the given image to be evaluated + /// + /// @return The image area's dominant color or black, if no pixel indices provided + /// + template + ColorRgb calculateDominantColorAdv(const Image & image, const std::vector & pixels) const + { + ColorRgb dominantColor {ColorRgb::BLACK}; + const auto pixelNum = pixels.size(); + if (pixelNum > 0) + { + // initial cluster with different colors + auto clusters = std::unique_ptr< ColorCluster >(new ColorCluster[_clusterCount]); + for(int k = 0; k < _clusterCount; ++k) + { + clusters.get()[k].newColor = DEFAULT_CLUSTER_COLORS[k]; + } + + // k-means + double min_rgb_euclidean {0}; + double old_rgb_euclidean {0}; + + while(1) + { + for(int k = 0; k < _clusterCount; ++k) + { + clusters.get()[k].count = 0; + clusters.get()[k].color = clusters.get()[k].newColor; + clusters.get()[k].newColor.setRgb(ColorRgb::BLACK); + } + + const auto& imgData = image.memptr(); + for (const int pixelOffset : pixels) + { + const auto& pixel = imgData[pixelOffset]; + + min_rgb_euclidean = 255 * 255 * 255; + int clusterIndex = -1; + for(int k = 0; k < _clusterCount; ++k) + { + double euclid = ColorSys::rgb_euclidean(ColorRgbScalar(pixel), clusters.get()[k].color); + + if( euclid < min_rgb_euclidean ) { + min_rgb_euclidean = euclid; + clusterIndex = k; + } + } + + clusters.get()[clusterIndex].count++; + clusters.get()[clusterIndex].newColor += ColorRgbScalar(pixel); + } + + min_rgb_euclidean = 0; + for(int k = 0; k < _clusterCount; ++k) + { + if (clusters.get()[k].count > 0) + { + // new color + clusters.get()[k].newColor /= clusters.get()[k].count; + double ecli = ColorSys::rgb_euclidean(clusters.get()[k].newColor, clusters.get()[k].color); + if(ecli > min_rgb_euclidean) + { + min_rgb_euclidean = ecli; + } + } + } + + if( fabs(min_rgb_euclidean - old_rgb_euclidean) < 1) + { + break; + } + + old_rgb_euclidean = min_rgb_euclidean; + } + + int colorsFoundMax = 0; + int dominantClusterIdx {0}; + + for(int clusterIdx=0; clusterIdx < _clusterCount; ++clusterIdx){ + int colorsFoundinCluster = clusters.get()[clusterIdx].count; + if (colorsFoundinCluster > colorsFoundMax) { + colorsFoundMax = colorsFoundinCluster; + dominantClusterIdx = clusterIdx; + } + } + + dominantColor.red = static_cast(clusters.get()[dominantClusterIdx].newColor.red); + dominantColor.green = static_cast(clusters.get()[dominantClusterIdx].newColor.green); + dominantColor.blue = static_cast(clusters.get()[dominantClusterIdx].newColor.blue); + } + + return dominantColor; + } + + /// + /// Calculates the 'dominant color' of an image area defined by a list of pixel indices + /// using a k-means algorithm (https://robocraft.ru/computervision/1063) + /// + /// @param[in] image The image for which a dominant color is to be computed + /// + /// @return The image's dominant color + /// + template + ColorRgb calculateDominantColorAdv(const Image & image) const + { + const unsigned pixelNum = image.width() * image.height(); + + std::vector pixels(pixelNum); + std::iota(pixels.begin(), pixels.end(), 0); + + return calculateDominantColorAdv(image, pixels); + } }; } // end namespace hyperion + +#endif // IMAGETOLEDSMAP_H diff --git a/include/utils/ColorRgb.h b/include/utils/ColorRgb.h index 901b000c..b9a91038 100644 --- a/include/utils/ColorRgb.h +++ b/include/utils/ColorRgb.h @@ -6,6 +6,7 @@ #include #include +#include /// /// Plain-Old-Data structure containing the red-green-blue color specification. Size of the @@ -52,6 +53,18 @@ struct ColorRgb return a; } + QRgb rgb() const + { + return qRgb(red,green,blue); + } + + void setRgb(QRgb rgb) + { + red = static_cast(qRed(rgb)); + green = static_cast(qGreen(rgb)); + blue = static_cast(qBlue(rgb)); + } + QString toQString() const { return QString("(%1,%2,%3)").arg(red).arg(green).arg(blue); diff --git a/include/utils/ColorRgbScalar.h b/include/utils/ColorRgbScalar.h new file mode 100644 index 00000000..3b605a2f --- /dev/null +++ b/include/utils/ColorRgbScalar.h @@ -0,0 +1,203 @@ +#ifndef COLORRGBSCALAR_H +#define COLORRGBSCALAR_H + +// STL includes +#include +#include + +#include +#include +#include +#include + +/// +/// Plain-Old-Data structure containing the red-green-blue color specification. Size of the +/// structure is exactly 3 times int for easy writing to led-device +/// +struct ColorRgbScalar +{ + /// The red color channel + int red; + /// The green color channel + int green; + /// The blue color channel + int blue; + + /// 'Black' RgbColor (0, 0, 0) + static const ColorRgbScalar BLACK; + /// 'Red' RgbColor (255, 0, 0) + static const ColorRgbScalar RED; + /// 'Green' RgbColor (0, 255, 0) + static const ColorRgbScalar GREEN; + /// 'Blue' RgbColor (0, 0, 255) + static const ColorRgbScalar BLUE; + /// 'Yellow' RgbColor (255, 255, 0) + static const ColorRgbScalar YELLOW; + /// 'White' RgbColor (255, 255, 255) + static const ColorRgbScalar WHITE; + + ColorRgbScalar() = default; + + ColorRgbScalar(int _red, int _green,int _blue): + red(_red), + green(_green), + blue(_blue) + { + + } + + ColorRgbScalar(ColorRgb rgb): + red(rgb.red), + green(rgb.green), + blue(rgb.blue) + { + + } + + ColorRgbScalar operator-(const ColorRgbScalar& b) const + { + ColorRgbScalar a(*this); + a.red -= b.red; + a.green -= b.green; + a.blue -= b.blue; + return a; + } + + void setRgb(QRgb rgb) + { + red = qRed(rgb); + green = qGreen(rgb); + blue = qBlue(rgb); + } + + void setRgb(ColorRgb rgb) + { + red = rgb.red; + green = rgb.green; + blue = rgb.blue; + } + + QString toQString() const + { + return QString("(%1,%2,%3)").arg(red).arg(green).arg(blue); + } +}; +/// Assert to ensure that the size of the structure is 'only' 3 times int +static_assert(sizeof(ColorRgbScalar) == 3 * sizeof(int), "Incorrect size of ColorRgbInt"); + + +/// +/// Stream operator to write ColorRgbInt to an outputstream (format "'{'[red]','[green]','[blue]'}'") +/// +/// @param os The output stream +/// @param color The color to write +/// @return The output stream (with the color written to it) +/// +inline std::ostream& operator<<(std::ostream& os, const ColorRgbScalar& color) +{ + os << "{" + << static_cast(color.red) << "," + << static_cast(color.green) << "," + << static_cast(color.blue) + << "}"; + + return os; +} + +/// +/// Stream operator to write ColorRgbInt to a QTextStream (format "'{'[red]','[green]','[blue]'}'") +/// +/// @param os The output stream +/// @param color The color to write +/// @return The output stream (with the color written to it) +/// +inline QTextStream& operator<<(QTextStream &os, const ColorRgbScalar& color) +{ + os << "{" + << static_cast(color.red) << "," + << static_cast(color.green) << "," + << static_cast(color.blue) + << "}"; + + return os; +} + +/// Compare operator to check if a color is 'equal' to another color +inline bool operator==(const ColorRgbScalar & lhs, const ColorRgbScalar & rhs) +{ + return lhs.red == rhs.red && + lhs.green == rhs.green && + lhs.blue == rhs.blue; +} + +/// Compare operator to check if a color is 'smaller' than another color +inline bool operator<(const ColorRgbScalar & lhs, const ColorRgbScalar & rhs) +{ + return lhs.red < rhs.red && + lhs.green < rhs.green && + lhs.blue < rhs.blue; +} + +/// Compare operator to check if a color is 'not equal' to another color +inline bool operator!=(const ColorRgbScalar & lhs, const ColorRgbScalar & rhs) +{ + return !(lhs == rhs); +} + +/// Compare operator to check if a color is 'smaller' than or 'equal' to another color +inline bool operator<=(const ColorRgbScalar & lhs, const ColorRgbScalar & rhs) +{ + return lhs.red <= rhs.red && + lhs.green <= rhs.green && + lhs.blue <= rhs.blue; +} + +/// Compare operator to check if a color is 'greater' to another color +inline bool operator>(const ColorRgbScalar & lhs, const ColorRgbScalar & rhs) +{ + return lhs.red > rhs.red && + lhs.green > rhs.green && + lhs.blue > rhs.blue; +} + +/// Compare operator to check if a color is 'greater' than or 'equal' to another color +inline bool operator>=(const ColorRgbScalar & lhs, const ColorRgbScalar & rhs) +{ + return lhs.red >= rhs.red && + lhs.green >= rhs.green && + lhs.blue >= rhs.blue; +} + +inline ColorRgbScalar& operator+=(ColorRgbScalar& lhs, const ColorRgbScalar& rhs) +{ + lhs.red += rhs.red; + lhs.green += rhs.green; + lhs.blue += rhs.blue; + + return lhs; +} + +inline ColorRgbScalar operator+(ColorRgbScalar lhs, const ColorRgbScalar rhs) +{ + lhs += rhs; + return lhs; +} + +inline ColorRgbScalar& operator/=(ColorRgbScalar& lhs, int count) +{ + if (count > 0) + { + lhs.red /= count; + lhs.green /= count; + lhs.blue /= count; + } + return lhs; +} + +inline ColorRgbScalar operator/(ColorRgbScalar lhs, int count) +{ + lhs /= count; + return lhs; +} + +#endif // COLORRGBSCALAR_H diff --git a/include/utils/ColorRgba.h b/include/utils/ColorRgba.h index 63afdc5a..648cebb7 100644 --- a/include/utils/ColorRgba.h +++ b/include/utils/ColorRgba.h @@ -30,11 +30,11 @@ struct ColorRgba static const ColorRgba WHITE; }; -/// Assert to ensure that the size of the structure is 'only' 3 bytes +/// Assert to ensure that the size of the structure is 'only' 4 bytes static_assert(sizeof(ColorRgba) == 4, "Incorrect size of ColorARGB"); /// -/// Stream operator to write ColorRgb to an outputstream (format "'{'[alpha]', '[red]','[green]','[blue]'}'") +/// Stream operator to write ColorRgba to an outputstream (format "'{'[alpha]', '[red]','[green]','[blue]'}'") /// /// @param os The output stream /// @param color The color to write diff --git a/include/utils/ColorSys.h b/include/utils/ColorSys.h index 63fb74cb..8a83ef7a 100644 --- a/include/utils/ColorSys.h +++ b/include/utils/ColorSys.h @@ -105,6 +105,19 @@ public: /// @note See https://bottosson.github.io/posts/colorpicker/#okhsv /// static void okhsv2rgb(double hue, double saturation, double value, uint8_t & red, uint8_t & green, uint8_t & blue); + + template + static double rgb_euclidean(Pixel_T p1, Pixel_T p2) + { + double val = sqrt( + (p1.red - p2.red) * (p1.red - p2.red) + + (p1.green - p2.green) * (p1.green - p2.green) + + (p1.blue - p2.blue) * (p1.blue - p2.blue) + ); + + return val; + } + }; #endif // COLORSYS_H diff --git a/libsrc/api/JSONRPC_schema/schema-processing.json b/libsrc/api/JSONRPC_schema/schema-processing.json index ddd04da1..d67828f0 100644 --- a/libsrc/api/JSONRPC_schema/schema-processing.json +++ b/libsrc/api/JSONRPC_schema/schema-processing.json @@ -12,7 +12,7 @@ }, "mappingType": { "type" : "string", - "enum" : ["multicolor_mean", "unicolor_mean"] + "enum" : ["multicolor_mean", "unicolor_mean", "multicolor_mean_squared", "dominant_color", "dominant_color_advanced"] } }, "additionalProperties": false diff --git a/libsrc/grabber/video/VideoWrapper.cpp b/libsrc/grabber/video/VideoWrapper.cpp index bd5ef76c..7a3ed201 100644 --- a/libsrc/grabber/video/VideoWrapper.cpp +++ b/libsrc/grabber/video/VideoWrapper.cpp @@ -74,9 +74,6 @@ void VideoWrapper::handleSettingsUpdate(settings::type type, const QJsonDocument // Device resolution _grabber.setWidthHeight(obj["width"].toInt(0), obj["height"].toInt(0)); - // Device framerate - _grabber.setFramerate(obj["fps"].toInt(15)); - // Device encoding format _grabber.setEncoding(obj["encoding"].toString("NO_CHANGE")); @@ -124,6 +121,11 @@ void VideoWrapper::handleSettingsUpdate(settings::type type, const QJsonDocument obj["blueSignalThreshold"].toDouble(0.0)/100.0, obj["noSignalCounterThreshold"].toInt(50)); + // Device framerate + _grabber.setFramerate(obj["fps"].toInt(15)); + + updateTimer(_ggrabber->getUpdateInterval()); + // Reload the Grabber if any settings have been changed that require it _grabber.reload(getV4lGrabberState()); } diff --git a/libsrc/hyperion/Grabber.cpp b/libsrc/hyperion/Grabber.cpp index 376da7ff..aefdb142 100644 --- a/libsrc/hyperion/Grabber.cpp +++ b/libsrc/hyperion/Grabber.cpp @@ -149,6 +149,8 @@ bool Grabber::setWidthHeight(int width, int height) bool Grabber::setFramerate(int fps) { + Debug(_log,"Set new frames per second to: %i fps, current fps: %i", fps, _fps); + if((fps > 0) && (_fps != fps)) { Info(_log,"Set new frames per second to: %i fps", fps); diff --git a/libsrc/hyperion/GrabberWrapper.cpp b/libsrc/hyperion/GrabberWrapper.cpp index a31e0339..1c846aa4 100644 --- a/libsrc/hyperion/GrabberWrapper.cpp +++ b/libsrc/hyperion/GrabberWrapper.cpp @@ -186,7 +186,8 @@ void GrabberWrapper::updateTimer(int interval) } void GrabberWrapper::handleSettingsUpdate(settings::type type, const QJsonDocument& config) -{ if(type == settings::SYSTEMCAPTURE && !_grabberName.startsWith("V4L")) +{ + if(type == settings::SYSTEMCAPTURE && !_grabberName.startsWith("V4L")) { // extract settings const QJsonObject& obj = config.object(); diff --git a/libsrc/hyperion/ImageProcessor.cpp b/libsrc/hyperion/ImageProcessor.cpp index f527a3a9..6f9c6222 100644 --- a/libsrc/hyperion/ImageProcessor.cpp +++ b/libsrc/hyperion/ImageProcessor.cpp @@ -4,26 +4,83 @@ #include #include -// Blacborder includes +// Blackborder includes #include +#include +#include + using namespace hyperion; +void ImageProcessor::registerProcessingUnit( + int width, + int height, + int horizontalBorder, + int verticalBorder) +{ + if (width > 0 && height > 0) + { + _imageToLedColors = QSharedPointer(new ImageToLedsMap( + _log, + width, + height, + horizontalBorder, + verticalBorder, + _ledString.leds(), + _reducedPixelSetFactorFactor, + _accuraryLevel + )); + } + else + { + _imageToLedColors = QSharedPointer(nullptr); + } +} + // global transform method int ImageProcessor::mappingTypeToInt(const QString& mappingType) { if (mappingType == "unicolor_mean" ) + { return 1; - + } + else if (mappingType == "multicolor_mean_squared" ) + { + return 2; + } + else if (mappingType == "dominant_color" ) + { + return 3; + } + else if (mappingType == "dominant_color_advanced" ) + { + return 4; + } return 0; } // global transform method QString ImageProcessor::mappingTypeToStr(int mappingType) { - if (mappingType == 1 ) - return "unicolor_mean"; + QString typeText; + switch (mappingType) { + case 1: + typeText = "unicolor_mean"; + break; + case 2: + typeText = "multicolor_mean_squared"; + break; + case 3: + typeText = "dominant_color"; + break; + case 4: + typeText = "dominant_color_advanced"; + break; + default: + typeText = "multicolor_mean"; + break; + } - return "multicolor_mean"; + return typeText; } ImageProcessor::ImageProcessor(const LedString& ledString, Hyperion* hyperion) @@ -31,10 +88,12 @@ ImageProcessor::ImageProcessor(const LedString& ledString, Hyperion* hyperion) , _log(nullptr) , _ledString(ledString) , _borderProcessor(new BlackBorderProcessor(hyperion, this)) - , _imageToLeds(nullptr) + , _imageToLedColors(nullptr) , _mappingType(0) , _userMappingType(0) - , _hardMappingType(0) + , _hardMappingType(-1) + , _accuraryLevel(0) + , _reducedPixelSetFactorFactor(1) , _hyperion(hyperion) { QString subComponent = hyperion->property("instance").toString(); @@ -48,7 +107,6 @@ ImageProcessor::ImageProcessor(const LedString& ledString, Hyperion* hyperion) ImageProcessor::~ImageProcessor() { - delete _imageToLeds; } void ImageProcessor::handleSettingsUpdate(settings::type type, const QJsonDocument& config) @@ -61,39 +119,40 @@ void ImageProcessor::handleSettingsUpdate(settings::type type, const QJsonDocume { setLedMappingType(newType); } + + int reducedPixelSetFactorFactor = obj["reducedPixelSetFactorFactor"].toString().toInt(); + setReducedPixelSetFactorFactor(reducedPixelSetFactorFactor); + + int accuracyLevel = obj["accuracyLevel"].toInt(); + setAccuracyLevel(accuracyLevel); } } -void ImageProcessor::setSize(unsigned width, unsigned height) +void ImageProcessor::setSize(int width, int height) { // Check if the existing buffer-image is already the correct dimensions - if (_imageToLeds && _imageToLeds->width() == width && _imageToLeds->height() == height) + if (!_imageToLedColors.isNull() && _imageToLedColors->width() == width && _imageToLedColors->height() == height) { return; } - // Clean up the old buffer and mapping - delete _imageToLeds; - // Construct a new buffer and mapping - _imageToLeds = (width>0 && height>0) ? (new ImageToLedsMap(width, height, 0, 0, _ledString.leds())) : nullptr; + registerProcessingUnit(width, height, 0, 0); } void ImageProcessor::setLedString(const LedString& ledString) { - if ( _imageToLeds != nullptr) + Debug(_log,""); + if ( !_imageToLedColors.isNull() ) { _ledString = ledString; // get current width/height - unsigned width = _imageToLeds->width(); - unsigned height = _imageToLeds->height(); - - // Clean up the old buffer and mapping - delete _imageToLeds; + int width = _imageToLedColors->width(); + int height = _imageToLedColors->height(); // Construct a new buffer and mapping - _imageToLeds = new ImageToLedsMap(width, height, 0, 0, _ledString.leds()); + registerProcessingUnit(width, height, 0, 0); } } @@ -107,15 +166,55 @@ bool ImageProcessor::blackBorderDetectorEnabled() const return _borderProcessor->enabled(); } +void ImageProcessor::setReducedPixelSetFactorFactor(int count) +{ + int currentReducedPixelSetFactor= _reducedPixelSetFactorFactor; + + _reducedPixelSetFactorFactor = count; + Debug(_log, "Set reduced pixel set factor to %d", _reducedPixelSetFactorFactor); + + if (currentReducedPixelSetFactor != _reducedPixelSetFactorFactor && !_imageToLedColors.isNull()) + { + int width = _imageToLedColors->width(); + int height = _imageToLedColors->height(); + + // Construct a new buffer and mapping + registerProcessingUnit(width, height, 0, 0); + } +} + +void ImageProcessor::setAccuracyLevel(int level) +{ + _accuraryLevel = level; + Debug(_log, "Set processing accuracy level to %d", _accuraryLevel); + + if (!_imageToLedColors.isNull()) + { + _imageToLedColors->setAccuracyLevel(_accuraryLevel); + } +} + void ImageProcessor::setLedMappingType(int mapType) { + int currentMappingType = _mappingType; + // if the _hardMappingType is >-1 we aren't allowed to overwrite it _userMappingType = mapType; - Debug(_log, "set user led mapping to %s", QSTRING_CSTR(mappingTypeToStr(mapType))); + + Debug(_log, "Set user LED mapping to %s", QSTRING_CSTR(mappingTypeToStr(mapType))); + if(_hardMappingType == -1) { _mappingType = mapType; } + + if (currentMappingType != _mappingType && !_imageToLedColors.isNull()) + { + int width = _imageToLedColors->width(); + int height = _imageToLedColors->height(); + + registerProcessingUnit(width, height, 0, 0); + } } void ImageProcessor::setHardLedMappingType(int mapType) diff --git a/libsrc/hyperion/ImageToLedsMap.cpp b/libsrc/hyperion/ImageToLedsMap.cpp index 783fdedd..f3e3ac24 100644 --- a/libsrc/hyperion/ImageToLedsMap.cpp +++ b/libsrc/hyperion/ImageToLedsMap.cpp @@ -3,17 +3,26 @@ using namespace hyperion; ImageToLedsMap::ImageToLedsMap( - unsigned width, - unsigned height, - unsigned horizontalBorder, - unsigned verticalBorder, - const std::vector& leds) - : _width(width) + Logger* log, + int width, + int height, + int horizontalBorder, + int verticalBorder, + const std::vector& leds, + int reducedPixelSetFactor, + int accuracyLevel) + : _log(log) + , _width(width) , _height(height) , _horizontalBorder(horizontalBorder) , _verticalBorder(verticalBorder) + , _nextPixelCount(reducedPixelSetFactor) + , _clusterCount() , _colorsMap() { + _nextPixelCount = reducedPixelSetFactor + 1; + setAccuracyLevel(accuracyLevel); + // Sanity check of the size of the borders (and width and height) Q_ASSERT(_width > 2*_verticalBorder); Q_ASSERT(_height > 2*_horizontalBorder); @@ -23,10 +32,14 @@ ImageToLedsMap::ImageToLedsMap( // Reserve enough space in the map for the leds _colorsMap.reserve(leds.size()); - const unsigned xOffset = _verticalBorder; - const unsigned actualWidth = _width - 2 * _verticalBorder; - const unsigned yOffset = _horizontalBorder; - const unsigned actualHeight = _height - 2 * _horizontalBorder; + const int xOffset = _verticalBorder; + const int actualWidth = _width - 2 * _verticalBorder; + const int yOffset = _horizontalBorder; + const int actualHeight = _height - 2 * _horizontalBorder; + + size_t totalCount = 0; + size_t totalCapacity = 0; + int ledCounter = 0; for (const Led& led : leds) { @@ -38,10 +51,10 @@ ImageToLedsMap::ImageToLedsMap( } // Compute the index boundaries for this led - unsigned minX_idx = xOffset + unsigned(qRound(actualWidth * led.minX_frac)); - unsigned maxX_idx = xOffset + unsigned(qRound(actualWidth * led.maxX_frac)); - unsigned minY_idx = yOffset + unsigned(qRound(actualHeight * led.minY_frac)); - unsigned maxY_idx = yOffset + unsigned(qRound(actualHeight * led.maxY_frac)); + int minX_idx = xOffset + int32_t(qRound(actualWidth * led.minX_frac)); + int maxX_idx = xOffset + int32_t(qRound(actualWidth * led.maxX_frac)); + int minY_idx = yOffset + int32_t(qRound(actualHeight * led.minY_frac)); + int maxY_idx = yOffset + int32_t(qRound(actualHeight * led.maxY_frac)); // make sure that the area is at least a single led large minX_idx = qMin(minX_idx, xOffset + actualWidth - 1); @@ -56,31 +69,70 @@ ImageToLedsMap::ImageToLedsMap( } // Add all the indices in the above defined rectangle to the indices for this led - const auto maxYLedCount = qMin(maxY_idx, yOffset+actualHeight); - const auto maxXLedCount = qMin(maxX_idx, xOffset+actualWidth); + const int maxYLedCount = qMin(maxY_idx, yOffset+actualHeight); + const int maxXLedCount = qMin(maxX_idx, xOffset+actualWidth); - std::vector ledColors; - ledColors.reserve((size_t) maxXLedCount*maxYLedCount); + const int realYLedCount = qAbs(maxYLedCount - minY_idx); + const int realXLedCount = qAbs(maxXLedCount - minX_idx); - for (unsigned y = minY_idx; y < maxYLedCount; ++y) + bool skipPixelProcessing {false}; + if (_nextPixelCount > 1) { - for (unsigned x = minX_idx; x < maxXLedCount; ++x) + skipPixelProcessing = true; + } + + size_t totalSize = static_cast(realYLedCount * realXLedCount); + + if (!skipPixelProcessing && totalSize > 1600) + { + skipPixelProcessing = true; + _nextPixelCount = 2; + Warning(_log, "Mapping LED/light [%d]. The current mapping area contains %d pixels which is huge. Therefore every %d pixels will be skipped. You can enable reduced processing to hide that warning.", ledCounter, totalSize, _nextPixelCount); + } + + std::vector ledColors; + ledColors.reserve(totalSize); + + for (int y = minY_idx; y < maxYLedCount; y += _nextPixelCount) + { + for (int x = minX_idx; x < maxXLedCount; x += _nextPixelCount) { - ledColors.push_back(y*width + x); + ledColors.push_back( y * width + x); } } // Add the constructed vector to the map _colorsMap.push_back(ledColors); + + totalCount += ledColors.size(); + totalCapacity += ledColors.capacity(); + + ledCounter++; } + Debug(_log, "Total index number is: %d (memory: %d). Reduced pixel set factor: %d, Accuracy level: %d, Image size: %d x %d, LED areas: %d", + totalCount, totalCapacity, reducedPixelSetFactor, accuracyLevel, width, height, leds.size()); + } -unsigned ImageToLedsMap::width() const +int ImageToLedsMap::width() const { return _width; } -unsigned ImageToLedsMap::height() const +int ImageToLedsMap::height() const { return _height; } + +void ImageToLedsMap::setAccuracyLevel (int accuracyLevel) +{ + if (accuracyLevel > 4 ) + { + Warning(_log, "Accuracy level %d is too high, it will be set to 4", accuracyLevel); + accuracyLevel = 4; + } + //Set cluster number for dominant color advanced + _clusterCount = accuracyLevel + 1; + +} + diff --git a/libsrc/hyperion/schema/schema-color.json b/libsrc/hyperion/schema/schema-color.json index abe39560..a56d3bed 100644 --- a/libsrc/hyperion/schema/schema-color.json +++ b/libsrc/hyperion/schema/schema-color.json @@ -9,20 +9,44 @@ "type" : "string", "required" : true, "title" : "edt_conf_color_imageToLedMappingType_title", - "enum" : ["multicolor_mean", "unicolor_mean"], + "enum" : ["multicolor_mean", "unicolor_mean", "multicolor_mean_squared", "dominant_color", "dominant_color_advanced"], "default" : "multicolor_mean", "options" : { - "enum_titles" : ["edt_conf_enum_multicolor_mean", "edt_conf_enum_unicolor_mean"] + "enum_titles" : ["edt_conf_enum_multicolor_mean", "edt_conf_enum_unicolor_mean", "edt_conf_enum_multicolor_mean_squared", "edt_conf_enum_dominant_color", "edt_conf_enum_dominant_color_advanced"] }, "propertyOrder" : 1 }, + "accuracyLevel": { + "type": "integer", + "title": "edt_conf_color_accuracyLevel_title", + "minimum": 1, + "maximum": 4, + "default": 2, + "propertyOrder": 2, + "options": { + "dependencies": { + "imageToLedMappingType": "dominant_color_advanced" + } + } + }, + "reducedPixelSetFactorFactor": { + "type": "string", + "title": "edt_conf_color_reducedPixelSetFactorFactor_title", + "default": 0, + "enum" : ["0", "1", "2", "3"], + "default" : "0", + "options" : { + "enum_titles" : ["edt_conf_enum_disabled", "edt_conf_enum_low", "edt_conf_enum_medium", "edt_conf_enum_high"] + }, + "propertyOrder": 3 + }, "channelAdjustment" : { "type" : "array", "title" : "edt_conf_color_channelAdjustment_header_title", "minItems": 1, "required" : true, - "propertyOrder" : 3, + "propertyOrder" : 4, "items" : { "type" : "object", diff --git a/test/TestImage2LedsMap.cpp b/test/TestImage2LedsMap.cpp index 052a21ef..bb9aa618 100644 --- a/test/TestImage2LedsMap.cpp +++ b/test/TestImage2LedsMap.cpp @@ -2,6 +2,7 @@ // Utils includes #include #include +#include // Hyperion includes #include @@ -9,6 +10,9 @@ int main() { + Logger* log = Logger::getInstance("TestImageLedsMap"); + Logger::setLogLevel(Logger::DEBUG); + const QString schemaFile = ":/hyperion-schema"; const QString configFile = ":/hyperion_default.config"; @@ -25,7 +29,7 @@ int main() const ColorRgb testColor = {64, 123, 12}; Image image(64, 64, testColor); - hyperion::ImageToLedsMap map(64, 64, 0, 0, ledString.leds()); + hyperion::ImageToLedsMap map(log, 64, 64, 0, 0, ledString.leds()); std::vector ledColors(ledString.leds().size()); map.getMeanLedColor(image, ledColors);