• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mcallegari / qlcplus / 18357067171

08 Oct 2025 08:16PM UTC coverage: 34.26% (+2.2%) from 32.066%
18357067171

push

github

mcallegari
Merge branch 'master' into filedialog

1282 of 4424 new or added lines in 152 files covered. (28.98%)

1342 existing lines in 152 files now uncovered.

17704 of 51675 relevant lines covered (34.26%)

19430.31 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

41.67
/engine/src/rgbmatrix.cpp
1
/*
2
  Q Light Controller Plus
3
  rgbmatrix.cpp
4

5
  Copyright (c) Heikki Junnila
6
                Massimo Callegari
7

8
  Licensed under the Apache License, Version 2.0 (the "License");
9
  you may not use this file except in compliance with the License.
10
  You may obtain a copy of the License at
11

12
      http://www.apache.org/licenses/LICENSE-2.0.txt
13

14
  Unless required by applicable law or agreed to in writing, software
15
  distributed under the License is distributed on an "AS IS" BASIS,
16
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
  See the License for the specific language governing permissions and
18
  limitations under the License.
19
*/
20

21
#include <QXmlStreamReader>
22
#include <QXmlStreamWriter>
23
#include <QCoreApplication>
24
#include <QElapsedTimer>
25
#include <QDebug>
26
#include <cmath>
27
#include <QDir>
28

29
#include "rgbscriptscache.h"
30
#include "qlcfixturehead.h"
31
#include "fixturegroup.h"
32
#include "genericfader.h"
33
#include "fadechannel.h"
34
#include "rgbmatrix.h"
35
#include "rgbimage.h"
36
#include "doc.h"
37

38
#define KXMLQLCRGBMatrixStartColor      QStringLiteral("MonoColor")
39
#define KXMLQLCRGBMatrixEndColor        QStringLiteral("EndColor")
40
#define KXMLQLCRGBMatrixColor           QStringLiteral("Color")
41
#define KXMLQLCRGBMatrixColorIndex      QStringLiteral("Index")
42

43
#define KXMLQLCRGBMatrixFixtureGroup    QStringLiteral("FixtureGroup")
44
#define KXMLQLCRGBMatrixDimmerControl   QStringLiteral("DimmerControl")
45

46
#define KXMLQLCRGBMatrixProperty        QStringLiteral("Property")
47
#define KXMLQLCRGBMatrixPropertyName    QStringLiteral("Name")
48
#define KXMLQLCRGBMatrixPropertyValue   QStringLiteral("Value")
49

50
#define KXMLQLCRGBMatrixControlMode         QStringLiteral("ControlMode")
51
#define KXMLQLCRGBMatrixControlModeRgb      QStringLiteral("RGB")
52
#define KXMLQLCRGBMatrixControlModeAmber    QStringLiteral("Amber")
53
#define KXMLQLCRGBMatrixControlModeWhite    QStringLiteral("White")
54
#define KXMLQLCRGBMatrixControlModeUV       QStringLiteral("UV")
55
#define KXMLQLCRGBMatrixControlModeDimmer   QStringLiteral("Dimmer")
56
#define KXMLQLCRGBMatrixControlModeShutter  QStringLiteral("Shutter")
57

58
/****************************************************************************
59
 * Initialization
60
 ****************************************************************************/
61

62
RGBMatrix::RGBMatrix(Doc *doc)
9✔
63
    : Function(doc, Function::RGBMatrixType)
64
    , m_dimmerControl(false)
9✔
65
    , m_fixtureGroupID(FixtureGroup::invalidId())
18✔
66
    , m_group(NULL)
9✔
67
    , m_requestEngineCreation(true)
9✔
68
    , m_runAlgorithm(NULL)
9✔
69
    , m_algorithm(NULL)
9✔
70
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
71
    , m_algorithmMutex(QMutex::Recursive)
72
#endif
73
    , m_stepHandler(new RGBMatrixStep())
9✔
74
    , m_stepsCount(0)
9✔
75
    , m_stepBeatDuration(0)
9✔
76
    , m_controlMode(RGBMatrix::ControlModeRgb)
18✔
77
{
78
    setName(tr("New RGB Matrix"));
9✔
79
    setDuration(500);
9✔
80

81
    m_rgbColors.fill(QColor(), RGBAlgorithmColorDisplayCount);
9✔
82
    setColor(0, Qt::red);
9✔
83

84
    setAlgorithm(RGBAlgorithm::algorithm(doc, "Stripes"));
9✔
85
}
9✔
86

87
RGBMatrix::~RGBMatrix()
11✔
88
{
89
    //if (m_runAlgorithm != NULL)
90
    //    delete m_runAlgorithm;
91
    delete m_algorithm;
9✔
92
    delete m_stepHandler;
9✔
93
}
11✔
94

95
QIcon RGBMatrix::getIcon() const
×
96
{
97
    return QIcon(":/rgbmatrix.png");
×
98
}
99

100
void RGBMatrix::setTotalDuration(quint32 msec)
1✔
101
{
102
    QMutexLocker algorithmLocker(&m_algorithmMutex);
1✔
103

104
    if (m_algorithm == NULL)
1✔
105
        return;
×
106

107
    FixtureGroup *grp = doc()->fixtureGroup(fixtureGroup());
1✔
108
    if (grp == NULL)
1✔
109
        return;
×
110

111
    int steps = m_algorithm->rgbMapStepCount(grp->size());
1✔
112
    setDuration(msec / steps);
1✔
113
}
1✔
114

115
quint32 RGBMatrix::totalDuration()
3✔
116
{
117
    QMutexLocker algorithmLocker(&m_algorithmMutex);
3✔
118

119
    if (m_algorithm == NULL)
3✔
120
        return 0;
×
121

122
    FixtureGroup *grp = doc()->fixtureGroup(fixtureGroup());
3✔
123
    if (grp == NULL)
3✔
124
        return 0;
1✔
125

126
    //qDebug () << "Algorithm steps:" << m_algorithm->rgbMapStepCount(grp->size());
127
    return m_algorithm->rgbMapStepCount(grp->size()) * duration();
2✔
128
}
3✔
129

