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

mcallegari / qlcplus / 16450929051

22 Jul 2025 05:09PM UTC coverage: 34.174% (-0.1%) from 34.286%
16450929051

Pull #1782

github

web-flow
Merge f80a306ab into 8a22b7b84
Pull Request #1782: Feature: Control RGB Matrix script properties with VC Knobs in animation widget

7 of 189 new or added lines in 6 files covered. (3.7%)

9 existing lines in 4 files now uncovered.

17704 of 51806 relevant lines covered (34.17%)

19373.32 hits per line

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

41.4
/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
    QMapIterator<QString, QString> it(mtx->m_properties);
1✔
184
    while (it.hasNext())
1✔
185
    {
NEW
186
        it.next();
×
NEW
187
        setProperty(it.key(), it.value());
×
188
    }
189

190
    return Function::copyFrom(function);
1✔
191
}
1✔
192

NEW
193
void RGBMatrix::setScriptProperty(QString propName, QString value)
×
194
{
NEW
195
    QMutexLocker algoLocker(&m_algorithmMutex);
×
NEW
196
    if (m_runAlgorithm != NULL && m_runAlgorithm->type() == RGBAlgorithm::Script)
×
197
    {
NEW
198
        RGBScript *script = static_cast<RGBScript*> (m_runAlgorithm);
×
NEW
199
        script->setProperty(propName, value);
×
200
    }
NEW
201
}
×
202

203
/****************************************************************************
204
 * Fixtures
205
 ****************************************************************************/
206

207
quint32 RGBMatrix::fixtureGroup() const
35✔
208
{
209
    return m_fixtureGroupID;
35✔
210
}
211

212
void RGBMatrix::setFixtureGroup(quint32 id)
8✔
213
{
214
    m_fixtureGroupID = id;
8✔
215
    {
216
        QMutexLocker algoLocker(&m_algorithmMutex);
8✔
217
        m_group = doc()->fixtureGroup(m_fixtureGroupID);
8✔
218
    }
8✔
219
    m_stepsCount = algorithmStepsCount();
8✔
220
}
8✔
221

222
QList<quint32> RGBMatrix::components()
2✔
223
{
224
    if (m_group != NULL)
2✔
225
        return m_group->fixtureList();
1✔
226

227
    return QList<quint32>();
1✔
228
}
229

230
/****************************************************************************
231
 * Algorithm
232
 ****************************************************************************/
233

234
void RGBMatrix::setAlgorithm(RGBAlgorithm *algo)
13✔
235
{
236
    {
237
        QMutexLocker algorithmLocker(&m_algorithmMutex);
13✔
238
        delete m_algorithm;
13✔
239
        m_algorithm = algo;
13✔
240

241
        m_requestEngineCreation = true;
13✔
242

243
        /** If there's been a change of Script algorithm "on the fly",
244
         *  then re-apply the properties currently set in this RGBMatrix */
245
        if (m_algorithm != NULL && m_algorithm->type() == RGBAlgorithm::Script)
13✔
246
        {
247
            RGBScript *script = static_cast<RGBScript*> (m_algorithm);
13✔
248
            QMapIterator<QString, QString> it(m_properties);
13✔
249
            while (it.hasNext())
13✔
250
            {
251
                it.next();
×
252
                if (script->setProperty(it.key(), it.value()) == false)
×
253
                {
254
                    /** If the new algorithm doesn't expose a property,
255
                     *  then remove it from the cached list, otherwise
256
                     *  it would be carried around forever (and saved on XML) */
257
                    m_properties.take(it.key());
×
258
                }
259
            }
260

261
            QVector<uint> colors = script->rgbMapGetColors();
13✔
262
            for (int i = 0; i < colors.count(); i++)
13✔
263
                m_rgbColors.replace(i, QColor::fromRgb(colors.at(i)));
×
264
        }
13✔
265
    }
13✔
266
    m_stepsCount = algorithmStepsCount();
13✔
267

268
    emit changed(id());
13✔
269
}
13✔
270

271
RGBAlgorithm *RGBMatrix::algorithm() const
18✔
272
{
273
    return m_algorithm;
18✔
274
}
275

276
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
277
QMutex& RGBMatrix::algorithmMutex()
278
{
279
    return m_algorithmMutex;
280
}
281
#else
282
QRecursiveMutex& RGBMatrix::algorithmMutex()
×
283
{
284
    return m_algorithmMutex;
×
285
}
286
#endif
287

288

289
int RGBMatrix::stepsCount()
2✔
290
{
291
    return m_stepsCount;
2✔
292
}
293

294
int RGBMatrix::algorithmStepsCount()
22✔
295
{
296
    QMutexLocker algorithmLocker(&m_algorithmMutex);
22✔
297

298
    if (m_algorithm == NULL)
22✔
299
        return 0;
×
300

301
    FixtureGroup *grp = doc()->fixtureGroup(fixtureGroup());
22✔
302
    if (grp != NULL)
22✔
303
        return m_algorithm->rgbMapStepCount(grp->size());
6✔
304

305
    return 0;
16✔
306
}
22✔
307

