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

mcallegari / qlcplus / 14790544976

02 May 2025 07:09AM UTC coverage: 31.845% (-0.03%) from 31.87%
14790544976

push

github

web-flow
Merge pull request #1742 from shaforostoff/rgbmatrix_memleak

Fix high RAM usage related to RGB scripts

6 of 13 new or added lines in 8 files covered. (46.15%)

13 existing lines in 3 files now uncovered.

14675 of 46082 relevant lines covered (31.85%)

26491.26 hits per line

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

42.46
/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      QString("MonoColor")
39
#define KXMLQLCRGBMatrixEndColor        QString("EndColor")
40
#define KXMLQLCRGBMatrixColor           QString("Color")
41
#define KXMLQLCRGBMatrixColorIndex      QString("Index")
42

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

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

50
#define KXMLQLCRGBMatrixControlMode         QString("ControlMode")
51
#define KXMLQLCRGBMatrixControlModeRgb      QString("RGB")
52
#define KXMLQLCRGBMatrixControlModeAmber    QString("Amber")
53
#define KXMLQLCRGBMatrixControlModeWhite    QString("White")
54
#define KXMLQLCRGBMatrixControlModeUV       QString("UV")
55
#define KXMLQLCRGBMatrixControlModeDimmer   QString("Dimmer")
56
#define KXMLQLCRGBMatrixControlModeShutter  QString("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_roundTime(new QElapsedTimer())
9✔
75
    , m_stepsCount(0)
9✔
76
    , m_stepBeatDuration(0)
9✔
77
    , m_controlMode(RGBMatrix::ControlModeRgb)
18✔
78
{
79
    setName(tr("New RGB Matrix"));
9✔
80
    setDuration(500);
9✔
81

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

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

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

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

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

106
    if (m_algorithm == NULL)
1✔
107
        return;
108

109
    FixtureGroup *grp = doc()->fixtureGroup(fixtureGroup());
1✔
110
    if (grp == NULL)
1✔
111
        return;
112

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

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

121
    if (m_algorithm == NULL)
3✔
122
        return 0;
123

124
    FixtureGroup *grp = doc()->fixtureGroup(fixtureGroup());
3✔
125
    if (grp == NULL)
3✔
126
        return 0;
127

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

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

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

142
/****************************************************************************
143
 * Copying
144
 ****************************************************************************/
145

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

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

162
    return copy;
1✔
163
}
164

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

171
    setDimmerControl(mtx->dimmerControl());
1✔
172
    setFixtureGroup(mtx->fixtureGroup());
1✔
173

174
    m_rgbColors.clear();
1✔
175
    foreach (QColor col, mtx->getColors())
7✔
176
        m_rgbColors.append(col);
5✔
177

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

183
    setControlMode(mtx->controlMode());
1✔
184

185
    return Function::copyFrom(function);
1✔
186
}
187

188
/****************************************************************************
189
 * Fixtures
190
 ****************************************************************************/
191

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

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

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

212
    return QList<quint32>();
213
}
214

215
/****************************************************************************
216
 * Algorithm
217
 ****************************************************************************/
218

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

226
        m_requestEngineCreation = true;
13✔
227

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

249
    emit changed(id());
13✔
250
}
13✔
251

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

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

269

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

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

279
    if (m_algorithm == NULL)
22✔
280
        return 0;
281

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

286
    return 0;
287
}
288

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

295
    if (m_group == NULL)
7✔
296
        m_group = doc()->fixtureGroup(fixtureGroup());
1✔
297

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

305
/****************************************************************************
306
 * Color
307
 ****************************************************************************/
308

309
void RGBMatrix::setColor(int i, QColor c)
25✔
310
{
311
    if (i < 0)
25✔
312
        return;
313

314
    if (i >= m_rgbColors.count())
25✔
315
        m_rgbColors.resize(i + 1);
×
316

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

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

335
    return m_rgbColors.at(i);
336
}
337

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

343
void RGBMatrix::updateColorDelta()
16✔
344
{
345
    m_stepHandler->calculateColorDelta(m_rgbColors[0], m_rgbColors[1], m_algorithm);
32✔
346
}
16✔
347