130
void RGBMatrix::setDimmerControl(bool dimmerControl)
1✔
131
{
132
    m_dimmerControl = dimmerControl;
1✔
133
}
1✔
134

135
bool RGBMatrix::dimmerControl() const
2✔
136
{
137
    return m_dimmerControl;
2✔
138
}
139

140
/****************************************************************************
141
 * Copying
142
 ****************************************************************************/
143

144
Function* RGBMatrix::createCopy(Doc* doc, bool addToDoc)
1✔
145
{
146
    Q_ASSERT(doc != NULL);
1✔
147

148
    Function *copy = new RGBMatrix(doc);
1✔
149
    if (copy->copyFrom(this) == false)
1✔
150
    {
151
        delete copy;
×
152
        copy = NULL;
×
153
    }
154
    if (addToDoc == true && doc->addFunction(copy) == false)
1✔
155
    {
156
        delete copy;
×
157
        copy = NULL;
×
158
    }
159

160
    return copy;
1✔
161
}
162

163
bool RGBMatrix::copyFrom(const Function* function)
1✔
164
{
165
    const RGBMatrix *mtx = qobject_cast<const RGBMatrix*> (function);
1✔
166
    if (mtx == NULL)
1✔
167
        return false;
×
168

169
    setDimmerControl(mtx->dimmerControl());
1✔
170
    setFixtureGroup(mtx->fixtureGroup());
1✔
171

172
    m_rgbColors.clear();
1✔
173
    foreach (QColor col, mtx->getColors())
7✔
174
        m_rgbColors.append(col);
6✔
175

176
    if (mtx->algorithm() != NULL)
1✔
177
        setAlgorithm(mtx->algorithm()->clone());
1✔
178
    else
179
        setAlgorithm(NULL);
×
180

181
    setControlMode(mtx->controlMode());
1✔
182

183
    return Function::copyFrom(function);
1✔
184
}
185

186
/****************************************************************************
187
 * Fixtures
188
 ****************************************************************************/
189

190
quint32 RGBMatrix::fixtureGroup() const
35✔
191
{
192
    return m_fixtureGroupID;
35✔
193
}
194

195
void RGBMatrix::setFixtureGroup(quint32 id)
8✔
196
{
197
    m_fixtureGroupID = id;
8✔
198
    {
199
        QMutexLocker algoLocker(&m_algorithmMutex);
8✔
200
        m_group = doc()->fixtureGroup(m_fixtureGroupID);
8✔
201
    }
8✔
202
    m_stepsCount = algorithmStepsCount();
8✔
203
}
8✔
204

205
QList<quint32> RGBMatrix::components()
2✔
206
{
207
    if (m_group != NULL)
2✔
208
        return m_group->fixtureList();
1✔
209

210
    return QList<quint32>();
1✔
211
}
212

213
/****************************************************************************
214
 * Algorithm
215
 ****************************************************************************/
216

217
void RGBMatrix::setAlgorithm(RGBAlgorithm *algo)
13✔
218
{
219
    {
220
        QMutexLocker algorithmLocker(&m_algorithmMutex);
13✔
221
        delete m_algorithm;
13✔
222
        m_algorithm = algo;
13✔
223

224
        m_requestEngineCreation = true;
13✔
225

226
        /** If there's been a change of Script algorithm "on the fly",
227
         *  then re-apply the properties currently set in this RGBMatrix */
228
        if (m_algorithm != NULL && m_algorithm->type() == RGBAlgorithm::Script)
13✔
229
        {
230
            RGBScript *script = static_cast<RGBScript*> (m_algorithm);
13✔
231
            QMapIterator<QString, QString> it(m_properties);
13✔
232
            while (it.hasNext())
13✔
233
            {
234
                it.next();
×
235
                if (script->setProperty(it.key(), it.value()) == false)
×
236
                {
237
                    /** If the new algorithm doesn't expose a property,
238
                     *  then remove it from the cached list, otherwise
239
                     *  it would be carried around forever (and saved on XML) */
240
                    m_properties.take(it.key());
×
241
                }
242
            }
243

244
            QVector<uint> colors = script->rgbMapGetColors();
13✔
245
            for (int i = 0; i < colors.count(); i++)
13✔
NEW
246
                m_rgbColors.replace(i, QColor::fromRgb(colors.at(i)));
×
247
        }
13✔
248
    }
13✔
249
    m_stepsCount = algorithmStepsCount();
13✔
250

251
    emit changed(id());
13✔
252
}
13✔
253

254
RGBAlgorithm *RGBMatrix::algorithm() const
18✔
255
{
256
    return m_algorithm;
18✔
257
}
258

259
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
260
QMutex& RGBMatrix::algorithmMutex()
261
{
262
    return m_algorithmMutex;
263
}
264
#else
265
QRecursiveMutex& RGBMatrix::algorithmMutex()
×
266
{
267
    return m_algorithmMutex;
×
268
}
269
#endif
270

271

272
int RGBMatrix::stepsCount()
2✔
273
{
274
    return m_stepsCount;
2✔
275
}
276

277
int RGBMatrix::algorithmStepsCount()
22✔
278
{
279
    QMutexLocker algorithmLocker(&m_algorithmMutex);
22✔
280

281
    if (m_algorithm == NULL)
22✔
282
        return 0;
×
283

284
    FixtureGroup *grp = doc()->fixtureGroup(fixtureGroup());
22✔
285
    if (grp != NULL)
22✔
286
        return m_algorithm->rgbMapStepCount(grp->size());
6✔
287

288
    return 0;
16✔
289
}
22✔
290