308
void RGBMatrix::previewMap(int step, RGBMatrixStep *handler)
7✔
309
{
310
    QMutexLocker algorithmLocker(&m_algorithmMutex);
7✔
311
    if (m_algorithm == NULL || handler == NULL)
7✔
312
        return;
×
313

314
    if (m_group == NULL)
7✔
315
        m_group = doc()->fixtureGroup(fixtureGroup());
1✔
316

317
    if (m_group != NULL)
7✔
318
    {
319
        setMapColors(m_algorithm);
6✔
320
        m_algorithm->rgbMap(m_group->size(), handler->stepColor().rgb(), step, handler->m_map);
6✔
321
    }
322
}
7✔
323

324
/****************************************************************************
325
 * Color
326
 ****************************************************************************/
327

328
void RGBMatrix::setColor(int i, QColor c)
25✔
329
{
330
    if (i < 0)
25✔
331
        return;
×
332

333
    if (i >= m_rgbColors.count())
25✔
334
        m_rgbColors.resize(i + 1);
×
335

336
    m_rgbColors.replace(i, c);
25✔
337
    {
338
        QMutexLocker algorithmLocker(&m_algorithmMutex);
25✔
339
        if (m_algorithm != NULL)
25✔
340
        {
341
            m_algorithm->setColors(m_rgbColors);
16✔
342
            updateColorDelta();
16✔
343
        }
344
    }
25✔
345
    setMapColors(m_algorithm);
25✔
346
    emit changed(id());
25✔
347
}
348

349
QColor RGBMatrix::getColor(int i) const
10✔
350
{
351
    if (i < 0 || i >= m_rgbColors.count())
10✔
352
        return QColor();
×
353

354
    return m_rgbColors.at(i);
10✔
355
}
356

357
QVector<QColor> RGBMatrix::getColors() const
1✔
358
{
359
    return m_rgbColors;
1✔
360
}
361

362
void RGBMatrix::updateColorDelta()
16✔
363
{
364
    if (m_rgbColors.count() > 1)
16✔
365
        m_stepHandler->calculateColorDelta(m_rgbColors[0], m_rgbColors[1], m_algorithm);
16✔
366
}
16✔
367

368
void RGBMatrix::setMapColors(RGBAlgorithm *algorithm)
31✔
369
{
370
    QMutexLocker algorithmLocker(&m_algorithmMutex);
31✔
371
    if (algorithm == NULL)
31✔
372
        return;
9✔
373

374
    if (algorithm->apiVersion() < 3)
22✔
375
        return;
22✔
376

377
    if (m_group == NULL)
×
378
        m_group = doc()->fixtureGroup(fixtureGroup());
×
379

380
    QVector<unsigned int> rawColors;
×
381
    const int acceptColors = algorithm->acceptColors();
×
382
    rawColors.reserve(acceptColors);
×
383
    for (int i = 0; i < acceptColors; i++)
×
384
    {
385
        if (m_rgbColors.count() > i)
×
386
        {
387
            QColor col = m_rgbColors.at(i);
×
388
            rawColors.append(col.isValid() ? col.rgb() : 0);
×
389
        }
390
        else
391
        {
392
            rawColors.append(0);
×
393
        }
394
    }
395

396
    algorithm->rgbMapSetColors(rawColors);
×
397
}
31✔
398

399
/************************************************************************
400
 * Properties
401
 ************************************************************************/
402

403
void RGBMatrix::setProperty(QString propName, QString value)
1✔
404
{
405
    QMutexLocker algoLocker(&m_algorithmMutex);
1✔
406
    m_properties[propName] = value;
1✔
407
    if (m_algorithm != NULL && m_algorithm->type() == RGBAlgorithm::Script)
1✔
408
    {
409
        RGBScript *script = static_cast<RGBScript*> (m_algorithm);
1✔
410
        script->setProperty(propName, value);
1✔
411

412
        QVector<uint> colors = script->rgbMapGetColors();
1✔
413
        for (int i = 0; i < colors.count(); i++)
1✔
414
            setColor(i, QColor::fromRgb(colors.at(i)));
×
415
    }
1✔
416
    m_stepsCount = algorithmStepsCount();
1✔
417
}
1✔
418

419
QString RGBMatrix::property(QString propName)
3✔
420
{
421
    QMutexLocker algoLocker(&m_algorithmMutex);
3✔
422

423
    /** If the property is cached, then return it right away */
424
    QMap<QString, QString>::iterator it = m_properties.find(propName);
3✔
425
    if (it != m_properties.end())
3✔
426
        return it.value();
1✔
427

428
    /** Otherwise, let's retrieve it from the Script */
429
    if (m_algorithm != NULL && m_algorithm->type() == RGBAlgorithm::Script)
2✔
430
    {
431
        RGBScript *script = static_cast<RGBScript*> (m_algorithm);
2✔
432
        return script->property(propName);
2✔
433
    }
434

435
    return QString();
×
436
}
3✔
437

