mirror of
https://github.com/hyperion-project/hyperion.ng.git
synced 2023-10-10 13:36:59 +02:00
v4l: make suggestions for all no signal values (#339)
* on v4l screenshot, print out nosignal threshold values * separate fractional parameters for no signal detection * fully implement handling for "rainbow grabber" * hyperion-v4l2 --screenshot can also evaluate best reagion and color for no signal detection * tune output of no signal detection * v4l outputs warninsg only, todo: make it adyustable * typo * v4l signal detection, now with validation checks * remove obsolete schema files * fix code style * fix text typos * code style
This commit is contained in:
parent
8aa0fbaa1e
commit
23194730c2
@ -7,8 +7,6 @@
|
||||
<file alias="schema-clear">schema/schema-clear.json</file>
|
||||
<file alias="schema-clearall">schema/schema-clearall.json</file>
|
||||
<file alias="schema-transform">schema/schema-transform.json</file>
|
||||
<file alias="schema-correction">schema/schema-correction.json</file>
|
||||
<file alias="schema-temperature">schema/schema-temperature.json</file>
|
||||
<file alias="schema-adjustment">schema/schema-adjustment.json</file>
|
||||
<file alias="schema-effect">schema/schema-effect.json</file>
|
||||
<file alias="schema-create-effect">schema/schema-create-effect.json</file>
|
||||
|
@ -1,37 +0,0 @@
|
||||
{
|
||||
"type":"object",
|
||||
"required":true,
|
||||
"properties":{
|
||||
"command": {
|
||||
"type" : "string",
|
||||
"required" : true,
|
||||
"enum" : ["correction"]
|
||||
},
|
||||
"tan" : {
|
||||
"type" : "integer"
|
||||
},
|
||||
"correction": {
|
||||
"type": "object",
|
||||
"required": true,
|
||||
"properties": {
|
||||
"id" : {
|
||||
"type" : "string",
|
||||
"required" : false
|
||||
},
|
||||
"correctionValues" : {
|
||||
"type": "array",
|
||||
"required": false,
|
||||
"items" : {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 255
|
||||
},
|
||||
"minItems": 3,
|
||||
"maxItems": 3
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
{
|
||||
"type":"object",
|
||||
"required":true,
|
||||
"properties":{
|
||||
"command": {
|
||||
"type" : "string",
|
||||
"required" : true,
|
||||
"enum" : ["temperature"]
|
||||
},
|
||||
"tan" : {
|
||||
"type" : "integer"
|
||||
},
|
||||
"temperature": {
|
||||
"type": "object",
|
||||
"required": true,
|
||||
"properties": {
|
||||
"id" : {
|
||||
"type" : "string",
|
||||
"required" : false
|
||||
},
|
||||
"correctionValues" : {
|
||||
"type": "array",
|
||||
"required": false,
|
||||
"items" : {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 255
|
||||
},
|
||||
"minItems": 3,
|
||||
"maxItems": 3
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
@ -5,7 +5,7 @@
|
||||
"command": {
|
||||
"type" : "string",
|
||||
"required" : true,
|
||||
"enum" : ["color", "image", "effect", "create-effect", "delete-effect", "serverinfo", "clear", "clearall", "transform", "correction", "temperature", "adjustment", "sourceselect", "config", "componentstate", "ledcolors", "logging"]
|
||||
"enum" : ["color", "image", "effect", "create-effect", "delete-effect", "serverinfo", "clear", "clearall", "transform", "adjustment", "sourceselect", "config", "componentstate", "ledcolors", "logging"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,10 @@
|
||||
#include <cmath>
|
||||
#include <utils/HslTransform.h>
|
||||
|
||||
HslTransform::HslTransform() :
|
||||
_saturationGain(1.0),
|
||||
_luminanceGain(1.0),
|
||||
_luminanceMinimum(0.0)
|
||||
HslTransform::HslTransform()
|
||||
: _saturationGain(1.0)
|
||||
, _luminanceGain(1.0)
|
||||
, _luminanceMinimum(0.0)
|
||||
{
|
||||
}
|
||||
|
||||
@ -59,10 +59,7 @@ void HslTransform::transform(uint8_t & red, uint8_t & green, uint8_t & blue) con
|
||||
rgb2hsl(red, green, blue, hue, saturation, luminance);
|
||||
|
||||
float s = saturation * _saturationGain;
|
||||
if (s > 1.0f)
|
||||
saturation = 1.0f;
|
||||
else
|
||||
saturation = s;
|
||||
saturation = std::min(s, 1.0f);
|
||||
|
||||
float l = luminance * _luminanceGain;
|
||||
if (l < _luminanceMinimum)
|
||||
@ -70,10 +67,7 @@ void HslTransform::transform(uint8_t & red, uint8_t & green, uint8_t & blue) con
|
||||
saturation = 0;
|
||||
l = _luminanceMinimum;
|
||||
}
|
||||
if (l > 1.0f)
|
||||
luminance = 1.0f;
|
||||
else
|
||||
luminance = l;
|
||||
luminance = std::min(l, 1.0f);
|
||||
|
||||
hsl2rgb(hue, saturation, luminance, red, green, blue);
|
||||
}
|
||||
@ -81,28 +75,28 @@ void HslTransform::transform(uint8_t & red, uint8_t & green, uint8_t & blue) con
|
||||
|
||||
void HslTransform::rgb2hsl(uint8_t red, uint8_t green, uint8_t blue, uint16_t & hue, float & saturation, float & luminance)
|
||||
{
|
||||
float r = red / 255.0f;
|
||||
float g = green / 255.0f;
|
||||
float b = blue / 255.0f;
|
||||
float r = (float)red / 255.0f;
|
||||
float g = (float)green / 255.0f;
|
||||
float b = (float)blue / 255.0f;
|
||||
|
||||
float rgbMin = r < g ? (r < b ? r : b) : (g < b ? g : b);
|
||||
float rgbMax = r > g ? (r > b ? r : b) : (g > b ? g : b);
|
||||
float rgbMin = std::min(r,std::min(g,b));
|
||||
float rgbMax = std::max(r,std::max(g,b));
|
||||
float diff = rgbMax - rgbMin;
|
||||
|
||||
//luminance
|
||||
luminance = (rgbMin + rgbMax) / 2.0f;
|
||||
|
||||
if (diff == 0.0f) {
|
||||
if (diff == 0.0f)
|
||||
{
|
||||
saturation = 0.0f;
|
||||
hue = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
//saturation
|
||||
if (luminance < 0.5f)
|
||||
saturation = diff / (rgbMin + rgbMax);
|
||||
else
|
||||
saturation = diff / (2.0f - rgbMin - rgbMax);
|
||||
saturation = (luminance < 0.5f)
|
||||
? (diff / (rgbMin + rgbMax))
|
||||
: (diff / (2.0f - rgbMin - rgbMax));
|
||||
|
||||
if (rgbMax == r)
|
||||
{
|
||||
@ -125,19 +119,17 @@ void HslTransform::rgb2hsl(uint8_t red, uint8_t green, uint8_t blue, uint16_t &
|
||||
|
||||
void HslTransform::hsl2rgb(uint16_t hue, float saturation, float luminance, uint8_t & red, uint8_t & green, uint8_t & blue)
|
||||
{
|
||||
if (saturation == 0.0f){
|
||||
if (saturation == 0.0f)
|
||||
{
|
||||
red = (uint8_t)(luminance * 255.0f);
|
||||
green = (uint8_t)(luminance * 255.0f);
|
||||
blue = (uint8_t)(luminance * 255.0f);
|
||||
return;
|
||||
}
|
||||
|
||||
float q;
|
||||
|
||||
if (luminance < 0.5f)
|
||||
q = luminance * (1.0f + saturation);
|
||||
else
|
||||
q = (luminance + saturation) - (luminance * saturation);
|
||||
float q = (luminance < 0.5f)
|
||||
? luminance * (1.0f + saturation)
|
||||
: (luminance + saturation) - (luminance * saturation);
|
||||
|
||||
float p = (2.0f * luminance) - q;
|
||||
float h = hue / 360.0f;
|
||||
@ -148,7 +140,8 @@ void HslTransform::hsl2rgb(uint16_t hue, float saturation, float luminance, uint
|
||||
t[1] = h;
|
||||
t[2] = h - (1.0f / 3.0f);
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
if (t[i] < 0.0f)
|
||||
t[i] += 1.0f;
|
||||
if (t[i] > 1.0f)
|
||||
@ -157,7 +150,8 @@ void HslTransform::hsl2rgb(uint16_t hue, float saturation, float luminance, uint
|
||||
|
||||
float out[3];
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
if (t[i] * 6.0f < 1.0f)
|
||||
out[i] = p + (q - p) * 6.0f * t[i];
|
||||
else if (t[i] * 2.0f < 1.0f)
|
||||
|
@ -1,9 +1,9 @@
|
||||
#include <iostream>
|
||||
#include <utils/HsvTransform.h>
|
||||
|
||||
HsvTransform::HsvTransform() :
|
||||
_saturationGain(1.0),
|
||||
_valueGain(1.0)
|
||||
HsvTransform::HsvTransform()
|
||||
: _saturationGain(1.0)
|
||||
, _valueGain(1.0)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
// Qt includes
|
||||
#include <QImage>
|
||||
#include <QCoreApplication>
|
||||
#include <QVector>
|
||||
#include <algorithm>
|
||||
|
||||
// hyperion-v4l2 includes
|
||||
#include "ScreenshotHandler.h"
|
||||
@ -17,41 +19,7 @@ ScreenshotHandler::~ScreenshotHandler()
|
||||
|
||||
void ScreenshotHandler::receiveImage(const Image<ColorRgb> & image)
|
||||
{
|
||||
double x_frac_min = _signalDetectionOffset.x();
|
||||
double y_frac_min = _signalDetectionOffset.y();
|
||||
double x_frac_max = _signalDetectionOffset.width();
|
||||
double y_frac_max = _signalDetectionOffset.height();
|
||||
|
||||
int xOffset = image.width() * x_frac_min;
|
||||
int yOffset = image.height() * y_frac_min;
|
||||
int xMax = image.width() * x_frac_max;
|
||||
int yMax = image.height() * y_frac_max;
|
||||
|
||||
std::cout << std::endl << "Screenshot details"
|
||||
<< std::endl << "=================="
|
||||
<< std::endl << "dimension after decimation: " << image.width() << " x " << image.height()
|
||||
<< std::endl << "signal detection area : " << xOffset << "," << yOffset << " x " << xMax << "," << yMax << std::endl;
|
||||
|
||||
ColorRgb noSignalThresholdColor = {0,0,0};
|
||||
|
||||
for (unsigned x = 0; x < (image.width()>>1); ++x)
|
||||
{
|
||||
int xImage = (image.width()>>2) + x;
|
||||
|
||||
for (unsigned y = 0; y < (image.height()>>1); ++y)
|
||||
{
|
||||
int yImage = (image.height()>>2) + y;
|
||||
|
||||
ColorRgb rgb = image(xImage, yImage);
|
||||
if (rgb > noSignalThresholdColor)
|
||||
{
|
||||
noSignalThresholdColor = rgb;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::cout << "signal threshold color : " << noSignalThresholdColor << std::endl;
|
||||
std::cout << "signal threshold values: " << (float)noSignalThresholdColor.red/255.0f << ", "<< (float)noSignalThresholdColor.green/255.0f << ", " << (float)noSignalThresholdColor.blue/255.0f << std::endl;
|
||||
|
||||
findNoSignalSettings(image);
|
||||
// store as PNG
|
||||
QImage pngImage((const uint8_t *) image.memptr(), image.width(), image.height(), 3*image.width(), QImage::Format_RGB888);
|
||||
pngImage.save(_filename);
|
||||
@ -59,3 +27,220 @@ void ScreenshotHandler::receiveImage(const Image<ColorRgb> & image)
|
||||
// Quit the application after the first image
|
||||
QCoreApplication::quit();
|
||||
}
|
||||
|
||||
bool ScreenshotHandler::findNoSignalSettings(const Image<ColorRgb> & image)
|
||||
{
|
||||
double x_frac_min = _signalDetectionOffset.x();
|
||||
double y_frac_min = _signalDetectionOffset.y();
|
||||
double x_frac_max = _signalDetectionOffset.width();
|
||||
double y_frac_max = _signalDetectionOffset.height();
|
||||
|
||||
unsigned xOffset = image.width() * x_frac_min;
|
||||
unsigned yOffset = image.height() * y_frac_min;
|
||||
unsigned xMax = image.width() * x_frac_max;
|
||||
unsigned yMax = image.height() * y_frac_max;
|
||||
|
||||
ColorRgb noSignalThresholdColor = {0,0,0};
|
||||
|
||||
unsigned yMid = (yMax+yOffset) / 2;
|
||||
ColorRgb redThresoldColor = {255,75,75};
|
||||
ColorRgb greenThresoldColor = {75,255,75};
|
||||
ColorRgb blueThresoldColor = {75,75,255};
|
||||
|
||||
QVector<unsigned> redOffsets;
|
||||
QVector<unsigned> redCounts;
|
||||
QVector<unsigned> greenOffsets;
|
||||
QVector<unsigned> greenCounts;
|
||||
QVector<unsigned> blueOffsets;
|
||||
QVector<unsigned> blueCounts;
|
||||
|
||||
unsigned currentColor = 255;
|
||||
for (unsigned x = xOffset; x < xMax; ++x)
|
||||
{
|
||||
ColorRgb rgb = image(x, yMid);
|
||||
if (rgb <= redThresoldColor)
|
||||
{
|
||||
if ( currentColor != 0)
|
||||
{
|
||||
redOffsets.append(x);
|
||||
redCounts.append(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
redCounts[redCounts.size()-1]++;
|
||||
}
|
||||
currentColor = 0;
|
||||
}
|
||||
if (rgb <= greenThresoldColor){
|
||||
if ( currentColor != 1)
|
||||
{
|
||||
greenOffsets.append(x);
|
||||
greenCounts.append(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
greenCounts[greenCounts.size()-1]++;
|
||||
}
|
||||
currentColor = 1;
|
||||
}
|
||||
if (rgb <= blueThresoldColor)
|
||||
{
|
||||
if ( currentColor != 2)
|
||||
{
|
||||
blueOffsets.append(x);
|
||||
blueCounts.append(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
blueCounts[blueCounts.size()-1]++;
|
||||
}
|
||||
currentColor = 2;
|
||||
}
|
||||
}
|
||||
|
||||
auto itR = std::max_element(std::begin(redCounts), std::end(redCounts));
|
||||
auto itG = std::max_element(std::begin(greenCounts), std::end(greenCounts));
|
||||
auto itB = std::max_element(std::begin(blueCounts), std::end(blueCounts));
|
||||
|
||||
//std::cout << *itR << " " << *itG << " " << *itB << std::endl;
|
||||
double xOffsetSuggested = xOffset;
|
||||
double yOffsetSuggested = yOffset;
|
||||
double xMaxSuggested = xMax;
|
||||
double yMaxSuggested = yMax;
|
||||
bool noSignalBlack = false;
|
||||
|
||||
noSignalThresholdColor = {0,0,0};
|
||||
if (*itR >= *itG && *itR >= *itB && *itR > 1)
|
||||
{
|
||||
xOffsetSuggested = redOffsets[redCounts.indexOf(*itR)];
|
||||
xMaxSuggested = xOffsetSuggested + *itR;
|
||||
noSignalThresholdColor = redThresoldColor;
|
||||
}
|
||||
else if (*itG >= *itR && *itG >= *itB && *itG > 1 )
|
||||
{
|
||||
xOffsetSuggested = greenOffsets[greenCounts.indexOf(*itG)];
|
||||
xMaxSuggested = xOffsetSuggested + *itG;
|
||||
noSignalThresholdColor = greenThresoldColor;
|
||||
}
|
||||
else if ( *itB > 1 )
|
||||
{
|
||||
xOffsetSuggested = blueOffsets[blueCounts.indexOf(*itB)];
|
||||
xMaxSuggested = xOffsetSuggested + *itB;
|
||||
noSignalThresholdColor = blueThresoldColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
noSignalThresholdColor = {75,75,75};
|
||||
noSignalBlack = true;
|
||||
}
|
||||
|
||||
// serach vertical max
|
||||
if (!noSignalBlack)
|
||||
{
|
||||
unsigned xMid = (xMaxSuggested + xOffsetSuggested) / 2;
|
||||
for (unsigned y = yMid; y >= yOffset && yOffsetSuggested != y; --y)
|
||||
{
|
||||
ColorRgb rgb = image(xMid, y);
|
||||
if (rgb <= noSignalThresholdColor)
|
||||
{
|
||||
yOffsetSuggested = y;
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned y = yMid; y <= yMax && yMaxSuggested != y; ++y)
|
||||
{
|
||||
ColorRgb rgb = image(xMid, y);
|
||||
if (rgb <= noSignalThresholdColor)
|
||||
{
|
||||
yMaxSuggested = y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// optimize thresold color
|
||||
noSignalThresholdColor = {0,0,0};
|
||||
for (unsigned x = xOffsetSuggested; x < xMaxSuggested; ++x)
|
||||
{
|
||||
for (unsigned y = yOffsetSuggested; y < yMaxSuggested; ++y)
|
||||
{
|
||||
ColorRgb rgb = image(x, y);
|
||||
if (rgb >= noSignalThresholdColor)
|
||||
{
|
||||
noSignalThresholdColor = rgb;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// calculate fractional values
|
||||
xOffsetSuggested = (int)(((float)xOffsetSuggested/image.width())*100+0.5)/100.0;
|
||||
xMaxSuggested = (int)(((float)xMaxSuggested/image.width())*100)/100.0;
|
||||
yOffsetSuggested = (int)(((float)yOffsetSuggested/image.height())*100+0.5)/100.0;
|
||||
yMaxSuggested = (int)(((float)yMaxSuggested/image.height())*100)/100.0;
|
||||
double thresholdRed = (int)(((float)noSignalThresholdColor.red/255.0f)*100+0.5)/100.0;
|
||||
double thresholdGreen = (int)(((float)noSignalThresholdColor.green/255.0f)*100+0.5)/100.0;
|
||||
double thresholdBlue = (int)(((float)noSignalThresholdColor.blue/255.0f)*100+0.5)/100.0;
|
||||
thresholdRed = (thresholdRed<0.1f) ?0.1f : thresholdRed;
|
||||
thresholdGreen = (thresholdGreen<0.1f)?0.1f : thresholdGreen;
|
||||
thresholdBlue = (thresholdBlue<0.1f) ?0.1f : thresholdBlue;
|
||||
|
||||
std::cout << std::endl << "Signal detection informations"
|
||||
<< std::endl << "============================="
|
||||
<< std::endl << "dimension after decimation: " << image.width() << " x " << image.height()
|
||||
<< std::endl << "signal detection area : " << xOffset << "," << yOffset << " x " << xMax << "," << yMax << std::endl << std::endl;
|
||||
|
||||
// check if values make sense
|
||||
if (thresholdRed < 0.5 && thresholdGreen < 0.5 && thresholdBlue < 0.5 && thresholdRed > 0.15 && thresholdGreen > 0.15 && thresholdBlue > 0.15)
|
||||
{
|
||||
std::cout << "WARNING \"no signal image\" is to dark, signal detection is not relaiable." << std::endl;
|
||||
}
|
||||
|
||||
if (thresholdRed > 0.5 && thresholdGreen > 0.5 && thresholdBlue > 0.5)
|
||||
{
|
||||
std::cout << "WARNING \"no signal image\" is to bright, signal detection is not relaiable." << std::endl;
|
||||
}
|
||||
|
||||
if (thresholdRed > thresholdGreen && thresholdRed > thresholdBlue && ((thresholdRed-thresholdGreen) <= 0.5 || (thresholdRed-thresholdBlue) <= 0.5))
|
||||
{
|
||||
std::cout << "WARNING difference between threshold color and the other color components is to small, signal detection might have problems." << std::endl;
|
||||
}
|
||||
|
||||
if (thresholdGreen > thresholdRed && thresholdGreen > thresholdBlue && ((thresholdGreen-thresholdRed) <= 0.5 || (thresholdGreen-thresholdBlue) <= 0.5))
|
||||
{
|
||||
std::cout << "WARNING difference between threshold color and the other color components is to small, signal detection might have problems." << std::endl;
|
||||
}
|
||||
|
||||
if (thresholdBlue > thresholdGreen && thresholdBlue > thresholdRed && ((thresholdBlue-thresholdGreen) <= 0.5 || (thresholdBlue-thresholdRed) <= 0.5))
|
||||
{
|
||||
std::cout << "WARNING difference between threshold color and the other color components is to small, signal detection might have problems." << std::endl;
|
||||
}
|
||||
|
||||
if (noSignalBlack)
|
||||
{
|
||||
std::cout << "WARNING no red, green or blue \"no signal area\" detected, signal detection might have problems." << std::endl;
|
||||
}
|
||||
|
||||
if (xOffsetSuggested >= xMaxSuggested || (xMaxSuggested - xOffsetSuggested) < 0.029 )
|
||||
{
|
||||
std::cout << "WARNING horizontal values of signal detection are invalid or detection area is to small, signal detection is not relaiable." << std::endl;
|
||||
}
|
||||
|
||||
if (yOffsetSuggested >= yMaxSuggested || (yMaxSuggested - yOffsetSuggested) < 0.029 )
|
||||
{
|
||||
std::cout << "WARNING horizontal values of signal detection are invalid or detection area is to small, signal detection is not relaiable." << std::endl;
|
||||
}
|
||||
|
||||
std::cout << std::endl
|
||||
<< "suggested config values for signal detection:" << std::endl
|
||||
<< "\t\"redSignalThreshold\" : " << thresholdRed << "," << std::endl
|
||||
<< "\t\"greenSignalThreshold\" : " << thresholdGreen << "," << std::endl
|
||||
<< "\t\"blueSignalThreshold\" : " << thresholdBlue << "," << std::endl
|
||||
<< "\t\"signalDetectionHorizontalOffsetMin\" : " << xOffsetSuggested << "," << std::endl
|
||||
<< "\t\"signalDetectionVerticalOffsetMin\" : " << yOffsetSuggested << "," << std::endl
|
||||
<< "\t\"signalDetectionHorizontalOffsetMax\" : " << xMaxSuggested << "," << std::endl
|
||||
<< "\t\"signalDetectionVerticalOffsetMax\" : " << yMaxSuggested << std::endl;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -21,6 +21,8 @@ public slots:
|
||||
void receiveImage(const Image<ColorRgb> & image);
|
||||
|
||||
private:
|
||||
bool findNoSignalSettings(const Image<ColorRgb> & image);
|
||||
|
||||
const QString _filename;
|
||||
const QRectF _signalDetectionOffset;
|
||||
};
|
||||
|
@ -35,6 +35,8 @@ void saveScreenshot(QString filename, const Image<ColorRgb> & image)
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
Logger *log = Logger::getInstance("V4L2GRABBER");
|
||||
Logger::setLogLevel(Logger::WARNING);
|
||||
|
||||
std::cout
|
||||
<< "hyperion-v4l2:" << std::endl
|
||||
<< "\tVersion : " << HYPERION_VERSION << " (" << HYPERION_BUILD_ID << ")" << std::endl
|
||||
|
Loading…
Reference in New Issue
Block a user