291
void RGBMatrix::previewMap(int step, RGBMatrixStep *handler)
7✔
292
{
293
    QMutexLocker algorithmLocker(&m_algorithmMutex);
7✔
294
    if (m_algorithm == NULL || handler == NULL)
7✔
295
        return;
×
296

297
    if (m_group == NULL)
7✔
298
        m_group = doc()->fixtureGroup(fixtureGroup());
1✔
299

300
    if (m_group != NULL)
7✔
301
    {
302
        setMapColors(m_algorithm);
6✔
303
        m_algorithm->rgbMap(m_group->size(), handler->stepColor().rgb(), step, handler->m_map);
6✔
304
    }
305
}
7✔
306

307
/****************************************************************************
308
 * Color
309
 ****************************************************************************/
310

311
void RGBMatrix::setColor(int i, QColor c)
25✔
312
{
313
    if (i < 0)
25✔
NEW
314
        return;
×
315

316
    if (i >= m_rgbColors.count())
25✔
NEW
317
        m_rgbColors.resize(i + 1);
×
318

319
    m_rgbColors.replace(i, c);
25✔
320
    {
321
        QMutexLocker algorithmLocker(&m_algorithmMutex);
25✔
322
        if (m_algorithm != NULL)
25✔
323
        {
324
            m_algorithm->setColors(m_rgbColors);
16✔
325
            updateColorDelta();
16✔
326
        }
327
    }
25✔
328
    setMapColors(m_algorithm);
25✔
329
    emit changed(id());
25✔
330
}
331

332
QColor RGBMatrix::getColor(int i) const
10✔
333
{
334
    if (i < 0 || i >= m_rgbColors.count())
10✔
NEW
335
        return QColor();
×
336

337
    return m_rgbColors.at(i);
10✔
338
}
339

340
QVector<QColor> RGBMatrix::getColors() const
1✔
341
{
342
    return m_rgbColors;
1✔
343
}
344

345
void RGBMatrix::updateColorDelta()
16✔
346
{
347
    if (m_rgbColors.count() > 1)
16✔
348
        m_stepHandler->calculateColorDelta(m_rgbColors[0], m_rgbColors[1], m_algorithm);
16✔
349
}
16✔
350

351
void RGBMatrix::setMapColors(RGBAlgorithm *algorithm)
31✔
352
{
353
    QMutexLocker algorithmLocker(&m_algorithmMutex);
31✔
354
    if (algorithm == NULL)
31✔
355
        return;
9✔
356

357
    if (algorithm->apiVersion() < 3)
22✔
358
        return;
22✔
359

NEW
360
    if (m_group == NULL)
×
NEW
361
        m_group = doc()->fixtureGroup(fixtureGroup());
×
362

NEW
363
    QVector<unsigned int> rawColors;
×
NEW
364
    const int acceptColors = algorithm->acceptColors();
×
NEW
365
    rawColors.reserve(acceptColors);
×
NEW
366
    for (int i = 0; i < acceptColors; i++)
×
367
    {
NEW
368
        if (m_rgbColors.count() > i)
×
369
        {
NEW
370
            QColor col = m_rgbColors.at(i);
×
NEW
371
            rawColors.append(col.isValid() ? col.rgb() : 0);
×
372
        }
373
        else
374
        {
NEW
375
            rawColors.append(0);
×
376
        }
377
    }
378

NEW
379
    algorithm->rgbMapSetColors(rawColors);
×
380
}
31✔
381

382
/************************************************************************
383
 * Properties
384
 ************************************************************************/
385

386
void RGBMatrix::setProperty(QString propName, QString value)
1✔
387
{
388
    QMutexLocker algoLocker(&m_algorithmMutex);
1✔
389
    m_properties[propName] = value;
1✔
390
    if (m_algorithm != NULL && m_algorithm->type() == RGBAlgorithm::Script)
1✔
391
    {
392
        RGBScript *script = static_cast<RGBScript*> (m_algorithm);
1✔
393
        script->setProperty(propName, value);
1✔
394

395
        QVector<uint> colors = script->rgbMapGetColors();
1✔
396
        for (int i = 0; i < colors.count(); i++)
1✔
NEW
397
            setColor(i, QColor::fromRgb(colors.at(i)));
×
398
    }
1✔
399
    m_stepsCount = algorithmStepsCount();
1✔
400
}
1✔
401

402
QString RGBMatrix::property(QString propName)
3✔
403
{
404
    QMutexLocker algoLocker(&m_algorithmMutex);
3✔
405

406
    /** If the property is cached, then return it right away */
407
    QMap<QString, QString>::iterator it = m_properties.find(propName);
3✔
408
    if (it != m_properties.end())
3✔
409
        return it.value();
1✔
410

411
    /** Otherwise, let's retrieve it from the Script */
412
    if (m_algorithm != NULL && m_algorithm->type() == RGBAlgorithm::Script)
2✔
413
    {
414
        RGBScript *script = static_cast<RGBScript*> (m_algorithm);
2✔
415
        return script->property(propName);
2✔
416
    }
417

418
    return QString();
×
419
}
3✔
420

421
/****************************************************************************
422
 * Load & Save
423
 ****************************************************************************/
424