438
/****************************************************************************
439
 * Load & Save
440
 ****************************************************************************/
441

442
bool RGBMatrix::loadXML(QXmlStreamReader &root)
3✔
443
{
444
    if (root.name() != KXMLQLCFunction)
3✔
445
    {
446
        qWarning() << Q_FUNC_INFO << "Function node not found";
1✔
447
        return false;
1✔
448
    }
449

450
    if (root.attributes().value(KXMLQLCFunctionType).toString() != typeToString(Function::RGBMatrixType))
4✔
451
    {
452
        qWarning() << Q_FUNC_INFO << "Function is not an RGB matrix";
1✔
453
        return false;
1✔
454
    }
455

456
    /* Load matrix contents */
457
    while (root.readNextStartElement())
12✔
458
    {
459
        if (root.name() == KXMLQLCFunctionSpeed)
11✔
460
        {
461
            loadXMLSpeed(root);
1✔
462
        }
463
        else if (root.name() == KXMLQLCRGBAlgorithm)
10✔
464
        {
465
            setAlgorithm(RGBAlgorithm::loader(doc(), root));
1✔
466
        }
467
        else if (root.name() == KXMLQLCRGBMatrixFixtureGroup)
9✔
468
        {
469
            setFixtureGroup(root.readElementText().toUInt());
1✔
470
        }
471
        else if (root.name() == KXMLQLCFunctionDirection)
8✔
472
        {
473
            loadXMLDirection(root);
1✔
474
        }
475
        else if (root.name() == KXMLQLCFunctionRunOrder)
7✔
476
        {
477
            loadXMLRunOrder(root);
1✔
478
        }
479
        // Legacy support
480
        else if (root.name() == KXMLQLCRGBMatrixStartColor)
6✔
481
        {
482
            setColor(0, QColor::fromRgb(QRgb(root.readElementText().toUInt())));
×
483
        }
484
        else if (root.name() == KXMLQLCRGBMatrixEndColor)
6✔
485
        {
486
            setColor(1, QColor::fromRgb(QRgb(root.readElementText().toUInt())));
×
487
        }
488
        else if (root.name() == KXMLQLCRGBMatrixColor)
6✔
489
        {
490
            int colorIdx = root.attributes().value(KXMLQLCRGBMatrixColorIndex).toInt();
10✔
491
            setColor(colorIdx, QColor::fromRgb(QRgb(root.readElementText().toUInt())));
5✔
492
        }
493
        else if (root.name() == KXMLQLCRGBMatrixControlMode)
1✔
494
        {
495
            setControlMode(stringToControlMode(root.readElementText()));
1✔
496
        }
497
        else if (root.name() == KXMLQLCRGBMatrixProperty)
×
498
        {
499
            QString name = root.attributes().value(KXMLQLCRGBMatrixPropertyName).toString();
×
500
            QString value = root.attributes().value(KXMLQLCRGBMatrixPropertyValue).toString();
×
501
            setProperty(name, value);
×
502
            root.skipCurrentElement();
×
503
        }
×
504
        else if (root.name() == KXMLQLCRGBMatrixDimmerControl)
×
505
        {
506
            setDimmerControl(root.readElementText().toInt());
×
507
        }
508
        else
509
        {
510
            qWarning() << Q_FUNC_INFO << "Unknown RGB matrix tag:" << root.name();
×
511
            root.skipCurrentElement();
×
512
        }
513
    }
514

515
    return true;
1✔
516
}
517

518
bool RGBMatrix::saveXML(QXmlStreamWriter *doc)
1✔
519
{
520
    Q_ASSERT(doc != NULL);
1✔
521

522
    /* Function tag */
523
    doc->writeStartElement(KXMLQLCFunction);
2✔
524

525
    /* Common attributes */
526
    saveXMLCommon(doc);
1✔
527

528
    /* Speeds */
529
    saveXMLSpeed(doc);
1✔
530

531
    /* Direction */
532
    saveXMLDirection(doc);
1✔
533

534
    /* Run order */
535
    saveXMLRunOrder(doc);
1✔
536

537
    /* Algorithm */
538
    if (m_algorithm != NULL)
1✔
539
        m_algorithm->saveXML(doc);
1✔
540

541
    /* LEGACY - Dimmer Control */
542
    if (dimmerControl())
1✔
543
        doc->writeTextElement(KXMLQLCRGBMatrixDimmerControl, QString::number(dimmerControl()));
×
544

545
    /* Colors */
546
    for (int i = 0; i < m_rgbColors.count(); i++)
6✔
547
    {
548
        if (m_rgbColors.at(i).isValid() == false)
5✔
549
            continue;
×
550

551
        doc->writeStartElement(KXMLQLCRGBMatrixColor);
10✔
552
        doc->writeAttribute(KXMLQLCRGBMatrixColorIndex, QString::number(i));
10✔
553
        doc->writeCharacters(QString::number(m_rgbColors.at(i).rgb()));
5✔
554
        doc->writeEndElement();
5✔
555
    }
556

557
    /* Control Mode */
558
    doc->writeTextElement(KXMLQLCRGBMatrixControlMode, RGBMatrix::controlModeToString(m_controlMode));
2✔
559

560
    /* Fixture Group */
561
    doc->writeTextElement(KXMLQLCRGBMatrixFixtureGroup, QString::number(fixtureGroup()));
2✔
562

563
    /* Properties */
564
    QMapIterator<QString, QString> it(m_properties);
1✔
565
    while (it.hasNext())
1✔
566
    {
567
        it.next();
×
568
        doc->writeStartElement(KXMLQLCRGBMatrixProperty);
×
569
        doc->writeAttribute(KXMLQLCRGBMatrixPropertyName, it.key());
×
570
        doc->writeAttribute(KXMLQLCRGBMatrixPropertyValue, it.value());
×
571
        doc->writeEndElement();
×
572
    }
573

574
    /* End the <Function> tag */
575
    doc->writeEndElement();
1✔
576

577
    return true;
1✔
578
}
1✔
579