348
void RGBMatrix::setMapColors(RGBAlgorithm *algorithm)
31✔
349
{
350
    QMutexLocker algorithmLocker(&m_algorithmMutex);
31✔
351
    if (algorithm == NULL)
31✔
352
        return;
353

354
    if (algorithm->apiVersion() < 3)
22✔
355
        return;
356

357
    if (m_group == NULL)
×
358
        m_group = doc()->fixtureGroup(fixtureGroup());
×
359

360
    if (m_group != NULL)
×
361
    {
362
        QVector<unsigned int> rawColors;
363
        for (int i = 0; i < algorithm->acceptColors(); i++)
×
364
        {
365
            QColor col = m_rgbColors.at(i);
366
            rawColors.append(col.isValid() ? col.rgb() : 0);
×
367
        }
368

369
        algorithm->rgbMapSetColors(rawColors);
×
370
    }
×
371
}
372

373
/************************************************************************
374
 * Properties
375
 ************************************************************************/
376

377
void RGBMatrix::setProperty(QString propName, QString value)
1✔
378
{
379
    QMutexLocker algoLocker(&m_algorithmMutex);
1✔
380
    m_properties[propName] = value;
1✔
381
    if (m_algorithm != NULL && m_algorithm->type() == RGBAlgorithm::Script)
1✔
382
    {
383
        RGBScript *script = static_cast<RGBScript*> (m_algorithm);
1✔
384
        script->setProperty(propName, value);
1✔
385

386
        QVector<uint> colors = script->rgbMapGetColors();
1✔
387
        for (int i = 0; i < colors.count(); i++)
1✔
388
            setColor(i, QColor::fromRgb(colors.at(i)));
×
389
    }
1✔
390
    m_stepsCount = algorithmStepsCount();
1✔
391
}
1✔
392

393
QString RGBMatrix::property(QString propName)
3✔
394
{
395
    QMutexLocker algoLocker(&m_algorithmMutex);
3✔
396

397
    /** If the property is cached, then return it right away */
398
    QMap<QString, QString>::iterator it = m_properties.find(propName);
3✔
399
    if (it != m_properties.end())
3✔
400
        return it.value();
401

402
    /** Otherwise, let's retrieve it from the Script */
403
    if (m_algorithm != NULL && m_algorithm->type() == RGBAlgorithm::Script)
2✔
404
    {
405
        RGBScript *script = static_cast<RGBScript*> (m_algorithm);
2✔
406
        return script->property(propName);
2✔
407
    }
408

409
    return QString();
410
}
411

412
/****************************************************************************
413
 * Load & Save
414
 ****************************************************************************/
415

416
bool RGBMatrix::loadXML(QXmlStreamReader &root)
3✔
417
{
418
    if (root.name() != KXMLQLCFunction)
6✔
419
    {
420
        qWarning() << Q_FUNC_INFO << "Function node not found";
1✔
421
        return false;
1✔
422
    }
423

424
    if (root.attributes().value(KXMLQLCFunctionType).toString() != typeToString(Function::RGBMatrixType))
4✔
425
    {
426
        qWarning() << Q_FUNC_INFO << "Function is not an RGB matrix";
1✔
427
        return false;
1✔
428
    }
429

430
    /* Load matrix contents */
431
    while (root.readNextStartElement())
12✔
432
    {
433
        if (root.name() == KXMLQLCFunctionSpeed)
22✔
434
        {
435
            loadXMLSpeed(root);
1✔
436
        }
437
        else if (root.name() == KXMLQLCRGBAlgorithm)
20✔
438
        {
439
            setAlgorithm(RGBAlgorithm::loader(doc(), root));
1✔
440
        }
441
        else if (root.name() == KXMLQLCRGBMatrixFixtureGroup)
18✔
442
        {
443
            setFixtureGroup(root.readElementText().toUInt());
1✔
444
        }
445
        else if (root.name() == KXMLQLCFunctionDirection)
16✔
446
        {
447
            loadXMLDirection(root);
1✔
448
        }
449
        else if (root.name() == KXMLQLCFunctionRunOrder)
14✔
450
        {
451
            loadXMLRunOrder(root);
1✔
452
        }
453
        // Legacy support
454
        else if (root.name() == KXMLQLCRGBMatrixStartColor)
12✔
455
        {
456
            setColor(0, QColor::fromRgb(QRgb(root.readElementText().toUInt())));
×
457
        }
458
        else if (root.name() == KXMLQLCRGBMatrixEndColor)
12✔
459
        {
460
            setColor(1, QColor::fromRgb(QRgb(root.readElementText().toUInt())));
×
461
        }
462
        else if (root.name() == KXMLQLCRGBMatrixColor)
12✔
463
        {
464
            int colorIdx = root.attributes().value(KXMLQLCRGBMatrixColorIndex).toInt();
5✔
465
            setColor(colorIdx, QColor::fromRgb(QRgb(root.readElementText().toUInt())));
5✔
466
        }
467
        else if (root.name() == KXMLQLCRGBMatrixControlMode)
2✔
468
        {
469
            setControlMode(stringToControlMode(root.readElementText()));
1✔
470
        }
471
        else if (root.name() == KXMLQLCRGBMatrixProperty)
×
472
        {
473
            QString name = root.attributes().value(KXMLQLCRGBMatrixPropertyName).toString();
×
474
            QString value = root.attributes().value(KXMLQLCRGBMatrixPropertyValue).toString();
×
475
            setProperty(name, value);
×
476
            root.skipCurrentElement();
×
477
        }
×
478
        else if (root.name() == KXMLQLCRGBMatrixDimmerControl)
×
479
        {
480
            setDimmerControl(root.readElementText().toInt());
×
481
        }
482
        else
483
        {
484
            qWarning() << Q_FUNC_INFO << "Unknown RGB matrix tag:" << root.name();
×
485
            root.skipCurrentElement();
×
486
        }
487
    }
488

489
    return true;
490
}
491