425
bool RGBMatrix::loadXML(QXmlStreamReader &root)
3✔
426
{
427
    if (root.name() != KXMLQLCFunction)
3✔
428
    {
429
        qWarning() << Q_FUNC_INFO << "Function node not found";
1✔
430
        return false;
1✔
431
    }
432

433
    if (root.attributes().value(KXMLQLCFunctionType).toString() != typeToString(Function::RGBMatrixType))
4✔
434
    {
435
        qWarning() << Q_FUNC_INFO << "Function is not an RGB matrix";
1✔
436
        return false;
1✔
437
    }
438

439
    /* Load matrix contents */
440
    while (root.readNextStartElement())
12✔
441
    {
442
        if (root.name() == KXMLQLCFunctionSpeed)
11✔
443
        {
444
            loadXMLSpeed(root);
1✔
445
        }
446
        else if (root.name() == KXMLQLCRGBAlgorithm)
10✔
447
        {
448
            setAlgorithm(RGBAlgorithm::loader(doc(), root));
1✔
449
        }
450
        else if (root.name() == KXMLQLCRGBMatrixFixtureGroup)
9✔
451
        {
452
            setFixtureGroup(root.readElementText().toUInt());
1✔
453
        }
454
        else if (root.name() == KXMLQLCFunctionDirection)
8✔
455
        {
456
            loadXMLDirection(root);
1✔
457
        }
458
        else if (root.name() == KXMLQLCFunctionRunOrder)
7✔
459
        {
460
            loadXMLRunOrder(root);
1✔
461
        }
462
        // Legacy support
463
        else if (root.name() == KXMLQLCRGBMatrixStartColor)
6✔
464
        {
NEW
465
            setColor(0, QColor::fromRgb(QRgb(root.readElementText().toUInt())));
×
466
        }
467
        else if (root.name() == KXMLQLCRGBMatrixEndColor)
6✔
468
        {
NEW
469
            setColor(1, QColor::fromRgb(QRgb(root.readElementText().toUInt())));
×
470
        }
471
        else if (root.name() == KXMLQLCRGBMatrixColor)
6✔
472
        {
473
            int colorIdx = root.attributes().value(KXMLQLCRGBMatrixColorIndex).toInt();
10✔
474
            setColor(colorIdx, QColor::fromRgb(QRgb(root.readElementText().toUInt())));
5✔
475
        }
476
        else if (root.name() == KXMLQLCRGBMatrixControlMode)
1✔
477
        {
478
            setControlMode(stringToControlMode(root.readElementText()));
1✔
479
        }
480
        else if (root.name() == KXMLQLCRGBMatrixProperty)
×
481
        {
482
            QString name = root.attributes().value(KXMLQLCRGBMatrixPropertyName).toString();
×
483
            QString value = root.attributes().value(KXMLQLCRGBMatrixPropertyValue).toString();
×
484
            setProperty(name, value);
×
485
            root.skipCurrentElement();
×
UNCOV
486
        }
×
487
        else if (root.name() == KXMLQLCRGBMatrixDimmerControl)
×
488
        {
489
            setDimmerControl(root.readElementText().toInt());
×
490
        }
491
        else
492
        {
493
            qWarning() << Q_FUNC_INFO << "Unknown RGB matrix tag:" << root.name();
×
494
            root.skipCurrentElement();
×
495
        }
496
    }
497

498
    return true;
1✔
499
}
500

501
bool RGBMatrix::saveXML(QXmlStreamWriter *doc)
1✔
502
{
503
    Q_ASSERT(doc != NULL);
1✔
504

505
    /* Function tag */
506
    doc->writeStartElement(KXMLQLCFunction);
2✔
507

508
    /* Common attributes */
509
    saveXMLCommon(doc);
1✔
510

511
    /* Speeds */
512
    saveXMLSpeed(doc);
1✔
513

514
    /* Direction */
515
    saveXMLDirection(doc);
1✔
516

517
    /* Run order */
518
    saveXMLRunOrder(doc);
1✔
519

520
    /* Algorithm */
521
    if (m_algorithm != NULL)
1✔
522
        m_algorithm->saveXML(doc);
1✔
523

524
    /* LEGACY - Dimmer Control */
525
    if (dimmerControl())
1✔
526
        doc->writeTextElement(KXMLQLCRGBMatrixDimmerControl, QString::number(dimmerControl()));
×
527

528
    /* Colors */
529
    for (int i = 0; i < m_rgbColors.count(); i++)
6✔
530
    {
531
        if (m_rgbColors.at(i).isValid() == false)
5✔
NEW
532
            continue;
×
533

534
        doc->writeStartElement(KXMLQLCRGBMatrixColor);
10✔
535
        doc->writeAttribute(KXMLQLCRGBMatrixColorIndex, QString::number(i));
10✔
536
        doc->writeCharacters(QString::number(m_rgbColors.at(i).rgb()));
5✔
537
        doc->writeEndElement();
5✔
538
    }
539

540
    /* Control Mode */
541
    doc->writeTextElement(KXMLQLCRGBMatrixControlMode, RGBMatrix::controlModeToString(m_controlMode));
2✔
542

543
    /* Fixture Group */
544
    doc->writeTextElement(KXMLQLCRGBMatrixFixtureGroup, QString::number(fixtureGroup()));
2✔
545

546
    /* Properties */
547
    QMapIterator<QString, QString> it(m_properties);
1✔
548
    while (it.hasNext())
1✔
549
    {
550
        it.next();
×
551
        doc->writeStartElement(KXMLQLCRGBMatrixProperty);
×
552
        doc->writeAttribute(KXMLQLCRGBMatrixPropertyName, it.key());
×
553
        doc->writeAttribute(KXMLQLCRGBMatrixPropertyValue, it.value());
×
554
        doc->writeEndElement();
×
555
    }
556

557
    /* End the <Function> tag */
558
    doc->writeEndElement();
1✔
559

560
    return true;
1✔
561
}
1✔
562

563
/****************************************************************************
564
 * Running
565
 ****************************************************************************/
566

567
void RGBMatrix::tap()
×
568
{
569
    if (stopped() == false)
×
570
    {
NEW
571
        FixtureGroup *grp = doc()->fixtureGroup(fixtureGroup());
×
572
        // Filter out taps that are too close to each other
NEW
573
        if (grp != NULL && uint(m_roundTime.elapsed()) >= (duration() / 4))
×
574
        {
575
            roundCheck();
×
576
            resetElapsed();
×
577
        }
578
    }
579
}
×
580

NEW
581
void RGBMatrix::checkEngineCreation()
×
582
{
NEW
583
    m_runAlgorithm = m_algorithm;
×
NEW
584
    m_requestEngineCreation = false;
×
NEW
585
}
×
586