580
/****************************************************************************
581
 * Running
582
 ****************************************************************************/
583

584
void RGBMatrix::tap()
×
585
{
586
    if (stopped() == false)
×
587
    {
588
        FixtureGroup *grp = doc()->fixtureGroup(fixtureGroup());
×
589
        // Filter out taps that are too close to each other
590
        if (grp != NULL && uint(m_roundTime.elapsed()) >= (duration() / 4))
×
591
        {
592
            roundCheck();
×
593
            resetElapsed();
×
594
        }
595
    }
596
}
×
597

598
void RGBMatrix::checkEngineCreation()
×
599
{
600
    m_runAlgorithm = m_algorithm;
×
601
    m_requestEngineCreation = false;
×
602
}
×
603

604
void RGBMatrix::preRun(MasterTimer *timer)
×
605
{
606
    {
607
        QMutexLocker algorithmLocker(&m_algorithmMutex);
×
608

609
        m_group = doc()->fixtureGroup(m_fixtureGroupID);
×
610
        if (m_group == NULL)
×
611
        {
612
            // No fixture group to control
613
            stop(FunctionParent::master());
×
614
            return;
×
615
        }
616

617
        if (m_algorithm != NULL)
×
618
        {
619
            checkEngineCreation();
×
620

621
            // Copy direction from parent class direction
622
            m_stepHandler->initializeDirection(direction(), m_rgbColors[0], m_rgbColors[1], m_stepsCount, m_runAlgorithm);
×
623

624
            if (m_runAlgorithm->type() == RGBAlgorithm::Script)
×
625
            {
626
                RGBScript *script = static_cast<RGBScript*> (m_runAlgorithm);
×
627
                QMapIterator<QString, QString> it(m_properties);
×
628
                while (it.hasNext())
×
629
                {
630
                    it.next();
×
631
                    script->setProperty(it.key(), it.value());
×
632
                }
633
            }
×
634
            else if (m_runAlgorithm->type() == RGBAlgorithm::Image)
×
635
            {
636
                RGBImage *image = static_cast<RGBImage*> (m_runAlgorithm);
×
637
                if (image->animatedSource())
×
638
                    image->rewindAnimation();
×
639
            }
640
        }
641
    }
×
642

643
    m_roundTime.restart();
×
644

645
    Function::preRun(timer);
×
646
}
647

648
void RGBMatrix::write(MasterTimer *timer, QList<Universe *> universes)
×
649
{
650
    Q_UNUSED(timer);
651

652
    {
653
        QMutexLocker algorithmLocker(&m_algorithmMutex);
×
654
        if (m_group == NULL)
×
655
        {
656
            // No fixture group to control
657
            stop(FunctionParent::master());
×
658
            return;
×
659
        }
660

661
        // No time to do anything.
662
        if (duration() == 0)
×
663
            return;
×
664

665
        if (m_algorithm != NULL && m_requestEngineCreation)
×
666
            checkEngineCreation();
×
667

668
        // Invalid/nonexistent script
669
        if (m_runAlgorithm == NULL || m_runAlgorithm->apiVersion() == 0)
×
670
            return;
×
671

672
        if (isPaused() == false)
×
673
        {
674
            // Get a new map every time elapsed is reset to zero
675
            if (elapsed() < MasterTimer::tick())
×
676
            {
677
                if (tempoType() == Beats)
×
678
                    m_stepBeatDuration = beatsToTime(duration(), timer->beatTimeDuration());
×
679

680
                //qDebug() << "RGBMatrix step" << m_stepHandler->currentStepIndex() << ", color:" << QString::number(m_stepHandler->stepColor().rgb(), 16);
681
                m_runAlgorithm->rgbMap(m_group->size(), m_stepHandler->stepColor().rgb(),
×
682
                                       m_stepHandler->currentStepIndex(), m_stepHandler->m_map);
×
683
                updateMapChannels(m_stepHandler->m_map, m_group, universes);
×
684
            }
685
        }
686
    }
×
687

688
    if (isPaused() == false)
×
689
    {
690
        // Increment the ms elapsed time
691
        incrementElapsed();
×
692

693
        /* Check if we need to change direction, stop completely or go to next step
694
         * The cases are:
695
         * 1- time tempo type: act normally, on ms elapsed time
696
         * 2- beat tempo type, beat occurred: check if the elapsed beats is a multiple of
697
         *    the step beat duration. If so, proceed to the next step
698
         * 3- beat tempo type, not beat: if the ms elapsed time reached the step beat
699
         *    duration in ms, and the ms time to the next beat is not less than 1/16 of
700
         *    the step beat duration in ms, then proceed to the next step. If the ms time to the
701
         *    next beat is less than 1/16 of the step beat duration in ms, then defer the step
702
         *    change to case #2, to resync the matrix to the next beat
703
         */
704
        if (tempoType() == Time && elapsed() >= duration())
×
705
        {
706
            roundCheck();
×
707
        }
708
        else if (tempoType() == Beats)
×
709
        {
710
            if (timer->isBeat())
×
711
            {
712
                incrementElapsedBeats();
×
713
                qDebug() << "Elapsed beats:" << elapsedBeats() << ", time elapsed:" << elapsed() << ", step time:" << m_stepBeatDuration;
×
714
                if (elapsedBeats() % duration() == 0)
×
715
                {
716
                    roundCheck();
×
717
                    resetElapsed();
×
718
                }
719
            }
720
            else if (elapsed() >= m_stepBeatDuration && (uint)timer->timeToNextBeat() > m_stepBeatDuration / 16)
×
721
            {
722
                qDebug() << "Elapsed exceeded";
×
723
                roundCheck();
×
724
            }
725
        }
726
    }
727
}
728

