KDEAmbi/HyperionProcessor.cpp

237 lines
9.0 KiB
C++

#include "HyperionProcessor.h"
#include <QDebug>
#include <QtMath>
HyperionProcessor::HyperionProcessor(const LedLayout &layout, const QJsonObject &config, QObject *parent)
: QObject(parent),
_layout(layout),
_config(config),
_borderDetector(config.value("blackborderdetector").toObject()),
_lastImageSize(0, 0),
_colorSmoother(config.value("smoothing").toObject()),
_colorAlgorithm(config.value("colorAlgorithm").toString("mean_sqrt")),
_offset(config.value("offset").toInt(0)) // Default offset is 0
{
}
QVector<QColor> HyperionProcessor::process(const QImage &image)
{
qDebug() << "HyperionProcessor::process called.";
if (image.isNull()) {
return QVector<QColor>();
}
// 1. Detect black borders
BlackBorder currentBorder = _borderDetector.process(image);
// 2. Check if we need to rebuild the LED map
if (image.size() != _lastImageSize ||
currentBorder.horizontalSize != _lastBorder.horizontalSize ||
currentBorder.verticalSize != _lastBorder.verticalSize)
{
qDebug() << "Change detected. Rebuilding LED map.";
qDebug() << "New image size:" << image.size();
qDebug() << "New border - H:" << currentBorder.horizontalSize << "V:" << currentBorder.verticalSize;
buildLedMap(image.width(), image.height(), currentBorder);
_lastImageSize = image.size();
_lastBorder = currentBorder;
}
// 3. Calculate the colors
QVector<QColor> calculatedColors = calculateLedColors(image);
// 4. Apply smoothing
QVector<QColor> finalColors = _colorSmoother.smooth(calculatedColors);
// 5. Apply offset if needed
if (_offset != 0 && !finalColors.isEmpty()) {
QVector<QColor> shiftedColors(finalColors.size());
int totalLeds = finalColors.size();
for (int i = 0; i < totalLeds; ++i) {
shiftedColors[i] = finalColors[(i + _offset) % totalLeds];
}
return shiftedColors;
}
return finalColors;
}
void HyperionProcessor::buildLedMap(int imageWidth, int imageHeight, const BlackBorder &border)
{
_ledMap.clear();
int contentWidth = imageWidth - (2 * border.verticalSize);
int contentHeight = imageHeight - (2 * border.horizontalSize);
qDebug() << "buildLedMap: imageWidth=" << imageWidth << "imageHeight=" << imageHeight;
qDebug() << "buildLedMap: border.verticalSize=" << border.verticalSize << "border.horizontalSize=" << border.horizontalSize;
qDebug() << "buildLedMap: contentWidth=" << contentWidth << "contentHeight=" << contentHeight;
if (contentWidth <= 0 || contentHeight <= 0) {
qWarning() << "No content to process after black border removal. Content W:" << contentWidth << "H:" << contentHeight;
return;
}
int totalLeds = _layout.left + _layout.right + _layout.top + _layout.bottom;
qDebug() << "buildLedMap: totalLeds=" << totalLeds;
_ledMap.resize(totalLeds);
// Define sampling thickness (e.g., 5% of the content height/width, minimum 1 pixel)
int hSamplingThickness = qMax(1, contentHeight / 20); // 5% of content height
int vSamplingThickness = qMax(1, contentWidth / 20); // 5% of content width
int ledIndex = 0;
// Bottom LEDs
for (int i = 0; i < _layout.bottom; ++i, ++ledIndex) {
double hScanStart = double(i) / _layout.bottom;
double hScanEnd = double(i + 1) / _layout.bottom;
int xStart = border.verticalSize + int(hScanStart * contentWidth);
int xEnd = border.verticalSize + int(hScanEnd * contentWidth);
int yStart = border.horizontalSize + contentHeight - hSamplingThickness; // Sample from bottom region
int yEnd = border.horizontalSize + contentHeight; // To the very bottom
qDebug() << "LED" << ledIndex << ": xStart=" << xStart << "xEnd=" << xEnd << "yStart=" << yStart << "yEnd=" << yEnd;
for (int x = xStart; x < xEnd; ++x) {
for (int y = yStart; y < yEnd; ++y) {
_ledMap[ledIndex].append(QPoint(x, y));
}
}
}
// Right LEDs
for (int i = 0; i < _layout.right; ++i, ++ledIndex) {
double vScanStart = double(i) / _layout.right;
double vScanEnd = double(i + 1) / _layout.right;
int yStart = border.horizontalSize + int((1.0 - vScanEnd) * contentHeight); // Sample from top of region
int yEnd = border.horizontalSize + int((1.0 - vScanStart) * contentHeight); // To bottom of region
int xStart = border.verticalSize + contentWidth - vSamplingThickness; // Sample from rightmost region
int xEnd = border.verticalSize + contentWidth; // To the very right
qDebug() << "LED" << ledIndex << ": xStart=" << xStart << "xEnd=" << xEnd << "yStart=" << yStart << "yEnd=" << yEnd;
for (int x = xStart; x < xEnd; ++x) {
for (int y = yStart; y < yEnd; ++y) {
_ledMap[ledIndex].append(QPoint(x, y));
}
}
}
// Top LEDs
for (int i = 0; i < _layout.top; ++i, ++ledIndex) {
double hScanStart = double(i) / _layout.top;
double hScanEnd = double(i + 1) / _layout.top;
int xStart = border.verticalSize + int((1.0 - hScanEnd) * contentWidth); // Sample from right of region
int xEnd = border.verticalSize + int((1.0 - hScanStart) * contentWidth); // To left of region
int yStart = border.horizontalSize; // Sample from top region
int yEnd = border.horizontalSize + hSamplingThickness; // To bottom of region
qDebug() << "LED" << ledIndex << ": xStart=" << xStart << "xEnd=" << xEnd << "yStart=" << yStart << "yEnd=" << yEnd;
for (int x = xStart; x < xEnd; ++x) {
for (int y = yStart; y < yEnd; ++y) {
_ledMap[ledIndex].append(QPoint(x, y));
}
}
}
// Left LEDs
for (int i = 0; i < _layout.left; ++i, ++ledIndex) {
double vScanStart = double(i) / _layout.left;
double vScanEnd = double(i + 1) / _layout.left;
int yStart = border.horizontalSize + int(vScanStart * contentHeight); // Sample from top of region
int yEnd = border.horizontalSize + int(vScanEnd * contentHeight); // To bottom of region
int xStart = border.verticalSize; // Sample leftmost region
int xEnd = border.verticalSize + vSamplingThickness; // To right of region
qDebug() << "LED" << ledIndex << ": xStart=" << xStart << "xEnd=" << xEnd << "yStart=" << yStart << "yEnd=" << yEnd;
for (int x = xStart; x < xEnd; ++x) {
for (int y = yStart; y < yEnd; ++y) {
_ledMap[ledIndex].append(QPoint(x, y));
}
}
}
}
QVector<QColor> HyperionProcessor::calculateLedColors(const QImage &image)
{
qDebug() << "HyperionProcessor::calculateLedColors called.";
QVector<QColor> ledColors;
if (_ledMap.isEmpty()) {
return ledColors;
}
ledColors.reserve(_ledMap.size());
for (const auto &pixelList : _ledMap) {
if (_colorAlgorithm == "mean_sqrt") {
ledColors.append(getMeanSqrtLedColor(image, pixelList));
} else if (_colorAlgorithm == "mean") {
ledColors.append(getAverageColor(image, pixelList));
} else if (_colorAlgorithm == "max") {
ledColors.append(getMaxColor(image, pixelList));
} else {
// Fallback to mean_sqrt if algorithm is unknown
ledColors.append(getMeanSqrtLedColor(image, pixelList));
}
}
return ledColors;
}
QColor HyperionProcessor::getAverageColor(const QImage &image, const QVector<QPoint> &pixels) const
{
if (pixels.isEmpty()) {
return Qt::black;
}
quint64 r = 0, g = 0, b = 0;
for (const QPoint &pt : pixels) {
QColor color = image.pixelColor(pt);
r += color.red();
g += color.green();
b += color.blue();
}
return QColor(r / pixels.size(), g / pixels.size(), b / pixels.size());
}
QColor HyperionProcessor::getMeanSqrtLedColor(const QImage &image, const QVector<QPoint> &pixels) const
{
if (pixels.isEmpty()) {
return Qt::black;
}
quint64 r_sq = 0, g_sq = 0, b_sq = 0;
for (const QPoint &pt : pixels) {
QColor color = image.pixelColor(pt);
r_sq += color.red() * color.red();
g_sq += color.green() * color.green();
b_sq += color.blue() * color.blue();
}
return QColor(static_cast<int>(qSqrt(static_cast<double>(r_sq / pixels.size()))),
static_cast<int>(qSqrt(static_cast<double>(g_sq / pixels.size()))),
static_cast<int>(qSqrt(static_cast<double>(b_sq / pixels.size()))));
}
QColor HyperionProcessor::getMaxColor(const QImage &image, const QVector<QPoint> &pixels) const
{
if (pixels.isEmpty()) {
return Qt::black;
}
int maxR = 0, maxG = 0, maxB = 0;
for (const QPoint &pt : pixels) {
QColor color = image.pixelColor(pt);
if (color.red() > maxR) maxR = color.red();
if (color.green() > maxG) maxG = color.green();
if (color.blue() > maxB) maxB = color.blue();
}
return QColor(maxR, maxG, maxB);
}