UNCOV
587
void RGBMatrix::preRun(MasterTimer *timer)
×
588
{
589
    {
590
        QMutexLocker algorithmLocker(&m_algorithmMutex);
×
591

592
        m_group = doc()->fixtureGroup(m_fixtureGroupID);
×
593
        if (m_group == NULL)
×
594
        {
595
            // No fixture group to control
596
            stop(FunctionParent::master());
×
597
            return;
×
598
        }
599

600
        if (m_algorithm != NULL)
×
601
        {
NEW
602
            checkEngineCreation();
×
603

604
            // Copy direction from parent class direction
NEW
605
            m_stepHandler->initializeDirection(direction(), m_rgbColors[0], m_rgbColors[1], m_stepsCount, m_runAlgorithm);
×
606

NEW
607
            if (m_runAlgorithm->type() == RGBAlgorithm::Script)
×
608
            {
NEW
609
                RGBScript *script = static_cast<RGBScript*> (m_runAlgorithm);
×
NEW
610
                QMapIterator<QString, QString> it(m_properties);
×
NEW
611
                while (it.hasNext())
×
612
                {
613
                    it.next();
×
614
                    script->setProperty(it.key(), it.value());
×
615
                }
UNCOV
616
            }
×
NEW
617
            else if (m_runAlgorithm->type() == RGBAlgorithm::Image)
×
618
            {
NEW
619
                RGBImage *image = static_cast<RGBImage*> (m_runAlgorithm);
×
NEW
620
                if (image->animatedSource())
×
NEW
621
                    image->rewindAnimation();
×
622
            }
623
        }
UNCOV
624
    }
×
625

NEW
626
    m_roundTime.restart();
×
627

628
    Function::preRun(timer);
×
629
}
630

631
void RGBMatrix::write(MasterTimer *timer, QList<Universe *> universes)
×
632
{
633
    Q_UNUSED(timer);
634

635
    {
636
        QMutexLocker algorithmLocker(&m_algorithmMutex);
×
637
        if (m_group == NULL)
×
638
        {
639
            // No fixture group to control
640
            stop(FunctionParent::master());
×
641
            return;
×
642
        }
643

644
        // No time to do anything.
645
        if (duration() == 0)
×
646
            return;
×
647

NEW
648
        if (m_algorithm != NULL && m_requestEngineCreation)
×
NEW
649
            checkEngineCreation();
×
650

651
        // Invalid/nonexistent script
NEW
652
        if (m_runAlgorithm == NULL || m_runAlgorithm->apiVersion() == 0)
×
653
            return;
×
654

655
        if (isPaused() == false)
×
656
        {
657
            // Get a new map every time elapsed is reset to zero
658
            if (elapsed() < MasterTimer::tick())
×
659
            {
660
                if (tempoType() == Beats)
×
661
                    m_stepBeatDuration = beatsToTime(duration(), timer->beatTimeDuration());
×
662

663
                //qDebug() << "RGBMatrix step" << m_stepHandler->currentStepIndex() << ", color:" << QString::number(m_stepHandler->stepColor().rgb(), 16);
NEW
664
                m_runAlgorithm->rgbMap(m_group->size(), m_stepHandler->stepColor().rgb(),
×
NEW
665
                                       m_stepHandler->currentStepIndex(), m_stepHandler->m_map);
×
UNCOV
666
                updateMapChannels(m_stepHandler->m_map, m_group, universes);
×
667
            }
668
        }
UNCOV
669
    }
×
670

671
    if (isPaused() == false)
×
672
    {
673
        // Increment the ms elapsed time
674
        incrementElapsed();
×
675

676
        /* Check if we need to change direction, stop completely or go to next step
677
         * The cases are:
678
         * 1- time tempo type: act normally, on ms elapsed time
679
         * 2- beat tempo type, beat occurred: check if the elapsed beats is a multiple of
680
         *    the step beat duration. If so, proceed to the next step
681
         * 3- beat tempo type, not beat: if the ms elapsed time reached the step beat
682
         *    duration in ms, and the ms time to the next beat is not less than 1/16 of
683
         *    the step beat duration in ms, then proceed to the next step. If the ms time to the
684
         *    next beat is less than 1/16 of the step beat duration in ms, then defer the step
685
         *    change to case #2, to resync the matrix to the next beat
686
         */
687
        if (tempoType() == Time && elapsed() >= duration())
×
688
        {
689
            roundCheck();
×
690
        }
691
        else if (tempoType() == Beats)
×
692
        {
693
            if (timer->isBeat())
×
694
            {
695
                incrementElapsedBeats();
×
696
                qDebug() << "Elapsed beats:" << elapsedBeats() << ", time elapsed:" << elapsed() << ", step time:" << m_stepBeatDuration;
×
697
                if (elapsedBeats() % duration() == 0)
×
698
                {
699
                    roundCheck();
×
700
                    resetElapsed();
×
701
                }
702
            }
703
            else if (elapsed() >= m_stepBeatDuration && (uint)timer->timeToNextBeat() > m_stepBeatDuration / 16)
×
704
            {
705
                qDebug() << "Elapsed exceeded";
×
706
                roundCheck();
×
707
            }
708
        }
709
    }
710
}
711

712
void RGBMatrix::postRun(MasterTimer *timer, QList<Universe *> universes)
×
713
{
714
    uint fadeout = overrideFadeOutSpeed() == defaultSpeed() ? fadeOutSpeed() : overrideFadeOutSpeed();
×
715

716
    /* If no fade out is needed, dismiss all the requested faders.
717
     * Otherwise, set all the faders to fade out and let Universe dismiss them
718
     * when done */
719
    if (fadeout == 0)
×
720
    {
721
        dismissAllFaders();
×
722
    }
723
    else
724
    {
725
        if (tempoType() == Beats)
×
726
            fadeout = beatsToTime(fadeout, timer->beatTimeDuration());
×
727

NEW
728
        foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
×
729
        {
730
            if (!fader.isNull())
×
731
                fader->setFadeOut(true, fadeout);
×
UNCOV
732
        }
×
733
    }
734

735
    m_fadersMap.clear();
×
736

737
    {
738
        QMutexLocker algorithmLocker(&m_algorithmMutex);
×
NEW
739
        checkEngineCreation();
×
NEW
740
        if (m_runAlgorithm != NULL)
×
NEW
741
            m_runAlgorithm->postRun();
×
UNCOV
742
    }
×
743

744
    Function::postRun(timer, universes);
×
745
}
×
746