729
void RGBMatrix::postRun(MasterTimer *timer, QList<Universe *> universes)
×
730
{
731
    uint fadeout = overrideFadeOutSpeed() == defaultSpeed() ? fadeOutSpeed() : overrideFadeOutSpeed();
×
732

733
    /* If no fade out is needed, dismiss all the requested faders.
734
     * Otherwise, set all the faders to fade out and let Universe dismiss them
735
     * when done */
736
    if (fadeout == 0)
×
737
    {
738
        dismissAllFaders();
×
739
    }
740
    else
741
    {
742
        if (tempoType() == Beats)
×
743
            fadeout = beatsToTime(fadeout, timer->beatTimeDuration());
×
744

745
        foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
×
746
        {
747
            if (!fader.isNull())
×
748
                fader->setFadeOut(true, fadeout);
×
749
        }
×
750
    }
751

752
    m_fadersMap.clear();
×
753

754
    {
755
        QMutexLocker algorithmLocker(&m_algorithmMutex);
×
756
        checkEngineCreation();
×
757
        if (m_runAlgorithm != NULL)
×
758
            m_runAlgorithm->postRun();
×
759
    }
×
760

761
    Function::postRun(timer, universes);
×
762
}
×
763

764
void RGBMatrix::roundCheck()
×
765
{
766
    QMutexLocker algorithmLocker(&m_algorithmMutex);
×
767
    if (m_algorithm == NULL)
×
768
        return;
×
769

770
    if (m_stepHandler->checkNextStep(runOrder(), m_rgbColors[0], m_rgbColors[1], m_stepsCount) == false)
×
771
        stop(FunctionParent::master());
×
772

773
    m_roundTime.restart();
×
774

775
    if (tempoType() == Beats)
×
776
        roundElapsed(m_stepBeatDuration);
×
777
    else
778
        roundElapsed(duration());
×
779
}
×
780

781
FadeChannel *RGBMatrix::getFader(Universe *universe, quint32 fixtureID, quint32 channel)
×
782
{
783
    // get the universe Fader first. If doesn't exist, create it
784
    if (universe == NULL)
×
785
        return NULL;
×
786

787
    QSharedPointer<GenericFader> fader = m_fadersMap.value(universe->id(), QSharedPointer<GenericFader>());
×
788
    if (fader.isNull())
×
789
    {
790
        fader = universe->requestFader();
×
791
        fader->adjustIntensity(getAttributeValue(Intensity));
×
792
        fader->setBlendMode(blendMode());
×
793
        fader->setName(name());
×
794
        fader->setParentFunctionID(id());
×
795
        m_fadersMap[universe->id()] = fader;
×
796
    }
797

798
    return fader->getChannelFader(doc(), universe, fixtureID, channel);
×
799
}
×
800

801
void RGBMatrix::updateFaderValues(FadeChannel *fc, uchar value, uint fadeTime)
×
802
{
803
    fc->setStart(fc->current());
×
804
    fc->setTarget(value);
×
805
    fc->setElapsed(0);
×
806
    fc->setReady(false);
×
807
    // fade in/out depends on target value
808
    if (value == 0)
×
809
        fc->setFadeTime(fadeOutSpeed());
×
810
    else
811
        fc->setFadeTime(fadeTime);
×
812
}
×
813

