From 491d6ff608ee3e9fc9e829e90aa39b6ed12d9fee Mon Sep 17 00:00:00 2001 From: johan Date: Mon, 5 Aug 2013 23:49:17 +0200 Subject: [PATCH] ColorTransform functions added to repo --- libsrc/hyperion/CMakeLists.txt | 4 +- libsrc/hyperion/ColorTransform.cpp | 102 +++++++++++++++++++++++++++++ libsrc/hyperion/ColorTransform.h | 49 ++++++++++++++ test/CMakeLists.txt | 14 ++-- test/TestColorTransform.cpp | 93 ++++++++++++++++++++++++++ 5 files changed, 257 insertions(+), 5 deletions(-) create mode 100644 libsrc/hyperion/ColorTransform.cpp create mode 100644 libsrc/hyperion/ColorTransform.h create mode 100644 test/TestColorTransform.cpp diff --git a/libsrc/hyperion/CMakeLists.txt b/libsrc/hyperion/CMakeLists.txt index 7ab851b4..94ab7525 100644 --- a/libsrc/hyperion/CMakeLists.txt +++ b/libsrc/hyperion/CMakeLists.txt @@ -13,7 +13,9 @@ add_library(hyperion ${CURRENT_SOURCE_DIR}/LedDeviceWs2801.cpp ${CURRENT_SOURCE_DIR}/LedString.cpp ${CURRENT_SOURCE_DIR}/Hyperion.cpp - ${CURRENT_SOURCE_DIR}/ImageToLedsMap.cpp + ${CURRENT_SOURCE_DIR}/ImageToLedsMap.cpp + ${CURRENT_SOURCE_DIR}/ColorTransform.h + ${CURRENT_SOURCE_DIR}/ColorTransform.cpp ) target_link_libraries(hyperion diff --git a/libsrc/hyperion/ColorTransform.cpp b/libsrc/hyperion/ColorTransform.cpp new file mode 100644 index 00000000..7a02fc0d --- /dev/null +++ b/libsrc/hyperion/ColorTransform.cpp @@ -0,0 +1,102 @@ +#include + +#include "ColorTransform.h" + +ColorTransform::ColorTransform() : + _threshold(0), + _gamma(1.0), + _blacklevel(0.0), + _whitelevel(1.0) +{ + initializeMapping(); +} + +ColorTransform::ColorTransform(double threshold, double gamma, double blacklevel, double whitelevel) : + _threshold(threshold), + _gamma(gamma), + _blacklevel(blacklevel), + _whitelevel(whitelevel) +{ + initializeMapping(); +} + +ColorTransform::~ColorTransform() +{ +} + +double ColorTransform::getThreshold() const +{ + return _threshold; +} + +void ColorTransform::setThreshold(double threshold) +{ + _threshold = threshold; + initializeMapping(); +} + +double ColorTransform::getGamma() const +{ + return _gamma; +} + +void ColorTransform::setGamma(double gamma) +{ + _gamma = gamma; + initializeMapping(); +} + +double ColorTransform::getBlacklevel() const +{ + return _blacklevel; +} + +void ColorTransform::setBlacklevel(double blacklevel) +{ + _blacklevel = blacklevel; + initializeMapping(); +} + +double ColorTransform::getWhitelevel() const +{ + return _whitelevel; +} + +void ColorTransform::setWhitelevel(double whitelevel) +{ + _whitelevel = whitelevel; + initializeMapping(); +} + +void ColorTransform::initializeMapping() +{ + // initialize the mapping as a linear array + for (int i = 0; i < 256; ++i) + { + double output = i / 255.0; + + // apply linear transform + if (output < _threshold) + { + output = 0.0; + } + + // apply gamma correction + output = std::pow(output, _gamma); + + // apply blacklevel and whitelevel + output = _blacklevel + (_whitelevel - _blacklevel) * output; + + // calc mapping + int mappingValue = output * 255; + if (mappingValue < 0) + { + mappingValue = 0; + } + else if (mappingValue > 255) + { + mappingValue = 255; + } + _mapping[i] = mappingValue; + } +} diff --git a/libsrc/hyperion/ColorTransform.h b/libsrc/hyperion/ColorTransform.h new file mode 100644 index 00000000..7a67e36e --- /dev/null +++ b/libsrc/hyperion/ColorTransform.h @@ -0,0 +1,49 @@ +#pragma once + +#include "stdint.h" + +/// Transform for a single color byte value +/// +/// Transforms are applied in the following order: +/// 1) a threshold is applied. All values below threshold will be set to zero +/// 2) gamma color correction is applied +/// 3) the output value is scaled from the [0:1] to [blacklevel:whitelevel] +/// 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 +{ +public: + ColorTransform(); + ColorTransform(double threshold, double gamma, double whitelevel, double blacklevel); + ~ColorTransform(); + + double getThreshold() const; + void setThreshold(double threshold); + + double getGamma() const; + void setGamma(double gamma); + + double getBlacklevel() const; + void setBlacklevel(double blacklevel); + + double getWhitelevel() const; + void setWhitelevel(double whitelevel); + + /// get the transformed value for the given byte value + uint8_t transform(uint8_t input) const + { + return _mapping[input]; + } + +private: + void initializeMapping(); + +private: + double _threshold; + double _gamma; + double _blacklevel; + double _whitelevel; + + uint8_t _mapping[256]; +}; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index aea05b4e..0a6e87b9 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,3 +1,5 @@ +# Needed for testing non-public components +include_directories(../libsrc) # Add the simple test executable 'TestSpi' add_executable(TestSpi @@ -25,15 +27,19 @@ add_executable(TestRgbImage target_link_libraries(TestRgbImage hyperion-utils) +add_executable(TestColorTransform + TestColorTransform.cpp) +target_link_libraries(TestColorTransform + hyperion) + # Find the libPNG find_package(PNG REQUIRED QUIET) -# Add additional includes dirs -include_directories(${PNG_INCLUDE_DIR}) - #if(PNG_FOUND) + # Add additional includes dirs + include_directories(${PNG_INCLUDE_DIR}) - add_executable(TestHyperionPng + add_executable(TestHyperionPng TestHyperionPng.cpp) target_link_libraries(TestHyperionPng diff --git a/test/TestColorTransform.cpp b/test/TestColorTransform.cpp new file mode 100644 index 00000000..376ac6bd --- /dev/null +++ b/test/TestColorTransform.cpp @@ -0,0 +1,93 @@ +#include +#include + +#include + +int main() +{ + { + std::cout << "Testing linear transform" << std::endl; + ColorTransform t; + for (int i = 0; i < 256; ++i) + { + uint8_t input = i; + uint8_t output = t.transform(input); + uint8_t expected = input; + + if (output != expected) + { + std::cerr << "ERROR: input (" << (int)input << ") => output (" << (int)output << ") : expected (" << (int) expected << ")" << std::endl; + return 1; + } + else + { + std::cerr << "OK: input (" << (int)input << ") => output (" << (int)output << ")" << std::endl; + } + } + } + + { + std::cout << "Testing threshold" << std::endl; + ColorTransform t(.10, 1.0, 0.0, 1.0); + for (int i = 0; i < 256; ++i) + { + uint8_t input = i; + uint8_t output = t.transform(input); + uint8_t expected = ((i/255.0) < t.getThreshold() ? 0 : output); + + if (output != expected) + { + std::cerr << "ERROR: input (" << (int)input << ") => output (" << (int)output << ") : expected (" << (int) expected << ")" << std::endl; + return 1; + } + else + { + std::cerr << "OK: input (" << (int)input << ") => output (" << (int)output << ")" << std::endl; + } + } + } + + { + std::cout << "Testing blacklevel and whitelevel" << std::endl; + ColorTransform t(0, 1.0, 0.2, 0.8); + for (int i = 0; i < 256; ++i) + { + uint8_t input = i; + uint8_t output = t.transform(input); + uint8_t expected = (uint8_t)(input * (t.getWhitelevel()-t.getBlacklevel()) + 255 * t.getBlacklevel()); + + if (output != expected) + { + std::cerr << "ERROR: input (" << (int)input << ") => output (" << (int)output << ") : expected (" << (int) expected << ")" << std::endl; + return 1; + } + else + { + std::cerr << "OK: input (" << (int)input << ") => output (" << (int)output << ")" << std::endl; + } + } + } + + { + std::cout << "Testing gamma" << std::endl; + ColorTransform t(0, 2.0, 0.0, 1.0); + for (int i = 0; i < 256; ++i) + { + uint8_t input = i; + uint8_t output = t.transform(input); + uint8_t expected = (uint8_t)(255 * std::pow(i / 255.0, 2)); + + if (output != expected) + { + std::cerr << "ERROR: input (" << (int)input << ") => output (" << (int)output << ") : expected (" << (int) expected << ")" << std::endl; + return 1; + } + else + { + std::cerr << "OK: input (" << (int)input << ") => output (" << (int)output << ")" << std::endl; + } + } + } + + return 0; +}