747
void RGBMatrix::roundCheck()
×
748
{
749
    QMutexLocker algorithmLocker(&m_algorithmMutex);
×
750
    if (m_algorithm == NULL)
×
751
        return;
×
752

NEW
753
    if (m_stepHandler->checkNextStep(runOrder(), m_rgbColors[0], m_rgbColors[1], m_stepsCount) == false)
×
754
        stop(FunctionParent::master());
×
755

NEW
756
    m_roundTime.restart();
×
757

758
    if (tempoType() == Beats)
×
759
        roundElapsed(m_stepBeatDuration);
×
760
    else
761
        roundElapsed(duration());
×
UNCOV
762
}
×
763

NEW
764
FadeChannel *RGBMatrix::getFader(Universe *universe, quint32 fixtureID, quint32 channel)
×
765
{
766
    // get the universe Fader first. If doesn't exist, create it
NEW
767
    if (universe == NULL)
×
NEW
768
        return NULL;
×
769

NEW
770
    QSharedPointer<GenericFader> fader = m_fadersMap.value(universe->id(), QSharedPointer<GenericFader>());
×
UNCOV
771
    if (fader.isNull())
×
772
    {
NEW
773
        fader = universe->requestFader();
×
774
        fader->adjustIntensity(getAttributeValue(Intensity));
×
775
        fader->setBlendMode(blendMode());
×
776
        fader->setName(name());
×
777
        fader->setParentFunctionID(id());
×
NEW
778
        m_fadersMap[universe->id()] = fader;
×
779
    }
780

NEW
781
    return fader->getChannelFader(doc(), universe, fixtureID, channel);
×
UNCOV
782
}
×
783

784
void RGBMatrix::updateFaderValues(FadeChannel *fc, uchar value, uint fadeTime)
×
785
{
786
    fc->setStart(fc->current());
×
787
    fc->setTarget(value);
×
788
    fc->setElapsed(0);
×
789
    fc->setReady(false);
×
790
    // fade in/out depends on target value
791
    if (value == 0)
×
792
        fc->setFadeTime(fadeOutSpeed());
×
793
    else
794
        fc->setFadeTime(fadeTime);
×
795
}
×
796

797
void RGBMatrix::updateMapChannels(const RGBMap& map, const FixtureGroup *grp, QList<Universe *> universes)
×
798
{
799
    uint fadeTime = (overrideFadeInSpeed() == defaultSpeed()) ? fadeInSpeed() : overrideFadeInSpeed();
×
800

801
    // Create/modify fade channels for ALL heads in the group
802
    QMapIterator<QLCPoint, GroupHead> it(grp->headsMap());
×
803
    while (it.hasNext())
×
804
    {
805
        it.next();
×
806
        QLCPoint pt = it.key();
×
807
        GroupHead grpHead = it.value();
×
808
        Fixture *fxi = doc()->fixture(grpHead.fxi);
×
809
        if (fxi == NULL)
×
810
            continue;
×
811

812
        QLCFixtureHead head = fxi->head(grpHead.head);
×
813

814
        if (pt.y() >= map.count() || pt.x() >= map[pt.y()].count())
×
815
            continue;
×
816

817
        uint col = map[pt.y()][pt.x()];
×
NEW
818
        QVector<quint32> channelList;
×
NEW
819
        QVector<uchar> valueList;
×
820

821
        if (m_controlMode == ControlModeRgb)
×
822
        {
NEW
823
            channelList = head.rgbChannels();
×
824

NEW
825
            if (channelList.size() == 3)
×
826
            {
NEW
827
                valueList.append(qRed(col));
×
NEW
828
                valueList.append(qGreen(col));
×
NEW
829
                valueList.append(qBlue(col));
×
830
            }
831
            else
832
            {
NEW
833
                channelList = head.cmyChannels();
×
834

NEW
835
                if (channelList.size() == 3)
×
836
                {
837
                    // CMY color mixing
NEW
838
                    QColor cmyCol(col);
×
NEW
839
                    valueList.append(cmyCol.cyan());
×
NEW
840
                    valueList.append(cmyCol.magenta());
×
NEW
841
                    valueList.append(cmyCol.yellow());
×
842
                }
843
            }
844
        }
845
        else if (m_controlMode == ControlModeShutter)
×
846
        {
NEW
847
            channelList = head.shutterChannels();
×
848

NEW
849
            if (channelList.size())
×
850
            {
851
                // make sure only one channel is in the list
NEW
852
                channelList.resize(1);
×
NEW
853
                valueList.append(rgbToGrey(col));
×
854
            }
855
        }
NEW
856
        else if (m_controlMode == ControlModeDimmer || m_dimmerControl)
×
857
        {
858
            // Collect all dimmers that affect current head:
859
            // They are the master dimmer (affects whole fixture)
860
            // and per-head dimmer.
861
            //
862
            // If there are no RGB or CMY channels, the least important* dimmer channel
863
            // is used to create grayscale image.
864
            //
865
            // The rest of the dimmer channels are set to full if dimmer control is
866
            // enabled and target color is > 0 (see
867
            // http://www.qlcplus.org/forum/viewtopic.php?f=29&t=11090)
868
            //
869
            // Note: If there is only one head, and only one dimmer channel,
870
            // make it a master dimmer in fixture definition.
871
            //
872
            // *least important - per head dimmer if present,
873
            // otherwise per fixture dimmer if present
874

NEW
875
            quint32 masterDim = fxi->masterIntensityChannel();
×
NEW
876
            quint32 headDim = head.channelNumber(QLCChannel::Intensity, QLCChannel::MSB);
×
877

NEW
878
            if (masterDim != QLCChannel::invalid())
×
879
            {
NEW
880
                channelList.append(masterDim);
×
NEW
881
                valueList.append(rgbToGrey(col));
×
882
            }
883

NEW
884
            if (headDim != QLCChannel::invalid() && headDim != masterDim)
×
885
            {
NEW
886
                channelList.append(headDim);
×
NEW
887
                valueList.append(rgbToGrey(col) == 0 ? 0 : 255);
×
888
            }
UNCOV
889
        }
×
890
        else
891
        {
NEW
892
            if (m_controlMode == ControlModeWhite)
×
NEW
893
                channelList.append(head.channelNumber(QLCChannel::White, QLCChannel::MSB));
×
NEW
894
            else if (m_controlMode == ControlModeAmber)
×
NEW
895
                channelList.append(head.channelNumber(QLCChannel::Amber, QLCChannel::MSB));
×
NEW
896
            else if (m_controlMode == ControlModeUV)
×
NEW
897
                channelList.append(head.channelNumber(QLCChannel::UV, QLCChannel::MSB));
×
898

NEW
899
            valueList.append(rgbToGrey(col));
×
900
        }
901

NEW
902
        quint32 absAddress = fxi->universeAddress();
×
903

NEW
904
        for (int i = 0; i < channelList.count(); i++)
×
905
        {
NEW
906
            if (channelList.at(i) == QLCChannel::invalid())
×
NEW
907
                continue;
×
908

NEW
909
            quint32 universeIndex = floor((absAddress + channelList.at(i)) / 512);
×
910

NEW
911
            FadeChannel *fc = getFader(universes.at(universeIndex), grpHead.fxi, channelList.at(i));
×
NEW
912
            updateFaderValues(fc, valueList.at(i), fadeTime);
×
913
        }
914
    }
×
915
}
×
916