814
void RGBMatrix::updateMapChannels(const RGBMap& map, const FixtureGroup *grp, QList<Universe *> universes)
×
815
{
816
    uint fadeTime = (overrideFadeInSpeed() == defaultSpeed()) ? fadeInSpeed() : overrideFadeInSpeed();
×
817

818
    // Create/modify fade channels for ALL heads in the group
819
    QMapIterator<QLCPoint, GroupHead> it(grp->headsMap());
×
820
    while (it.hasNext())
×
821
    {
822
        it.next();
×
823
        QLCPoint pt = it.key();
×
824
        GroupHead grpHead = it.value();
×
825
        Fixture *fxi = doc()->fixture(grpHead.fxi);
×
826
        if (fxi == NULL)
×
827
            continue;
×
828

829
        QLCFixtureHead head = fxi->head(grpHead.head);
×
830

831
        if (pt.y() >= map.count() || pt.x() >= map[pt.y()].count())
×
832
            continue;
×
833

834
        uint col = map[pt.y()][pt.x()];
×
835
        QVector<quint32> channelList;
×
836
        QVector<uchar> valueList;
×
837

838
        if (m_controlMode == ControlModeRgb)
×
839
        {
840
            channelList = head.rgbChannels();
×
841

842
            if (channelList.size() == 3)
×
843
            {
844
                valueList.append(qRed(col));
×
845
                valueList.append(qGreen(col));
×
846
                valueList.append(qBlue(col));
×
847
            }
848
            else
849
            {
850
                channelList = head.cmyChannels();
×
851

852
                if (channelList.size() == 3)
×
853
                {
854
                    // CMY color mixing
855
                    QColor cmyCol(col);
×
856
                    valueList.append(cmyCol.cyan());
×
857
                    valueList.append(cmyCol.magenta());
×
858
                    valueList.append(cmyCol.yellow());
×
859
                }
860
            }
861
        }
862
        else if (m_controlMode == ControlModeShutter)
×
863
        {
864
            channelList = head.shutterChannels();
×
865

866
            if (channelList.size())
×
867
            {
868
                // make sure only one channel is in the list
869
                channelList.resize(1);
×
870
                valueList.append(rgbToGrey(col));
×
871
            }
872
        }
873
        else if (m_controlMode == ControlModeDimmer || m_dimmerControl)
×
874
        {
875
            // Collect all dimmers that affect current head:
876
            // They are the master dimmer (affects whole fixture)
877
            // and per-head dimmer.
878
            //
879
            // If there are no RGB or CMY channels, the least important* dimmer channel
880
            // is used to create grayscale image.
881
            //
882
            // The rest of the dimmer channels are set to full if dimmer control is
883
            // enabled and target color is > 0 (see
884
            // http://www.qlcplus.org/forum/viewtopic.php?f=29&t=11090)
885
            //
886
            // Note: If there is only one head, and only one dimmer channel,
887
            // make it a master dimmer in fixture definition.
888
            //
889
            // *least important - per head dimmer if present,
890
            // otherwise per fixture dimmer if present
891

892
            quint32 masterDim = fxi->masterIntensityChannel();
×
893
            quint32 headDim = head.channelNumber(QLCChannel::Intensity, QLCChannel::MSB);
×
894

895
            if (masterDim != QLCChannel::invalid())
×
896
            {
897
                channelList.append(masterDim);
×
898
                valueList.append(rgbToGrey(col));
×
899
            }
900

901
            if (headDim != QLCChannel::invalid() && headDim != masterDim)
×
902
            {
903
                channelList.append(headDim);
×
904
                valueList.append(rgbToGrey(col) == 0 ? 0 : 255);
×
905
            }
906
        }
×
907
        else
908
        {
909
            if (m_controlMode == ControlModeWhite)
×
910
                channelList.append(head.channelNumber(QLCChannel::White, QLCChannel::MSB));
×
911
            else if (m_controlMode == ControlModeAmber)
×
912
                channelList.append(head.channelNumber(QLCChannel::Amber, QLCChannel::MSB));
×
913
            else if (m_controlMode == ControlModeUV)
×
914
                channelList.append(head.channelNumber(QLCChannel::UV, QLCChannel::MSB));
×
915

916
            valueList.append(rgbToGrey(col));
×
917
        }
918

919
        quint32 absAddress = fxi->universeAddress();
×
920

921
        for (int i = 0; i < channelList.count(); i++)
×
922
        {
923
            if (channelList.at(i) == QLCChannel::invalid())
×
924
                continue;
×
925

926
            quint32 universeIndex = floor((absAddress + channelList.at(i)) / 512);
×
927

928
            FadeChannel *fc = getFader(universes.at(universeIndex), grpHead.fxi, channelList.at(i));
×
929
            updateFaderValues(fc, valueList.at(i), fadeTime);
×
930
        }
931
    }
×
932
}
×
933

934
uchar RGBMatrix::rgbToGrey(uint col)
×
935
{
936
    // the weights are taken from
937
    // https://en.wikipedia.org/wiki/YUV#SDTV_with_BT.601
938
    return (0.299 * qRed(col) + 0.587 * qGreen(col) + 0.114 * qBlue(col));
×
939
}
940

