mirror of
https://github.com/hyperion-project/hyperion.ng.git
synced 2025-03-01 10:33:28 +00:00
Merge branch 'HEAD' of https://github.com/tvdzwan/hyperion.git
This commit is contained in:
@@ -21,6 +21,7 @@ SET(Hyperion_HEADERS
|
||||
${CURRENT_SOURCE_DIR}/BlackBorderProcessor.h
|
||||
${CURRENT_SOURCE_DIR}/BlackBorderDetector.h
|
||||
${CURRENT_SOURCE_DIR}/ColorTransform.h
|
||||
${CURRENT_SOURCE_DIR}/HsvTransform.h
|
||||
)
|
||||
|
||||
SET(Hyperion_SOURCES
|
||||
@@ -36,6 +37,7 @@ SET(Hyperion_SOURCES
|
||||
${CURRENT_SOURCE_DIR}/BlackBorderProcessor.cpp
|
||||
${CURRENT_SOURCE_DIR}/BlackBorderDetector.cpp
|
||||
${CURRENT_SOURCE_DIR}/ColorTransform.cpp
|
||||
${CURRENT_SOURCE_DIR}/HsvTransform.cpp
|
||||
)
|
||||
|
||||
set(Hyperion_RESOURCES
|
||||
|
134
libsrc/hyperion/HsvTransform.cpp
Normal file
134
libsrc/hyperion/HsvTransform.cpp
Normal file
@@ -0,0 +1,134 @@
|
||||
#include "HsvTransform.h"
|
||||
|
||||
using namespace hyperion;
|
||||
|
||||
HsvTransform::HsvTransform() :
|
||||
_saturationGain(1.0),
|
||||
_valueGain(1.0)
|
||||
{
|
||||
}
|
||||
|
||||
HsvTransform::HsvTransform(double saturationGain, double valueGain) :
|
||||
_saturationGain(saturationGain),
|
||||
_valueGain(valueGain)
|
||||
{
|
||||
}
|
||||
|
||||
HsvTransform::~HsvTransform()
|
||||
{
|
||||
}
|
||||
|
||||
void HsvTransform::setSaturationGain(double saturationGain)
|
||||
{
|
||||
_saturationGain = saturationGain;
|
||||
}
|
||||
|
||||
double HsvTransform::getSaturationGain() const
|
||||
{
|
||||
return _saturationGain;
|
||||
}
|
||||
|
||||
void HsvTransform::setValueGain(double valueGain)
|
||||
{
|
||||
_valueGain = valueGain;
|
||||
}
|
||||
|
||||
double HsvTransform::getValueGain() const
|
||||
{
|
||||
return _valueGain;
|
||||
}
|
||||
|
||||
void HsvTransform::transform(uint8_t & red, uint8_t & green, uint8_t & blue) const
|
||||
{
|
||||
if (_saturationGain != 1.0 || _valueGain != 1.0)
|
||||
{
|
||||
uint8_t hue, saturation, value;
|
||||
rgb2hsv(red, green, blue, hue, saturation, value);
|
||||
|
||||
int s = saturation * _saturationGain;
|
||||
if (s > 255)
|
||||
saturation = 255;
|
||||
else
|
||||
saturation = s;
|
||||
|
||||
int v = value * _valueGain;
|
||||
if (v > 255)
|
||||
value = 255;
|
||||
else
|
||||
value = v;
|
||||
|
||||
hsv2rgb(hue, saturation, value, red, green, blue);
|
||||
}
|
||||
}
|
||||
|
||||
void HsvTransform::rgb2hsv(uint8_t red, uint8_t green, uint8_t blue, uint8_t & hue, uint8_t & saturation, uint8_t & value)
|
||||
{
|
||||
uint8_t rgbMin, rgbMax;
|
||||
|
||||
rgbMin = red < green ? (red < blue ? red : blue) : (green < blue ? green : blue);
|
||||
rgbMax = red > green ? (red > blue ? red : blue) : (green > blue ? green : blue);
|
||||
|
||||
value = rgbMax;
|
||||
if (value == 0)
|
||||
{
|
||||
hue = 0;
|
||||
saturation = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
saturation = 255 * long(rgbMax - rgbMin) / value;
|
||||
if (saturation == 0)
|
||||
{
|
||||
hue = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (rgbMax == red)
|
||||
hue = 0 + 43 * (green - blue) / (rgbMax - rgbMin);
|
||||
else if (rgbMax == green)
|
||||
hue = 85 + 43 * (blue - red) / (rgbMax - rgbMin);
|
||||
else
|
||||
hue = 171 + 43 * (red - green) / (rgbMax - rgbMin);
|
||||
}
|
||||
|
||||
void HsvTransform::hsv2rgb(uint8_t hue, uint8_t saturation, uint8_t value, uint8_t & red, uint8_t & green, uint8_t & blue)
|
||||
{
|
||||
uint8_t region, remainder, p, q, t;
|
||||
|
||||
if (saturation == 0)
|
||||
{
|
||||
red = value;
|
||||
green = value;
|
||||
blue = value;
|
||||
return;
|
||||
}
|
||||
|
||||
region = hue / 43;
|
||||
remainder = (hue - (region * 43)) * 6;
|
||||
|
||||
p = (value * (255 - saturation)) >> 8;
|
||||
q = (value * (255 - ((saturation * remainder) >> 8))) >> 8;
|
||||
t = (value * (255 - ((saturation * (255 - remainder)) >> 8))) >> 8;
|
||||
|
||||
switch (region)
|
||||
{
|
||||
case 0:
|
||||
red = value; green = t; blue = p;
|
||||
break;
|
||||
case 1:
|
||||
red = q; green = value; blue = p;
|
||||
break;
|
||||
case 2:
|
||||
red = p; green = value; blue = t;
|
||||
break;
|
||||
case 3:
|
||||
red = p; green = q; blue = value;
|
||||
break;
|
||||
case 4:
|
||||
red = t; green = p; blue = value;
|
||||
break;
|
||||
default:
|
||||
red = value; green = p; blue = q;
|
||||
break;
|
||||
}
|
||||
}
|
33
libsrc/hyperion/HsvTransform.h
Normal file
33
libsrc/hyperion/HsvTransform.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace hyperion
|
||||
{
|
||||
|
||||
class HsvTransform
|
||||
{
|
||||
public:
|
||||
HsvTransform();
|
||||
HsvTransform(double saturationGain, double valueGain);
|
||||
~HsvTransform();
|
||||
|
||||
void setSaturationGain(double saturationGain);
|
||||
double getSaturationGain() const;
|
||||
|
||||
void setValueGain(double valueGain);
|
||||
double getValueGain() const;
|
||||
|
||||
void transform(uint8_t & red, uint8_t & green, uint8_t & blue) const;
|
||||
|
||||
private:
|
||||
// integer version of the conversion are faster, but a little less accurate
|
||||
static void rgb2hsv(uint8_t red, uint8_t green, uint8_t blue, uint8_t & hue, uint8_t & saturation, uint8_t & value);
|
||||
static void hsv2rgb(uint8_t hue, uint8_t saturation, uint8_t value, uint8_t & red, uint8_t & green, uint8_t & blue);
|
||||
|
||||
private:
|
||||
double _saturationGain;
|
||||
double _valueGain;
|
||||
};
|
||||
|
||||
} // namespace hyperion
|
@@ -14,6 +14,7 @@
|
||||
#include "LedDeviceWs2801.h"
|
||||
#include "LedDeviceTest.h"
|
||||
#include "ColorTransform.h"
|
||||
#include "HsvTransform.h"
|
||||
|
||||
using namespace hyperion;
|
||||
|
||||
@@ -44,6 +45,11 @@ LedDevice* constructDevice(const Json::Value& deviceConfig)
|
||||
return device;
|
||||
}
|
||||
|
||||
HsvTransform * createHsvTransform(const Json::Value & hsvConfig)
|
||||
{
|
||||
return new HsvTransform(hsvConfig["saturationGain"].asDouble(), hsvConfig["valueGain"].asDouble());
|
||||
}
|
||||
|
||||
ColorTransform* createColorTransform(const Json::Value& colorConfig)
|
||||
{
|
||||
const double threshold = colorConfig["threshold"].asDouble();
|
||||
@@ -102,74 +108,221 @@ Hyperion::Hyperion(const std::string& configFile) :
|
||||
}
|
||||
|
||||
Hyperion::Hyperion(const Json::Value &jsonConfig) :
|
||||
mLedString(createLedString(jsonConfig["leds"])),
|
||||
mRedTransform( createColorTransform(jsonConfig["color"]["red"])),
|
||||
mGreenTransform(createColorTransform(jsonConfig["color"]["green"])),
|
||||
mBlueTransform( createColorTransform(jsonConfig["color"]["blue"])),
|
||||
mDevice(constructDevice(jsonConfig["device"])),
|
||||
_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"])),
|
||||
_device(constructDevice(jsonConfig["device"])),
|
||||
_timer()
|
||||
{
|
||||
ImageProcessorFactory::getInstance().init(mLedString);
|
||||
ImageProcessorFactory::getInstance().init(_ledString);
|
||||
|
||||
_timer.setSingleShot(true);
|
||||
QObject::connect(&_timer, SIGNAL(timeout()), this, SLOT(update()));
|
||||
|
||||
// initialize the leds
|
||||
update();
|
||||
}
|
||||
|
||||
|
||||
Hyperion::~Hyperion()
|
||||
{
|
||||
// Delete the Led-String
|
||||
delete mDevice;
|
||||
delete _device;
|
||||
|
||||
// delete he hsv transform
|
||||
delete _hsvTransform;
|
||||
|
||||
// Delete the color-transform
|
||||
delete mBlueTransform;
|
||||
delete mGreenTransform;
|
||||
delete mRedTransform;
|
||||
delete _blueTransform;
|
||||
delete _greenTransform;
|
||||
delete _redTransform;
|
||||
}
|
||||
|
||||
unsigned Hyperion::getLedCount() const
|
||||
{
|
||||
return mLedString.leds().size();
|
||||
return _ledString.leds().size();
|
||||
}
|
||||
|
||||
void Hyperion::setValue(int priority, std::vector<RgbColor>& ledColors, const int timeout_ms)
|
||||
void Hyperion::setColor(int priority, RgbColor & color, const int timeout_ms)
|
||||
{
|
||||
// Apply the transform to each led and color-channel
|
||||
for (RgbColor& color : ledColors)
|
||||
{
|
||||
color.red = mRedTransform->transform(color.red);
|
||||
color.green = mGreenTransform->transform(color.green);
|
||||
color.blue = mBlueTransform->transform(color.blue);
|
||||
}
|
||||
// create led output
|
||||
std::vector<RgbColor> ledColors(_ledString.leds().size(), color);
|
||||
|
||||
// set colors
|
||||
setColors(priority, ledColors, timeout_ms);
|
||||
}
|
||||
|
||||
void Hyperion::setColors(int priority, std::vector<RgbColor>& ledColors, const int timeout_ms)
|
||||
{
|
||||
if (timeout_ms > 0)
|
||||
{
|
||||
const uint64_t timeoutTime = QDateTime::currentMSecsSinceEpoch() + timeout_ms;
|
||||
mMuxer.setInput(priority, ledColors, timeoutTime);
|
||||
_muxer.setInput(priority, ledColors, timeoutTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
mMuxer.setInput(priority, ledColors);
|
||||
_muxer.setInput(priority, ledColors);
|
||||
}
|
||||
|
||||
if (priority == mMuxer.getCurrentPriority())
|
||||
if (priority == _muxer.getCurrentPriority())
|
||||
{
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void Hyperion::setTransform(Hyperion::Transform transform, Hyperion::Color color, double value)
|
||||
{
|
||||
// select the transform of the requested color
|
||||
ColorTransform * t = nullptr;
|
||||
switch (color)
|
||||
{
|
||||
case RED:
|
||||
t = _redTransform;
|
||||
break;
|
||||
case GREEN:
|
||||
t = _greenTransform;
|
||||
break;
|
||||
case BLUE:
|
||||
t = _blueTransform;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// set transform value
|
||||
switch (transform)
|
||||
{
|
||||
case SATURATION_GAIN:
|
||||
_hsvTransform->setSaturationGain(value);
|
||||
break;
|
||||
case VALUE_GAIN:
|
||||
_hsvTransform->setValueGain(value);
|
||||
break;
|
||||
case THRESHOLD:
|
||||
assert (t != nullptr);
|
||||
t->setThreshold(value);
|
||||
break;
|
||||
case GAMMA:
|
||||
assert (t != nullptr);
|
||||
t->setGamma(value);
|
||||
break;
|
||||
case BLACKLEVEL:
|
||||
assert (t != nullptr);
|
||||
t->setBlacklevel(value);
|
||||
break;
|
||||
case WHITELEVEL:
|
||||
assert (t != nullptr);
|
||||
t->setWhitelevel(value);
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
// update the led output
|
||||
update();
|
||||
}
|
||||
|
||||
void Hyperion::clear(int priority)
|
||||
{
|
||||
if (_muxer.hasPriority(priority))
|
||||
{
|
||||
_muxer.clearInput(priority);
|
||||
|
||||
// update leds if necessary
|
||||
if (priority < _muxer.getCurrentPriority());
|
||||
{
|
||||
update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Hyperion::clearall()
|
||||
{
|
||||
_muxer.clearAll();
|
||||
|
||||
// update leds
|
||||
update();
|
||||
}
|
||||
|
||||
double Hyperion::getTransform(Hyperion::Transform transform, Hyperion::Color color) const
|
||||
{
|
||||
// select the transform of the requested color
|
||||
ColorTransform * t = nullptr;
|
||||
switch (color)
|
||||
{
|
||||
case RED:
|
||||
t = _redTransform;
|
||||
break;
|
||||
case GREEN:
|
||||
t = _greenTransform;
|
||||
break;
|
||||
case BLUE:
|
||||
t = _blueTransform;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// set transform value
|
||||
switch (transform)
|
||||
{
|
||||
case SATURATION_GAIN:
|
||||
return _hsvTransform->getSaturationGain();
|
||||
case VALUE_GAIN:
|
||||
return _hsvTransform->getValueGain();
|
||||
case THRESHOLD:
|
||||
assert (t != nullptr);
|
||||
return t->getThreshold();
|
||||
case GAMMA:
|
||||
assert (t != nullptr);
|
||||
return t->getGamma();
|
||||
case BLACKLEVEL:
|
||||
assert (t != nullptr);
|
||||
return t->getBlacklevel();
|
||||
case WHITELEVEL:
|
||||
assert (t != nullptr);
|
||||
return t->getWhitelevel();
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
return 999.0;
|
||||
}
|
||||
|
||||
QList<int> Hyperion::getActivePriorities() const
|
||||
{
|
||||
return _muxer.getPriorities();
|
||||
}
|
||||
|
||||
const Hyperion::InputInfo &Hyperion::getPriorityInfo(const int priority) const
|
||||
{
|
||||
return _muxer.getInputInfo(priority);
|
||||
}
|
||||
|
||||
void Hyperion::update()
|
||||
{
|
||||
// Update the muxer, cleaning obsolete priorities
|
||||
mMuxer.setCurrentTime(QDateTime::currentMSecsSinceEpoch());
|
||||
_muxer.setCurrentTime(QDateTime::currentMSecsSinceEpoch());
|
||||
|
||||
// Obtain the current priority channel
|
||||
int priority = mMuxer.getCurrentPriority();
|
||||
const PriorityMuxer::InputInfo & priorityInfo = mMuxer.getInputInfo(priority);
|
||||
int priority = _muxer.getCurrentPriority();
|
||||
const PriorityMuxer::InputInfo & priorityInfo = _muxer.getInputInfo(priority);
|
||||
|
||||
// Apply the transform to each led and color-channel
|
||||
std::vector<RgbColor> ledColors(priorityInfo.ledColors);
|
||||
for (RgbColor& color : ledColors)
|
||||
{
|
||||
_hsvTransform->transform(color.red, color.green, color.blue);
|
||||
color.red = _redTransform->transform(color.red);
|
||||
color.green = _greenTransform->transform(color.green);
|
||||
color.blue = _blueTransform->transform(color.blue);
|
||||
}
|
||||
|
||||
// Write the data to the device
|
||||
mDevice->write(priorityInfo.ledColors);
|
||||
_device->write(ledColors);
|
||||
|
||||
// Start the timeout-timer
|
||||
if (priorityInfo.timeoutTime_ms == -1)
|
||||
|
@@ -6,10 +6,14 @@
|
||||
// Hyperion includes
|
||||
#include <hyperion/PriorityMuxer.h>
|
||||
|
||||
PriorityMuxer::PriorityMuxer() :
|
||||
mCurrentPriority(MAX_PRIORITY)
|
||||
PriorityMuxer::PriorityMuxer(int ledCount) :
|
||||
_currentPriority(LOWEST_PRIORITY),
|
||||
_activeInputs(),
|
||||
_lowestPriorityInfo()
|
||||
{
|
||||
// empty
|
||||
_lowestPriorityInfo.priority = LOWEST_PRIORITY;
|
||||
_lowestPriorityInfo.timeoutTime_ms = -1;
|
||||
_lowestPriorityInfo.ledColors = std::vector<RgbColor>(ledCount, {0, 0, 0});
|
||||
}
|
||||
|
||||
PriorityMuxer::~PriorityMuxer()
|
||||
@@ -19,23 +23,28 @@ PriorityMuxer::~PriorityMuxer()
|
||||
|
||||
int PriorityMuxer::getCurrentPriority() const
|
||||
{
|
||||
return mCurrentPriority;
|
||||
return _currentPriority;
|
||||
}
|
||||
|
||||
QList<int> PriorityMuxer::getPriorities() const
|
||||
{
|
||||
return mActiveInputs.keys();
|
||||
return _activeInputs.keys();
|
||||
}
|
||||
|
||||
bool PriorityMuxer::hasPriority(const int priority) const
|
||||
{
|
||||
return mActiveInputs.contains(priority);
|
||||
return _activeInputs.contains(priority);
|
||||
}
|
||||
|
||||
const PriorityMuxer::InputInfo& PriorityMuxer::getInputInfo(const int priority) const
|
||||
{
|
||||
auto elemIt = mActiveInputs.find(priority);
|
||||
if (elemIt == mActiveInputs.end())
|
||||
if (priority == LOWEST_PRIORITY)
|
||||
{
|
||||
return _lowestPriorityInfo;
|
||||
}
|
||||
|
||||
auto elemIt = _activeInputs.find(priority);
|
||||
if (elemIt == _activeInputs.end())
|
||||
{
|
||||
throw std::runtime_error("no such priority");
|
||||
}
|
||||
@@ -44,50 +53,50 @@ const PriorityMuxer::InputInfo& PriorityMuxer::getInputInfo(const int priority)
|
||||
|
||||
void PriorityMuxer::setInput(const int priority, const std::vector<RgbColor>& ledColors, const int64_t timeoutTime_ms)
|
||||
{
|
||||
InputInfo& input = mActiveInputs[priority];
|
||||
InputInfo& input = _activeInputs[priority];
|
||||
input.priority = priority;
|
||||
input.timeoutTime_ms = timeoutTime_ms;
|
||||
input.ledColors = ledColors;
|
||||
|
||||
mCurrentPriority = std::min(mCurrentPriority, priority);
|
||||
_currentPriority = std::min(_currentPriority, priority);
|
||||
}
|
||||
|
||||
void PriorityMuxer::clearInput(const int priority)
|
||||
{
|
||||
mActiveInputs.remove(priority);
|
||||
if (mCurrentPriority == priority)
|
||||
_activeInputs.remove(priority);
|
||||
if (_currentPriority == priority)
|
||||
{
|
||||
if (mActiveInputs.empty())
|
||||
if (_activeInputs.empty())
|
||||
{
|
||||
mCurrentPriority = MAX_PRIORITY;
|
||||
_currentPriority = LOWEST_PRIORITY;
|
||||
}
|
||||
else
|
||||
{
|
||||
QList<int> keys = mActiveInputs.keys();
|
||||
mCurrentPriority = *std::min_element(keys.begin(), keys.end());
|
||||
QList<int> keys = _activeInputs.keys();
|
||||
_currentPriority = *std::min_element(keys.begin(), keys.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PriorityMuxer::clearAll()
|
||||
{
|
||||
mActiveInputs.clear();
|
||||
mCurrentPriority = MAX_PRIORITY;
|
||||
_activeInputs.clear();
|
||||
_currentPriority = LOWEST_PRIORITY;
|
||||
}
|
||||
|
||||
void PriorityMuxer::setCurrentTime(const int64_t& now)
|
||||
{
|
||||
mCurrentPriority = MAX_PRIORITY;
|
||||
_currentPriority = LOWEST_PRIORITY;
|
||||
|
||||
for (auto infoIt = mActiveInputs.begin(); infoIt != mActiveInputs.end();)
|
||||
for (auto infoIt = _activeInputs.begin(); infoIt != _activeInputs.end();)
|
||||
{
|
||||
if (infoIt->timeoutTime_ms != -1 && infoIt->timeoutTime_ms <= now)
|
||||
{
|
||||
infoIt = mActiveInputs.erase(infoIt);
|
||||
infoIt = _activeInputs.erase(infoIt);
|
||||
}
|
||||
else
|
||||
{
|
||||
mCurrentPriority = std::min(mCurrentPriority, infoIt->priority);
|
||||
_currentPriority = std::min(_currentPriority, infoIt->priority);
|
||||
++infoIt;
|
||||
}
|
||||
}
|
||||
|
@@ -33,6 +33,23 @@
|
||||
"type":"object",
|
||||
"required":true,
|
||||
"properties": {
|
||||
"hsv" : {
|
||||
"type" : "object",
|
||||
"required" : true,
|
||||
"properties" : {
|
||||
"saturationGain" : {
|
||||
"type" : "number",
|
||||
"required" : true,
|
||||
"minimum" : 0.0
|
||||
},
|
||||
"valueGain" : {
|
||||
"type" : "number",
|
||||
"required" : true,
|
||||
"minimum" : 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties" : false
|
||||
},
|
||||
"red": {
|
||||
"type":"object",
|
||||
"required":true,
|
||||
@@ -41,10 +58,6 @@
|
||||
"type":"number",
|
||||
"required":true
|
||||
},
|
||||
"adjust": {
|
||||
"type":"number",
|
||||
"required":true
|
||||
},
|
||||
"blacklevel": {
|
||||
"type":"number",
|
||||
"required":true
|
||||
@@ -55,7 +68,9 @@
|
||||
},
|
||||
"threshold": {
|
||||
"type":"number",
|
||||
"required":true
|
||||
"required":true,
|
||||
"minimum" : 0.0,
|
||||
"maximum" : 1.0
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -67,13 +82,19 @@
|
||||
"type":"number",
|
||||
"required":true
|
||||
},
|
||||
"adjust": {
|
||||
"type":"number",
|
||||
"required":true
|
||||
},
|
||||
"blacklevel": {
|
||||
"type":"number",
|
||||
"required":true
|
||||
},
|
||||
"whitelevel": {
|
||||
"type":"number",
|
||||
"required":true
|
||||
},
|
||||
"threshold": {
|
||||
"type":"number",
|
||||
"required":true,
|
||||
"minimum" : 0.0,
|
||||
"maximum" : 1.0
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -85,13 +106,19 @@
|
||||
"type":"number",
|
||||
"required":true
|
||||
},
|
||||
"adjust": {
|
||||
"whitelevel": {
|
||||
"type":"number",
|
||||
"required":true
|
||||
},
|
||||
"blacklevel": {
|
||||
"type":"number",
|
||||
"required":true
|
||||
},
|
||||
"threshold": {
|
||||
"type":"number",
|
||||
"required":true,
|
||||
"minimum" : 0.0,
|
||||
"maximum" : 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user