917
uchar RGBMatrix::rgbToGrey(uint col)
×
918
{
919
    // the weights are taken from
920
    // https://en.wikipedia.org/wiki/YUV#SDTV_with_BT.601
921
    return (0.299 * qRed(col) + 0.587 * qGreen(col) + 0.114 * qBlue(col));
×
922
}
923

924
/*********************************************************************
925
 * Attributes
926
 *********************************************************************/
927

928
int RGBMatrix::adjustAttribute(qreal fraction, int attributeId)
×
929
{
930
    int attrIndex = Function::adjustAttribute(fraction, attributeId);
×
931

932
    if (attrIndex == Intensity)
×
933
    {
NEW
934
        foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
×
935
        {
936
            if (!fader.isNull())
×
937
                fader->adjustIntensity(getAttributeValue(Function::Intensity));
×
UNCOV
938
        }
×
939
    }
940

941
    return attrIndex;
×
942
}
943

944
/*************************************************************************
945
 * Blend mode
946
 *************************************************************************/
947

948
void RGBMatrix::setBlendMode(Universe::BlendMode mode)
×
949
{
950
    if (mode == blendMode())
×
951
        return;
×
952

NEW
953
    foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
×
954
    {
955
        if (!fader.isNull())
×
956
            fader->setBlendMode(mode);
×
UNCOV
957
    }
×
958

959
    Function::setBlendMode(mode);
×
960
    emit changed(id());
×
961
}
962

963
/*************************************************************************
964
 * Control Mode
965
 *************************************************************************/
966

967
RGBMatrix::ControlMode RGBMatrix::controlMode() const
2✔
968
{
969
    return m_controlMode;
2✔
970
}
971

972
void RGBMatrix::setControlMode(RGBMatrix::ControlMode mode)
3✔
973
{
974
    m_controlMode = mode;
3✔
975
    emit changed(id());
3✔
976
}
3✔
977

978
RGBMatrix::ControlMode RGBMatrix::stringToControlMode(QString mode)
1✔
979
{
980
    if (mode == KXMLQLCRGBMatrixControlModeRgb)
1✔
981
        return ControlModeRgb;
1✔
982
    else if (mode == KXMLQLCRGBMatrixControlModeAmber)
×
983
        return ControlModeAmber;
×
984
    else if (mode == KXMLQLCRGBMatrixControlModeWhite)
×
985
        return ControlModeWhite;
×
986
    else if (mode == KXMLQLCRGBMatrixControlModeUV)
×
987
        return ControlModeUV;
×
988
    else if (mode == KXMLQLCRGBMatrixControlModeDimmer)
×
989
        return ControlModeDimmer;
×
990
    else if (mode == KXMLQLCRGBMatrixControlModeShutter)
×
991
        return ControlModeShutter;
×
992

993
    return ControlModeRgb;
×
994
}
995

996
QString RGBMatrix::controlModeToString(RGBMatrix::ControlMode mode)
1✔
997
{
998
    switch(mode)
1✔
999
    {
1000
        default:
1✔
1001
        case ControlModeRgb:
1002
            return QString(KXMLQLCRGBMatrixControlModeRgb);
1✔
1003
        break;
1004
        case ControlModeAmber:
×
1005
            return QString(KXMLQLCRGBMatrixControlModeAmber);
×
1006
        break;
1007
        case ControlModeWhite:
×
1008
            return QString(KXMLQLCRGBMatrixControlModeWhite);
×
1009
        break;
1010
        case ControlModeUV:
×
1011
            return QString(KXMLQLCRGBMatrixControlModeUV);
×
1012
        break;
1013
        case ControlModeDimmer:
×
1014
            return QString(KXMLQLCRGBMatrixControlModeDimmer);
×
1015
        break;
1016
        case ControlModeShutter:
×
1017
            return QString(KXMLQLCRGBMatrixControlModeShutter);
×
1018
        break;
1019
    }
1020
}
1021

1022

1023
/*************************************************************************
1024
 *************************************************************************
1025
 *                          RGBMatrixStep class
1026
 *************************************************************************
1027
 *************************************************************************/
1028

1029
RGBMatrixStep::RGBMatrixStep()
10✔
1030
    : m_direction(Function::Forward)
10✔
1031
    , m_currentStepIndex(0)
10✔
1032
    , m_stepColor(QColor())