941
/*********************************************************************
942
 * Attributes
943
 *********************************************************************/
944

945
int RGBMatrix::adjustAttribute(qreal fraction, int attributeId)
×
946
{
947
    int attrIndex = Function::adjustAttribute(fraction, attributeId);
×
948

949
    if (attrIndex == Intensity)
×
950
    {
951
        foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
×
952
        {
953
            if (!fader.isNull())
×
954
                fader->adjustIntensity(getAttributeValue(Function::Intensity));
×
955
        }
×
956
    }
957

958
    return attrIndex;
×
959
}
960

961
/*************************************************************************
962
 * Blend mode
963
 *************************************************************************/
964

965
void RGBMatrix::setBlendMode(Universe::BlendMode mode)
×
966
{
967
    if (mode == blendMode())
×
968
        return;
×
969

970
    foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
×
971
    {
972
        if (!fader.isNull())
×
973
            fader->setBlendMode(mode);
×
974
    }
×
975

976
    Function::setBlendMode(mode);
×
977
    emit changed(id());
×
978
}
979

980
/*************************************************************************
981
 * Control Mode
982
 *************************************************************************/
983

984
RGBMatrix::ControlMode RGBMatrix::controlMode() const
2✔
985
{
986
    return m_controlMode;
2✔
987
}
988

989
void RGBMatrix::setControlMode(RGBMatrix::ControlMode mode)
3✔
990
{
991
    m_controlMode = mode;
3✔
992
    emit changed(id());
3✔
993
}
3✔
994

995
RGBMatrix::ControlMode RGBMatrix::stringToControlMode(QString mode)
1✔
996
{
997
    if (mode == KXMLQLCRGBMatrixControlModeRgb)
1✔
998
        return ControlModeRgb;
1✔
999
    else if (mode == KXMLQLCRGBMatrixControlModeAmber)
×
1000
        return ControlModeAmber;
×
1001
    else if (mode == KXMLQLCRGBMatrixControlModeWhite)
×
1002
        return ControlModeWhite;
×
1003
    else if (mode == KXMLQLCRGBMatrixControlModeUV)
×
1004
        return ControlModeUV;
×
1005
    else if (mode == KXMLQLCRGBMatrixControlModeDimmer)
×
1006
        return ControlModeDimmer;
×
1007
    else if (mode == KXMLQLCRGBMatrixControlModeShutter)
×
1008
        return ControlModeShutter;
×
1009

1010
    return ControlModeRgb;
×
1011
}
1012

1013
QString RGBMatrix::controlModeToString(RGBMatrix::ControlMode mode)
1✔
1014
{
1015
    switch(mode)
1✔
1016
    {
1017
        default:
1✔
1018
        case ControlModeRgb:
1019
            return QString(KXMLQLCRGBMatrixControlModeRgb);
1✔
1020
        break;
1021
        case ControlModeAmber:
×
1022
            return QString(KXMLQLCRGBMatrixControlModeAmber);
×
1023
        break;
1024
        case ControlModeWhite:
×
1025
            return QString(KXMLQLCRGBMatrixControlModeWhite);
×
1026
        break;
1027
        case ControlModeUV:
×
1028
            return QString(KXMLQLCRGBMatrixControlModeUV);
×
1029
        break;
1030
        case ControlModeDimmer:
×
1031
            return QString(KXMLQLCRGBMatrixControlModeDimmer);
×
1032
        break;
1033
        case ControlModeShutter:
×
1034
            return QString(KXMLQLCRGBMatrixControlModeShutter);
×
1035
        break;
1036
    }
1037
}
1038

1039

1040
/*************************************************************************
1041
 *************************************************************************
1042
 *                          RGBMatrixStep class
1043
 *************************************************************************
1044
 *************************************************************************/
1045

1046
RGBMatrixStep::RGBMatrixStep()
10✔
1047
    : m_direction(Function::Forward)
10✔
1048
    , m_currentStepIndex(0)
10✔
1049
    , m_stepColor(QColor())
10✔
1050
    , m_crDelta(0)
10✔
1051
    , m_cgDelta(0)
10✔
1052
    , m_cbDelta(0)
10✔
1053
{
1054

1055
}
10✔
1056

1057
void RGBMatrixStep::setCurrentStepIndex(int index)
×
1058
{
1059
    m_currentStepIndex = index;
×
1060
}
×
1061

1062
int RGBMatrixStep::currentStepIndex() const
1✔
1063
{
1064
    return m_currentStepIndex;
1✔
1065
}
1066

1067
void RGBMatrixStep::calculateColorDelta(QColor startColor, QColor endColor, RGBAlgorithm *algorithm)
16✔
1068
{
1069
    m_crDelta = 0;
16✔
1070
    m_cgDelta = 0;
16✔
1071
    m_cbDelta = 0;
16✔
1072

1073
    if (endColor.isValid() && algorithm != NULL && algorithm->acceptColors() > 1)
16✔
1074
    {
1075
        m_crDelta = endColor.red() - startColor.red();
10✔
1076
        m_cgDelta = endColor.green() - startColor.green();
10✔
1077
        m_cbDelta = endColor.blue() - startColor.blue();
10✔
1078

1079
        //qDebug() << "Color deltas:" << m_crDelta << m_cgDelta << m_cbDelta;
1080
    }
1081
}
16✔
1082

1083
void RGBMatrixStep::setStepColor(QColor color)
×
1084
{
1085
    m_stepColor = color;
×
1086
}
×
1087

1088
QColor RGBMatrixStep::stepColor()
6✔
1089
{
1090
    return m_stepColor;
6✔
1091
}
1092

1093
void RGBMatrixStep::updateStepColor(int stepIndex, QColor startColor, int stepsCount)
×
1094
{
1095
    if (stepsCount <= 0)
×
1096
        return;
×
1097

1098
    if (stepsCount == 1)
×
1099
    {
1100
        m_stepColor = startColor;
×
1101
    }
1102
    else
1103
    {
1104
        m_stepColor.setRed(startColor.red() + (m_crDelta * stepIndex / (stepsCount - 1)));
×
1105
        m_stepColor.setGreen(startColor.green() + (m_cgDelta * stepIndex / (stepsCount - 1)));
×
1106
        m_stepColor.setBlue(startColor.blue() + (m_cbDelta * stepIndex / (stepsCount - 1)));
×
1107
    }
1108

1109
    //qDebug() << "RGBMatrix step" << stepIndex << ", color:" << QString::number(m_stepColor.rgb(), 16);
1110
}
1111

1112
void RGBMatrixStep::initializeDirection(Function::Direction direction, QColor startColor, QColor endColor, int stepsCount, RGBAlgorithm *algorithm)
×
1113
{
1114
    m_direction = direction;
×
1115

1116
    if (m_direction == Function::Forward)
×
1117
    {
1118
        setCurrentStepIndex(0);
×
1119
        setStepColor(startColor);
×
1120
    }
1121
    else
1122
    {
1123
        setCurrentStepIndex(stepsCount - 1);
×
1124

1125
        if (endColor.isValid())
×
1126
            setStepColor(endColor);
×
1127
        else
1128
            setStepColor(startColor);
×
1129
    }
1130

1131
    calculateColorDelta(startColor, endColor, algorithm);
×
1132
}
×
1133

1134
bool RGBMatrixStep::checkNextStep(Function::RunOrder order,
×
1135
                                  QColor startColor, QColor endColor, int stepsNumber)
1136
{
1137
    if (order == Function::PingPong)
×
1138
    {
1139
        if (m_direction == Function::Forward && (m_currentStepIndex + 1) == stepsNumber)
×
1140
        {
1141
            m_direction = Function::Backward;
×
1142
            m_currentStepIndex = stepsNumber - 2;
×
1143
            if (endColor.isValid())
×
1144
                m_stepColor = endColor;
×
1145

1146
            updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1147
        }
1148
        else if (m_direction == Function::Backward && (m_currentStepIndex - 1) < 0)
×
1149
        {
1150
            m_direction = Function::Forward;
×
1151
            m_currentStepIndex = 1;
×
1152
            m_stepColor = startColor;
×
1153
            updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1154
        }
1155
        else
1156
        {
1157
            if (m_direction == Function::Forward)
×
1158
                m_currentStepIndex++;
×
1159
            else
1160
                m_currentStepIndex--;
×
1161
            updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1162
        }
1163
    }
1164
    else if (order == Function::SingleShot)
×
1165
    {
1166
        if (m_direction == Function::Forward)
×
1167
        {
1168
            if (m_currentStepIndex >= stepsNumber - 1)
×
1169
                return false;
×
1170
            else
1171
            {
1172
                m_currentStepIndex++;
×
1173
                updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1174
            }
1175
        }
1176
        else
1177
        {
1178
            if (m_currentStepIndex <= 0)
×
1179
                return false;
×
1180
            else
1181
            {
1182
                m_currentStepIndex--;
×
1183
                updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1184
            }
1185
        }
1186
    }
1187
    else
1188
    {
1189
        if (m_direction == Function::Forward)
×
1190
        {
1191
            if (m_currentStepIndex >= stepsNumber - 1)
×
1192
            {
1193
                m_currentStepIndex = 0;
×
1194
                m_stepColor = startColor;
×
1195
            }
1196
            else
1197
            {
1198
                m_currentStepIndex++;
×
1199
                updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1200
            }
1201
        }
1202
        else
1203
        {
1204
            if (m_currentStepIndex <= 0)
×
1205
            {
1206
                m_currentStepIndex = stepsNumber - 1;
×
1207
                if (endColor.isValid())
×
1208
                    m_stepColor = endColor;
×
1209
            }
1210
            else
1211
            {
1212
                m_currentStepIndex--;
×
1213
                updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1214
            }
1215
        }
1216
    }
1217

1218
    return true;
×
1219
}
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