492
bool RGBMatrix::saveXML(QXmlStreamWriter *doc)
1✔
493
{
494
    Q_ASSERT(doc != NULL);
495

496
    /* Function tag */
497
    doc->writeStartElement(KXMLQLCFunction);
1✔
498

499
    /* Common attributes */
500
    saveXMLCommon(doc);
1✔
501

502
    /* Speeds */
503
    saveXMLSpeed(doc);
1✔
504

505
    /* Direction */
506
    saveXMLDirection(doc);
1✔
507

508
    /* Run order */
509
    saveXMLRunOrder(doc);
1✔
510

511
    /* Algorithm */
512
    if (m_algorithm != NULL)
1✔
513
        m_algorithm->saveXML(doc);
1✔
514

515
    /* LEGACY - Dimmer Control */
516
    if (dimmerControl())
1✔
517
        doc->writeTextElement(KXMLQLCRGBMatrixDimmerControl, QString::number(dimmerControl()));
×
518

519
    /* Colors */
520
    for (int i = 0; i < m_rgbColors.count(); i++)
6✔
521
    {
522
        doc->writeStartElement(KXMLQLCRGBMatrixColor);
5✔
523
        doc->writeAttribute(KXMLQLCRGBMatrixColorIndex, QString::number(i));
5✔
524
        doc->writeCharacters(QString::number(m_rgbColors.at(i).rgb()));
5✔
525
        doc->writeEndElement();
5✔
526
    }
527

528
    /* Control Mode */
529
    doc->writeTextElement(KXMLQLCRGBMatrixControlMode, RGBMatrix::controlModeToString(m_controlMode));
1✔
530

531
    /* Fixture Group */
532
    doc->writeTextElement(KXMLQLCRGBMatrixFixtureGroup, QString::number(fixtureGroup()));
1✔
533

534
    /* Properties */
535
    QMapIterator<QString, QString> it(m_properties);
1✔
536
    while (it.hasNext())
1✔
537
    {
538
        it.next();
×
539
        doc->writeStartElement(KXMLQLCRGBMatrixProperty);
×
540
        doc->writeAttribute(KXMLQLCRGBMatrixPropertyName, it.key());
×
541
        doc->writeAttribute(KXMLQLCRGBMatrixPropertyValue, it.value());
×
542
        doc->writeEndElement();
×
543
    }
544

545
    /* End the <Function> tag */
546
    doc->writeEndElement();
1✔
547

548
    return true;
1✔
549
}
550

551
/****************************************************************************
552
 * Running
553
 ****************************************************************************/
554

555
void RGBMatrix::tap()
×
556
{
557
    if (stopped() == false)
×
558
    {
559
        FixtureGroup *grp = doc()->fixtureGroup(fixtureGroup());
×
560
        // Filter out taps that are too close to each other
561
        if (grp != NULL && uint(m_roundTime->elapsed()) >= (duration() / 4))
×
562
        {
563
            roundCheck();
×
564
            resetElapsed();
×
565
        }
566
    }
567
}
×
568

569
void RGBMatrix::checkEngineCreation()
×
570
{
UNCOV
571
    m_runAlgorithm = m_algorithm;
×
UNCOV
572
    m_requestEngineCreation = false;
×
573
}
×
574

575
void RGBMatrix::preRun(MasterTimer *timer)
×
576
{
577
    {
578
        QMutexLocker algorithmLocker(&m_algorithmMutex);
×
579

580
        m_group = doc()->fixtureGroup(m_fixtureGroupID);
×
581
        if (m_group == NULL)
×
582
        {
583
            // No fixture group to control
584
            stop(FunctionParent::master());
×
585
            return;
586
        }
587

588
        if (m_algorithm != NULL)
×
589
        {
590
            checkEngineCreation();
×
591

592
            // Copy direction from parent class direction
593
            m_stepHandler->initializeDirection(direction(), m_rgbColors[0], m_rgbColors[1], m_stepsCount, m_runAlgorithm);
×
594

595
            if (m_runAlgorithm->type() == RGBAlgorithm::Script)
×
596
            {
597
                RGBScript *script = static_cast<RGBScript*> (m_runAlgorithm);
×
598
                QMapIterator<QString, QString> it(m_properties);
×
599
                while (it.hasNext())
×
600
                {
601
                    it.next();
×
602
                    script->setProperty(it.key(), it.value());
×
603
                }
604
            }
605
            else if (m_runAlgorithm->type() == RGBAlgorithm::Image)
×
606
            {
607
                RGBImage *image = static_cast<RGBImage*> (m_runAlgorithm);
×
608
                if (image->animatedSource())
×
609
                    image->rewindAnimation();
×
610
            }
611
        }
612
    }
613

614
    m_roundTime->restart();
×
615

616
    Function::preRun(timer);
×
617
}
618

619
void RGBMatrix::write(MasterTimer *timer, QList<Universe *> universes)
×
620
{
621
    Q_UNUSED(timer);
622

623
    {
624
        QMutexLocker algorithmLocker(&m_algorithmMutex);
×
625
        if (m_group == NULL)
×
626
        {
627
            // No fixture group to control
628
            stop(FunctionParent::master());
×
629
            return;
630
        }
631

632
        // No time to do anything.
633
        if (duration() == 0)
×
634
            return;
635

636
        if (m_algorithm != NULL && m_requestEngineCreation)
×
637
            checkEngineCreation();
×
638

639
        // Invalid/nonexistent script
640
        if (m_runAlgorithm == NULL || m_runAlgorithm->apiVersion() == 0)
×
641
            return;
×
642

643
        if (isPaused() == false)
×
644
        {
645
            // Get a new map every time elapsed is reset to zero
646
            if (elapsed() < MasterTimer::tick())
×
647
            {
648
                if (tempoType() == Beats)
×
649
                    m_stepBeatDuration = beatsToTime(duration(), timer->beatTimeDuration());
×
650

651
                //qDebug() << "RGBMatrix step" << m_stepHandler->currentStepIndex() << ", color:" << QString::number(m_stepHandler->stepColor().rgb(), 16);
652
                m_runAlgorithm->rgbMap(m_group->size(), m_stepHandler->stepColor().rgb(),
×
653
                                       m_stepHandler->currentStepIndex(), m_stepHandler->m_map);
×
654
                updateMapChannels(m_stepHandler->m_map, m_group, universes);
×
655
            }
656
        }
657
    }
658

659
    if (isPaused() == false)
×
660
    {
661
        // Increment the ms elapsed time
662
        incrementElapsed();
×
663

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

700
void RGBMatrix::postRun(MasterTimer *timer, QList<Universe *> universes)
×
701
{
702
    uint fadeout = overrideFadeOutSpeed() == defaultSpeed() ? fadeOutSpeed() : overrideFadeOutSpeed();
×
703

704
    /* If no fade out is needed, dismiss all the requested faders.
705
     * Otherwise, set all the faders to fade out and let Universe dismiss them
706
     * when done */
707
    if (fadeout == 0)
×
708
    {
709
        dismissAllFaders();
×
710
    }
711
    else
712
    {
713
        if (tempoType() == Beats)
×
714
            fadeout = beatsToTime(fadeout, timer->beatTimeDuration());
×
715

716
        foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
×
717
        {
718
            if (!fader.isNull())
×
719
                fader->setFadeOut(true, fadeout);
×
720
        }
721
    }
722

723
    m_fadersMap.clear();
×
724

725
    {
726
        QMutexLocker algorithmLocker(&m_algorithmMutex);
×
727
        checkEngineCreation();
×
728
        if (m_runAlgorithm != NULL)
×
729
            m_runAlgorithm->postRun();
×
730
    }
731

732
    Function::postRun(timer, universes);
×
733
}
×
734

735
void RGBMatrix::roundCheck()
×
736
{
737
    QMutexLocker algorithmLocker(&m_algorithmMutex);
×
738
    if (m_algorithm == NULL)
×
739
        return;
740

741
    if (m_stepHandler->checkNextStep(runOrder(), m_rgbColors[0], m_rgbColors[1], m_stepsCount) == false)
×
742
        stop(FunctionParent::master());
×
743

744
    m_roundTime->restart();
×
745

746
    if (tempoType() == Beats)
×
747
        roundElapsed(m_stepBeatDuration);
×
748
    else
749
        roundElapsed(duration());
×
750
}
751

752
FadeChannel *RGBMatrix::getFader(Universe *universe, quint32 fixtureID, quint32 channel)
×
753
{
754
    // get the universe Fader first. If doesn't exist, create it
755
    if (universe == NULL)
×
756
        return NULL;
757

758
    QSharedPointer<GenericFader> fader = m_fadersMap.value(universe->id(), QSharedPointer<GenericFader>());
×
759
    if (fader.isNull())
×
760
    {
761
        fader = universe->requestFader();
×
762
        fader->adjustIntensity(getAttributeValue(Intensity));
×
763
        fader->setBlendMode(blendMode());
×
764
        fader->setName(name());
×
765
        fader->setParentFunctionID(id());
×
766
        m_fadersMap[universe->id()] = fader;
×
767
    }
768

769
    return fader->getChannelFader(doc(), universe, fixtureID, channel);
×
770
}
771

772
void RGBMatrix::updateFaderValues(FadeChannel *fc, uchar value, uint fadeTime)
×
773
{
774
    fc->setStart(fc->current());
×
775
    fc->setTarget(value);
×
776
    fc->setElapsed(0);
×
777
    fc->setReady(false);
×
778
    // fade in/out depends on target value
779
    if (value == 0)
×
780
        fc->setFadeTime(fadeOutSpeed());
×
781
    else
782
        fc->setFadeTime(fadeTime);
×
783
}
×
784

785
void RGBMatrix::updateMapChannels(const RGBMap& map, const FixtureGroup *grp, QList<Universe *> universes)
×
786
{
787
    uint fadeTime = (overrideFadeInSpeed() == defaultSpeed()) ? fadeInSpeed() : overrideFadeInSpeed();
×
788

789
    // Create/modify fade channels for ALL heads in the group
790
    QMapIterator<QLCPoint, GroupHead> it(grp->headsMap());
×
791
    while (it.hasNext())
×
792
    {
793
        it.next();
×
794
        QLCPoint pt = it.key();
×
795
        GroupHead grpHead = it.value();
×
796
        Fixture *fxi = doc()->fixture(grpHead.fxi);
×
797
        if (fxi == NULL)
×
798
            continue;
×
799

800
        QLCFixtureHead head = fxi->head(grpHead.head);
×
801

802
        if (pt.y() >= map.count() || pt.x() >= map[pt.y()].count())
×
803
            continue;
804

805
        uint col = map[pt.y()][pt.x()];
×
806
        QVector<quint32> channelList;
807
        QVector<uchar> valueList;
808

809
        if (m_controlMode == ControlModeRgb)
×
810
        {
811
            channelList = head.rgbChannels();
×
812

813
            if (channelList.size() == 3)
×
814
            {
815
                valueList.append(qRed(col));
×
816
                valueList.append(qGreen(col));
×
817
                valueList.append(qBlue(col));
×
818
            }
819
            else
820
            {
821
                channelList = head.cmyChannels();
×
822

823
                if (channelList.size() == 3)
×
824
                {
825
                    // CMY color mixing
826
                    QColor cmyCol(col);
×
827
                    valueList.append(cmyCol.cyan());
×
828
                    valueList.append(cmyCol.magenta());
×
829
                    valueList.append(cmyCol.yellow());
×
830
                }
831
            }
832
        }
833
        else if (m_controlMode == ControlModeShutter)
×
834
        {
835
            channelList = head.shutterChannels();
×
836

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

863
            quint32 masterDim = fxi->masterIntensityChannel();
×
864
            quint32 headDim = head.channelNumber(QLCChannel::Intensity, QLCChannel::MSB);
×
865

866
            if (masterDim != QLCChannel::invalid())
×
867
            {
868
                channelList.append(masterDim);
×
869
                valueList.append(rgbToGrey(col));
×
870
            }
871

872
            if (headDim != QLCChannel::invalid() && headDim != masterDim)
×
873
            {
874
                channelList.append(headDim);
×
875
                valueList.append(col == 0 ? 0 : 255);
×
876
            }
877
        }
×
878
        else
879
        {
880
            if (m_controlMode == ControlModeWhite)
×
881
                channelList.append(head.channelNumber(QLCChannel::White, QLCChannel::MSB));
×
882
            else if (m_controlMode == ControlModeAmber)
×
883
                channelList.append(head.channelNumber(QLCChannel::Amber, QLCChannel::MSB));
×
884
            else if (m_controlMode == ControlModeUV)
×
885
                channelList.append(head.channelNumber(QLCChannel::UV, QLCChannel::MSB));
×
886

887
            valueList.append(rgbToGrey(col));
×
888
        }
889

890
        quint32 absAddress = fxi->universeAddress();
×
891

892
        for (int i = 0; i < channelList.count(); i++)
×
893
        {
894
            if (channelList.at(i) == QLCChannel::invalid())
×
895
                continue;
×
896

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

899
            FadeChannel *fc = getFader(universes.at(universeIndex), grpHead.fxi, channelList.at(i));
×
900
            updateFaderValues(fc, valueList.at(i), fadeTime);
×
901
        }
902
    }
×
903
}
×
904

905
uchar RGBMatrix::rgbToGrey(uint col)
×
906
{
907
    // the weights are taken from
908
    // https://en.wikipedia.org/wiki/YUV#SDTV_with_BT.601
909
    return (0.299 * qRed(col) + 0.587 * qGreen(col) + 0.114 * qBlue(col));
×
910
}
911

912
/*********************************************************************
913
 * Attributes
914
 *********************************************************************/
915

916
int RGBMatrix::adjustAttribute(qreal fraction, int attributeId)
×
917
{
918
    int attrIndex = Function::adjustAttribute(fraction, attributeId);
×
919

920
    if (attrIndex == Intensity)
×
921
    {
922
        foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
×
923
        {
924
            if (!fader.isNull())
×
925
                fader->adjustIntensity(getAttributeValue(Function::Intensity));
×
926
        }
927
    }
928

929
    return attrIndex;
×
930
}
931

932
/*************************************************************************
933
 * Blend mode
934
 *************************************************************************/
935

936
void RGBMatrix::setBlendMode(Universe::BlendMode mode)
×
937
{
938
    if (mode == blendMode())
×
939
        return;
940

941
    foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
×
942
    {
943
        if (!fader.isNull())
×
944
            fader->setBlendMode(mode);
×
945
    }
946

947
    Function::setBlendMode(mode);
×
948
    emit changed(id());
×
949
}
950

951
/*************************************************************************
952
 * Control Mode
953
 *************************************************************************/
954

955
RGBMatrix::ControlMode RGBMatrix::controlMode() const
2✔
956
{
957
    return m_controlMode;
2✔
958
}
959

960
void RGBMatrix::setControlMode(RGBMatrix::ControlMode mode)
3✔
961
{
962
    m_controlMode = mode;
3✔
963
    emit changed(id());
3✔
964
}
3✔
965

966
RGBMatrix::ControlMode RGBMatrix::stringToControlMode(QString mode)
1✔
967
{
968
    if (mode == KXMLQLCRGBMatrixControlModeRgb)
1✔
969
        return ControlModeRgb;
970
    else if (mode == KXMLQLCRGBMatrixControlModeAmber)
×
971
        return ControlModeAmber;
972
    else if (mode == KXMLQLCRGBMatrixControlModeWhite)
×
973
        return ControlModeWhite;
974
    else if (mode == KXMLQLCRGBMatrixControlModeUV)
×
975
        return ControlModeUV;
976
    else if (mode == KXMLQLCRGBMatrixControlModeDimmer)
×
977
        return ControlModeDimmer;
978
    else if (mode == KXMLQLCRGBMatrixControlModeShutter)
×
979
        return ControlModeShutter;
×
980

981
    return ControlModeRgb;
982
}
983

984
QString RGBMatrix::controlModeToString(RGBMatrix::ControlMode mode)
1✔
985
{
986
    switch(mode)
1✔
987
    {
988
        default:
1✔
989
        case ControlModeRgb:
990
            return QString(KXMLQLCRGBMatrixControlModeRgb);
1✔
991
        break;
992
        case ControlModeAmber:
×
993
            return QString(KXMLQLCRGBMatrixControlModeAmber);
×
994
        break;
995
        case ControlModeWhite:
×
996
            return QString(KXMLQLCRGBMatrixControlModeWhite);
×
997
        break;
998
        case ControlModeUV:
×
999
            return QString(KXMLQLCRGBMatrixControlModeUV);
×
1000
        break;
1001
        case ControlModeDimmer:
×
1002
            return QString(KXMLQLCRGBMatrixControlModeDimmer);
×
1003
        break;
1004
        case ControlModeShutter:
×
1005
            return QString(KXMLQLCRGBMatrixControlModeShutter);
×
1006
        break;
1007
    }
1008
}
1009

1010

1011
/*************************************************************************
1012
 *************************************************************************
1013
 *                          RGBMatrixStep class
1014
 *************************************************************************
1015
 *************************************************************************/
1016

1017
RGBMatrixStep::RGBMatrixStep()
10✔
1018
    : m_direction(Function::Forward)
10✔
1019
    , m_currentStepIndex(0)
10✔
1020
    , m_stepColor(QColor())
1021
    , m_crDelta(0)
10✔
1022
    , m_cgDelta(0)
10✔
1023
    , m_cbDelta(0)
10✔
1024
{
1025

1026
}
10✔
1027

1028
void RGBMatrixStep::setCurrentStepIndex(int index)
×
1029
{
1030
    m_currentStepIndex = index;
×
1031
}
×
1032

1033
int RGBMatrixStep::currentStepIndex() const
1✔
1034
{
1035
    return m_currentStepIndex;
1✔
1036
}
1037

1038
void RGBMatrixStep::calculateColorDelta(QColor startColor, QColor endColor, RGBAlgorithm *algorithm)
16✔
1039
{
1040
    m_crDelta = 0;
16✔
1041
    m_cgDelta = 0;
16✔
1042
    m_cbDelta = 0;
16✔
1043

1044
    if (endColor.isValid() && algorithm != NULL && algorithm->acceptColors() > 1)
16✔
1045
    {
1046
        m_crDelta = endColor.red() - startColor.red();
10✔
1047
        m_cgDelta = endColor.green() - startColor.green();
10✔
1048
        m_cbDelta = endColor.blue() - startColor.blue();
10✔
1049

1050
        //qDebug() << "Color deltas:" << m_crDelta << m_cgDelta << m_cbDelta;
1051
    }
1052
}
16✔
1053

1054
void RGBMatrixStep::setStepColor(QColor color)
×
1055
{
1056
    m_stepColor = color;
×
1057
}
×
1058

1059
QColor RGBMatrixStep::stepColor()
6✔
1060
{
1061
    return m_stepColor;
6✔
1062
}
1063

1064
void RGBMatrixStep::updateStepColor(int stepIndex, QColor startColor, int stepsCount)
×
1065
{
1066
    if (stepsCount <= 0)
×
1067
        return;
1068

1069
    if (stepsCount == 1)
×
1070
    {
1071
        m_stepColor = startColor;
×
1072
    }
1073
    else
1074
    {
1075
        m_stepColor.setRed(startColor.red() + (m_crDelta * stepIndex / (stepsCount - 1)));
×
1076
        m_stepColor.setGreen(startColor.green() + (m_cgDelta * stepIndex / (stepsCount - 1)));
×
1077
        m_stepColor.setBlue(startColor.blue() + (m_cbDelta * stepIndex / (stepsCount - 1)));
×
1078
    }
1079

1080
    //qDebug() << "RGBMatrix step" << stepIndex << ", color:" << QString::number(m_stepColor.rgb(), 16);
1081
}
1082

1083
void RGBMatrixStep::initializeDirection(Function::Direction direction, QColor startColor, QColor endColor, int stepsCount, RGBAlgorithm *algorithm)
×
1084
{
1085
    m_direction = direction;
×
1086

1087
    if (m_direction == Function::Forward)
×
1088
    {
1089
        setCurrentStepIndex(0);
×
1090
        setStepColor(startColor);
×
1091
    }
1092
    else
1093
    {
1094
        setCurrentStepIndex(stepsCount - 1);
×
1095

1096
        if (endColor.isValid())
×
1097
            setStepColor(endColor);
×
1098
        else
1099
            setStepColor(startColor);
×
1100
    }
1101

1102
    calculateColorDelta(startColor, endColor, algorithm);
×
1103
}
×
1104

1105
bool RGBMatrixStep::checkNextStep(Function::RunOrder order,
×
1106
                                  QColor startColor, QColor endColor, int stepsNumber)
1107
{
1108
    if (order == Function::PingPong)
×
1109
    {
1110
        if (m_direction == Function::Forward && (m_currentStepIndex + 1) == stepsNumber)
×
1111
        {
1112
            m_direction = Function::Backward;
×
1113
            m_currentStepIndex = stepsNumber - 2;
×
1114
            if (endColor.isValid())
×
1115
                m_stepColor = endColor;
×
1116

1117
            updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1118
        }
1119
        else if (m_direction == Function::Backward && (m_currentStepIndex - 1) < 0)
×
1120
        {
1121
            m_direction = Function::Forward;
×
1122
            m_currentStepIndex = 1;
×
1123
            m_stepColor = startColor;
×
1124
            updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1125
        }
1126
        else
1127
        {
1128
            if (m_direction == Function::Forward)
×
1129
                m_currentStepIndex++;
×
1130
            else
1131
                m_currentStepIndex--;
×
1132
            updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1133
        }
1134
    }
1135
    else if (order == Function::SingleShot)
×
1136
    {
1137
        if (m_direction == Function::Forward)
×
1138
        {
1139
            if (m_currentStepIndex >= stepsNumber - 1)
×
1140
                return false;
1141
            else
1142
            {
1143
                m_currentStepIndex++;
×
1144
                updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1145
            }
1146
        }
1147
        else
1148
        {
1149
            if (m_currentStepIndex <= 0)
×
1150
                return false;
1151
            else
1152
            {
1153
                m_currentStepIndex--;
×
1154
                updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1155
            }
1156
        }
1157
    }
1158
    else
1159
    {
1160
        if (m_direction == Function::Forward)
×
1161
        {
1162
            if (m_currentStepIndex >= stepsNumber - 1)
×
1163
            {
1164
                m_currentStepIndex = 0;
×
1165
                m_stepColor = startColor;
×
1166
            }
1167
            else
1168
            {
1169
                m_currentStepIndex++;
×
1170
                updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1171
            }
1172
        }
1173
        else
1174
        {
1175
            if (m_currentStepIndex <= 0)
×
1176
            {
1177
                m_currentStepIndex = stepsNumber - 1;
×
1178
                if (endColor.isValid())
×
1179
                    m_stepColor = endColor;
×
1180
            }
1181
            else
1182
            {
1183
                m_currentStepIndex--;
×
1184
                updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1185
            }
1186
        }
1187
    }
1188

1189
    return true;
1190
}
1191

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