10✔
1033
    , m_crDelta(0)
10✔
1034
    , m_cgDelta(0)
10✔
1035
    , m_cbDelta(0)
10✔
1036
{
1037

1038
}
10✔
1039

1040
void RGBMatrixStep::setCurrentStepIndex(int index)
×
1041
{
1042
    m_currentStepIndex = index;
×
1043
}
×
1044

1045
int RGBMatrixStep::currentStepIndex() const
1✔
1046
{
1047
    return m_currentStepIndex;
1✔
1048
}
1049

1050
void RGBMatrixStep::calculateColorDelta(QColor startColor, QColor endColor, RGBAlgorithm *algorithm)
16✔
1051
{
1052
    m_crDelta = 0;
16✔
1053
    m_cgDelta = 0;
16✔
1054
    m_cbDelta = 0;
16✔
1055

1056
    if (endColor.isValid() && algorithm != NULL && algorithm->acceptColors() > 1)
16✔
1057
    {
1058
        m_crDelta = endColor.red() - startColor.red();
10✔
1059
        m_cgDelta = endColor.green() - startColor.green();
10✔
1060
        m_cbDelta = endColor.blue() - startColor.blue();
10✔
1061

1062
        //qDebug() << "Color deltas:" << m_crDelta << m_cgDelta << m_cbDelta;
1063
    }
1064
}
16✔
1065

1066
void RGBMatrixStep::setStepColor(QColor color)
×
1067
{
1068
    m_stepColor = color;
×
1069
}
×
1070

1071
QColor RGBMatrixStep::stepColor()
6✔
1072
{
1073
    return m_stepColor;
6✔
1074
}
1075

1076
void RGBMatrixStep::updateStepColor(int stepIndex, QColor startColor, int stepsCount)
×
1077
{
1078
    if (stepsCount <= 0)
×
1079
        return;
×
1080

1081
    if (stepsCount == 1)
×
1082
    {
1083
        m_stepColor = startColor;
×
1084
    }
1085
    else
1086
    {
1087
        m_stepColor.setRed(startColor.red() + (m_crDelta * stepIndex / (stepsCount - 1)));
×
1088
        m_stepColor.setGreen(startColor.green() + (m_cgDelta * stepIndex / (stepsCount - 1)));
×
1089
        m_stepColor.setBlue(startColor.blue() + (m_cbDelta * stepIndex / (stepsCount - 1)));
×
1090
    }
1091

1092
    //qDebug() << "RGBMatrix step" << stepIndex << ", color:" << QString::number(m_stepColor.rgb(), 16);
1093
}
1094

NEW
1095
void RGBMatrixStep::initializeDirection(Function::Direction direction, QColor startColor, QColor endColor, int stepsCount, RGBAlgorithm *algorithm)
×
1096
{
1097
    m_direction = direction;
×
1098

1099
    if (m_direction == Function::Forward)
×
1100
    {
1101
        setCurrentStepIndex(0);
×
1102
        setStepColor(startColor);
×
1103
    }
1104
    else
1105
    {
1106
        setCurrentStepIndex(stepsCount - 1);
×
1107

1108
        if (endColor.isValid())
×
1109
            setStepColor(endColor);
×
1110
        else
1111
            setStepColor(startColor);
×
1112
    }
1113

NEW
1114
    calculateColorDelta(startColor, endColor, algorithm);
×
1115
}
×
1116

1117
bool RGBMatrixStep::checkNextStep(Function::RunOrder order,
×
1118
                                  QColor startColor, QColor endColor, int stepsNumber)
1119
{
1120
    if (order == Function::PingPong)
×
1121
    {
1122
        if (m_direction == Function::Forward && (m_currentStepIndex + 1) == stepsNumber)
×
1123
        {
1124
            m_direction = Function::Backward;
×
1125
            m_currentStepIndex = stepsNumber - 2;
×
1126
            if (endColor.isValid())
×
1127
                m_stepColor = endColor;
×
1128

1129
            updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1130
        }
1131
        else if (m_direction == Function::Backward && (m_currentStepIndex - 1) < 0)
×
1132
        {
1133
            m_direction = Function::Forward;
×
1134
            m_currentStepIndex = 1;
×
1135
            m_stepColor = startColor;
×
1136
            updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1137
        }
1138
        else
1139
        {
1140
            if (m_direction == Function::Forward)
×
1141
                m_currentStepIndex++;
×
1142
            else
1143
                m_currentStepIndex--;
×
1144
            updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1145
        }
1146
    }
1147
    else if (order == Function::SingleShot)
×
1148
    {
1149
        if (m_direction == Function::Forward)
×
1150
        {
1151
            if (m_currentStepIndex >= stepsNumber - 1)
×
1152
                return false;
×
1153
            else
1154
            {
1155
                m_currentStepIndex++;
×
1156
                updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1157
            }
1158
        }
1159
        else
1160
        {
1161
            if (m_currentStepIndex <= 0)
×
1162
                return false;
×
1163
            else
1164
            {
1165
                m_currentStepIndex--;
×
1166
                updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1167
            }
1168
        }
1169
    }
1170
    else
1171
    {
1172
        if (m_direction == Function::Forward)
×
1173
        {
1174
            if (m_currentStepIndex >= stepsNumber - 1)
×
1175
            {
1176
                m_currentStepIndex = 0;
×
1177
                m_stepColor = startColor;
×
1178
            }
1179
            else
1180
            {
1181
                m_currentStepIndex++;
×
1182
                updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1183
            }
1184
        }
1185
        else
1186
        {
1187
            if (m_currentStepIndex <= 0)
×
1188
            {
1189
                m_currentStepIndex = stepsNumber - 1;
×
1190
                if (endColor.isValid())
×
1191
                    m_stepColor = endColor;
×
1192
            }
1193
            else
1194
            {
1195
                m_currentStepIndex--;
×
1196
                updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1197
            }
1198
        }
1199
    }
1200

1201
    return true;
×
1202
}
1203

STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc