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

mcallegari / qlcplus / 28807794184

06 Jul 2026 04:42PM UTC coverage: 35.285% (+1.1%) from 34.233%
28807794184

push

github

mcallegari
qmlui: VC Speed dial tap button can tap global BPM

18560 of 52600 relevant lines covered (35.29%)

40987.85 hits per line

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

38.9
/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
static const int RGBMatrixColorMask = 0x00FFFFFF;
59

60
/****************************************************************************
61
 * Initialization
62
 ****************************************************************************/
63

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

85
    m_rgbColors.fill(QColor(), RGBAlgorithmColorDisplayCount);
9✔
86
    setColor(0, Qt::red);
9✔
87

88
    setAlgorithm(RGBAlgorithm::algorithm(doc, "Stripes"));
9✔
89

90
    for (int i = 0; i < ColorAttributeCount; ++i)
54✔
91
    {
92
        registerAttribute(tr("Color %1").arg(i + 1), LastWins | Single, -1.0, 16777215.0,
54✔
93
                          getColor(i).isValid() ? int(getColor(i).rgb() & RGBMatrixColorMask) : -1);
99✔
94
    }
95

96
    int algoCount = RGBAlgorithm::algorithms(doc).count();
9✔
97
    registerAttribute(tr("Pattern"), LastWins | Single, 0.0, algoCount > 0 ? algoCount - 1 : 0, algorithmIndex());
9✔
98
}
9✔
99

100
RGBMatrix::~RGBMatrix()
11✔
101
{
102
    //if (m_runAlgorithm != NULL)
103
    //    delete m_runAlgorithm;
104
    delete m_algorithm;
9✔
105
    delete m_stepHandler;
9✔
106
}
11✔
107

108
QIcon RGBMatrix::getIcon() const
×
109
{
110
    return QIcon(":/rgbmatrix.png");
×
111
}
112

113
int RGBMatrix::algorithmIndex() const
22✔
114
{
115
    if (m_algorithm == NULL || doc() == NULL)
22✔
116
        return 0;
×
117

118
    QStringList algoList = RGBAlgorithm::algorithms(doc());
22✔
119
    int idx = algoList.indexOf(m_algorithm->name());
22✔
120
    return idx >= 0 ? idx : 0;
22✔
121
}
22✔
122

123
void RGBMatrix::setTotalDuration(quint32 msec)
1✔
124
{
125
    QMutexLocker algorithmLocker(&m_algorithmMutex);
1✔
126

127
    if (m_algorithm == NULL)
1✔
128
        return;
×
129

130
    FixtureGroup *grp = doc()->fixtureGroup(fixtureGroup());
1✔
131
    if (grp == NULL)
1✔
132
        return;
×
133

134
    int steps = m_algorithm->rgbMapStepCount(grp->size());
1✔
135
    setDuration(msec / steps);
1✔
136
}
1✔
137

138
quint32 RGBMatrix::totalDuration()
3✔
139
{
140
    QMutexLocker algorithmLocker(&m_algorithmMutex);
3✔
141

142
    if (m_algorithm == NULL)
3✔
143
        return 0;
×
144

145
    FixtureGroup *grp = doc()->fixtureGroup(fixtureGroup());
3✔
146
    if (grp == NULL)
3✔
147
        return 0;
1✔
148

149
    //qDebug () << "Algorithm steps:" << m_algorithm->rgbMapStepCount(grp->size());
150
    return m_algorithm->rgbMapStepCount(grp->size()) * duration();
2✔
151
}
3✔
152

153
void RGBMatrix::setDimmerControl(bool dimmerControl)
1✔
154
{
155
    m_dimmerControl = dimmerControl;
1✔
156
}
1✔
157

158
bool RGBMatrix::dimmerControl() const
2✔
159
{
160
    return m_dimmerControl;
2✔
161
}
162

163
/****************************************************************************
164
 * Copying
165
 ****************************************************************************/
166

167
Function* RGBMatrix::createCopy(Doc* doc, bool addToDoc)
1✔
168
{
169
    Q_ASSERT(doc != NULL);
1✔
170

171
    Function *copy = new RGBMatrix(doc);
1✔
172
    if (copy->copyFrom(this) == false)
1✔
173
    {
174
        delete copy;
×
175
        copy = NULL;
×
176
    }
177
    if (addToDoc == true && doc->addFunction(copy) == false)
1✔
178
    {
179
        delete copy;
×
180
        copy = NULL;
×
181
    }
182

183
    return copy;
1✔
184
}
185

186
bool RGBMatrix::copyFrom(const Function* function)
1✔
187
{
188
    const RGBMatrix *mtx = qobject_cast<const RGBMatrix*> (function);
1✔
189
    if (mtx == NULL)
1✔
190
        return false;
×
191

192
    setDimmerControl(mtx->dimmerControl());
1✔
193
    setFixtureGroup(mtx->fixtureGroup());
1✔
194

195
    m_rgbColors.clear();
1✔
196
    foreach (QColor col, mtx->getColors())
7✔
197
        m_rgbColors.append(col);
6✔
198

199
    if (mtx->algorithm() != NULL)
1✔
200
        setAlgorithm(mtx->algorithm()->clone());
1✔
201
    else
202
        setAlgorithm(NULL);
×
203

204
    setControlMode(mtx->controlMode());
1✔
205

206
    return Function::copyFrom(function);
1✔
207
}
208

209
/****************************************************************************
210
 * Fixtures
211
 ****************************************************************************/
212

213
quint32 RGBMatrix::fixtureGroup() const
35✔
214
{
215
    return m_fixtureGroupID;
35✔
216
}
217

218
void RGBMatrix::setFixtureGroup(quint32 id)
8✔
219
{
220
    m_fixtureGroupID = id;
8✔
221
    {
222
        QMutexLocker algoLocker(&m_algorithmMutex);
8✔
223
        m_group = doc()->fixtureGroup(m_fixtureGroupID);
8✔
224
    }
8✔
225
    m_stepsCount = algorithmStepsCount();
8✔
226
}
8✔
227

228
QList<quint32> RGBMatrix::components() const
2✔
229
{
230
    if (m_group != NULL)
2✔
231
        return m_group->fixtureList();
1✔
232

233
    return QList<quint32>();
1✔
234
}
235

236
/****************************************************************************
237
 * Algorithm
238
 ****************************************************************************/
239

240
void RGBMatrix::setAlgorithm(RGBAlgorithm *algo)
13✔
241
{
242
    {
243
        QMutexLocker algorithmLocker(&m_algorithmMutex);
13✔
244
        delete m_algorithm;
13✔
245
        m_algorithm = algo;
13✔
246

247
        m_requestEngineCreation = true;
13✔
248

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

267
            QVector<uint> colors = script->rgbMapGetColors();
13✔
268
            for (int i = 0; i < colors.count(); i++)
13✔
269
                m_rgbColors.replace(i, QColor::fromRgb(colors.at(i)));
×
270
        }
13✔
271
    }
13✔
272
    m_stepsCount = algorithmStepsCount();
13✔
273

274
    if (m_applyingStyleAttributes == false)
13✔
275
        Function::adjustAttribute(algorithmIndex(), PatternAttr);
13✔
276

277
    emit changed(id());
13✔
278
}
13✔
279

280
RGBAlgorithm *RGBMatrix::algorithm() const
18✔
281
{
282
    return m_algorithm;
18✔
283
}
284

285
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
286
QMutex& RGBMatrix::algorithmMutex()
287
{
288
    return m_algorithmMutex;
289
}
290
#else
291
QRecursiveMutex& RGBMatrix::algorithmMutex()
×
292
{
293
    return m_algorithmMutex;
×
294
}
295
#endif
296

297

298
int RGBMatrix::stepsCount() const
2✔
299
{
300
    return m_stepsCount;
2✔
301
}
302

303
int RGBMatrix::algorithmStepsCount()
22✔
304
{
305
    QMutexLocker algorithmLocker(&m_algorithmMutex);
22✔
306

307
    if (m_algorithm == NULL)
22✔
308
        return 0;
×
309

310
    FixtureGroup *grp = doc()->fixtureGroup(fixtureGroup());
22✔
311
    if (grp != NULL)
22✔
312
        return m_algorithm->rgbMapStepCount(grp->size());
6✔
313

314
    return 0;
16✔
315
}
22✔
316

317
void RGBMatrix::previewMap(int step, RGBMatrixStep *handler)
7✔
318
{
319
    QMutexLocker algorithmLocker(&m_algorithmMutex);
7✔
320
    if (m_algorithm == NULL || handler == NULL)
7✔
321
        return;
×
322

323
    if (m_group == NULL)
7✔
324
        m_group = doc()->fixtureGroup(fixtureGroup());
1✔
325

326
    if (m_group != NULL)
7✔
327
    {
328
        setMapColors(m_algorithm);
6✔
329
        m_algorithm->rgbMap(m_group->size(), handler->stepColor().rgb(), step, handler->m_map);
6✔
330
    }
331
}
7✔
332

333
/****************************************************************************
334
 * Color
335
 ****************************************************************************/
336

337
void RGBMatrix::setColor(int i, QColor c)
25✔
338
{
339
    if (i < 0)
25✔
340
        return;
×
341

342
    if (i >= m_rgbColors.count())
25✔
343
        m_rgbColors.resize(i + 1);
×
344

345
    m_rgbColors.replace(i, c);
25✔
346
    {
347
        QMutexLocker algorithmLocker(&m_algorithmMutex);
25✔
348
        if (m_algorithm != NULL)
25✔
349
        {
350
            m_algorithm->setColors(m_rgbColors);
16✔
351
            updateColorDelta();
16✔
352
        }
353
    }
25✔
354
    setMapColors(m_algorithm);
25✔
355

356
    if (m_applyingStyleAttributes == false && i >= 0 && i < ColorAttributeCount)
25✔
357
        Function::adjustAttribute(c.isValid() ? int(c.rgb() & RGBMatrixColorMask) : -1, Color1Attr + i);
25✔
358

359
    emit changed(id());
25✔
360
}
361

362
QColor RGBMatrix::getColor(int i) const
64✔
363
{
364
    if (i < 0 || i >= m_rgbColors.count())
64✔
365
        return QColor();
×
366

367
    return m_rgbColors.at(i);
64✔
368
}
369

370
QVector<QColor> RGBMatrix::getColors() const
1✔
371
{
372
    return m_rgbColors;
1✔
373
}
374

375
void RGBMatrix::updateColorDelta()
16✔
376
{
377
    if (m_rgbColors.count() > 1)
16✔
378
        m_stepHandler->calculateColorDelta(m_rgbColors[0], m_rgbColors[1], m_algorithm);
16✔
379
}
16✔
380

381
void RGBMatrix::setMapColors(RGBAlgorithm *algorithm)
31✔
382
{
383
    QMutexLocker algorithmLocker(&m_algorithmMutex);
31✔
384
    if (algorithm == NULL)
31✔
385
        return;
9✔
386

387
    if (algorithm->apiVersion() < 3)
22✔
388
        return;
22✔
389

390
    if (m_group == NULL)
×
391
        m_group = doc()->fixtureGroup(fixtureGroup());
×
392

393
    QVector<unsigned int> rawColors;
×
394
    const int acceptColors = algorithm->acceptColors();
×
395
    rawColors.reserve(acceptColors);
×
396
    for (int i = 0; i < acceptColors; i++)
×
397
    {
398
        if (m_rgbColors.count() > i)
×
399
        {
400
            QColor col = m_rgbColors.at(i);
×
401
            rawColors.append(col.isValid() ? col.rgb() : 0);
×
402
        }
403
        else
404
        {
405
            rawColors.append(0);
×
406
        }
407
    }
408

409
    algorithm->rgbMapSetColors(rawColors);
×
410
}
31✔
411

412
/************************************************************************
413
 * Properties
414
 ************************************************************************/
415

416
void RGBMatrix::setProperty(QString propName, QString value)
1✔
417
{
418
    QMutexLocker algoLocker(&m_algorithmMutex);
1✔
419

420
    // Remember the old step count before changing it (used to scale the step index)
421
    int oldStepsCount = m_stepsCount;
1✔
422

423
    m_properties[propName] = value;
1✔
424
    if (m_algorithm != NULL && m_algorithm->type() == RGBAlgorithm::Script)
1✔
425
    {
426
        RGBScript *script = static_cast<RGBScript*> (m_algorithm);
1✔
427
        script->setProperty(propName, value);
1✔
428

429
        QVector<uint> colors = script->rgbMapGetColors();
1✔
430
        for (int i = 0; i < colors.count(); i++)
1✔
431
            setColor(i, QColor::fromRgb(colors.at(i)));
×
432
    }
1✔
433
    m_stepsCount = algorithmStepsCount();
1✔
434

435
    // Scale currentStepIndex to the new step count to preserve the phase.
436
    // We use m_continuousPhase (a continuous 0.0-1.0 value) instead of the discrete
437
    // stepIndex to avoid accumulating rounding errors over multiple speed changes.
438
    // Analogous to EFXFixture::durationChanged() using m_currentAngle.
439
    if (m_stepHandler != NULL && m_stepsCount > 0 && oldStepsCount != m_stepsCount)
1✔
440
    {
441
        // If m_continuousPhase is not up to date yet (first change),
442
        // compute it from the current stepIndex
443
        if (oldStepsCount > 0)
×
444
        {
445
            int currentStepIndex = m_stepHandler->currentStepIndex();
×
446
            m_continuousPhase = double(currentStepIndex) / double(oldStepsCount);
×
447
        }
448

449
        // Rescale the phase to the new step count (preserve the continuous phase,
450
        // not the discrete index)
451
        int newStepIndex = int(round(m_continuousPhase * double(m_stepsCount)));
×
452
        // Make sure it stays within range
453
        if (newStepIndex >= m_stepsCount)
×
454
            newStepIndex = m_stepsCount - 1;
×
455
        if (newStepIndex < 0)
×
456
            newStepIndex = 0;
×
457
        m_stepHandler->setCurrentStepIndex(newStepIndex);
×
458
    }
459
}
1✔
460

461
QString RGBMatrix::property(QString propName)
3✔
462
{
463
    QMutexLocker algoLocker(&m_algorithmMutex);
3✔
464

465
    /** If the property is cached, then return it right away */
466
    QMap<QString, QString>::iterator it = m_properties.find(propName);
3✔
467
    if (it != m_properties.end())
3✔
468
        return it.value();
1✔
469

470
    /** Otherwise, let's retrieve it from the Script */
471
    if (m_algorithm != NULL && m_algorithm->type() == RGBAlgorithm::Script)
2✔
472
    {
473
        RGBScript *script = static_cast<RGBScript*> (m_algorithm);
2✔
474
        return script->property(propName);
2✔
475
    }
476

477
    return QString();
×
478
}
3✔
479

480
/****************************************************************************
481
 * Load & Save
482
 ****************************************************************************/
483

484
bool RGBMatrix::loadXML(QXmlStreamReader &root)
3✔
485
{
486
    if (root.name() != KXMLQLCFunction)
3✔
487
    {
488
        qWarning() << Q_FUNC_INFO << "Function node not found";
1✔
489
        return false;
1✔
490
    }
491

492
    if (root.attributes().value(KXMLQLCFunctionType).toString() != typeToString(Function::RGBMatrixType))
4✔
493
    {
494
        qWarning() << Q_FUNC_INFO << "Function is not an RGB matrix";
1✔
495
        return false;
1✔
496
    }
497

498
    /* Load matrix contents */
499
    while (root.readNextStartElement())
12✔
500
    {
501
        if (root.name() == KXMLQLCFunctionSpeed)
11✔
502
        {
503
            loadXMLSpeed(root);
1✔
504
        }
505
        else if (root.name() == KXMLQLCFunctionTempoType)
10✔
506
        {
507
            loadXMLTempoType(root);
×
508
        }
509
        else if (root.name() == KXMLQLCRGBAlgorithm)
10✔
510
        {
511
            setAlgorithm(RGBAlgorithm::loader(doc(), root));
1✔
512
        }
513
        else if (root.name() == KXMLQLCRGBMatrixFixtureGroup)
9✔
514
        {
515
            setFixtureGroup(root.readElementText().toUInt());
1✔
516
        }
517
        else if (root.name() == KXMLQLCFunctionDirection)
8✔
518
        {
519
            loadXMLDirection(root);
1✔
520
        }
521
        else if (root.name() == KXMLQLCFunctionRunOrder)
7✔
522
        {
523
            loadXMLRunOrder(root);
1✔
524
        }
525
        // Legacy support
526
        else if (root.name() == KXMLQLCRGBMatrixStartColor)
6✔
527
        {
528
            setColor(0, QColor::fromRgb(QRgb(root.readElementText().toUInt())));
×
529
        }
530
        else if (root.name() == KXMLQLCRGBMatrixEndColor)
6✔
531
        {
532
            setColor(1, QColor::fromRgb(QRgb(root.readElementText().toUInt())));
×
533
        }
534
        else if (root.name() == KXMLQLCRGBMatrixColor)
6✔
535
        {
536
            int colorIdx = root.attributes().value(KXMLQLCRGBMatrixColorIndex).toInt();
10✔
537
            setColor(colorIdx, QColor::fromRgb(QRgb(root.readElementText().toUInt())));
5✔
538
        }
539
        else if (root.name() == KXMLQLCRGBMatrixControlMode)
1✔
540
        {
541
            setControlMode(stringToControlMode(root.readElementText()));
1✔
542
        }
543
        else if (root.name() == KXMLQLCRGBMatrixProperty)
×
544
        {
545
            QString name = root.attributes().value(KXMLQLCRGBMatrixPropertyName).toString();
×
546
            QString value = root.attributes().value(KXMLQLCRGBMatrixPropertyValue).toString();
×
547
            setProperty(name, value);
×
548
            root.skipCurrentElement();
×
549
        }
×
550
        else if (root.name() == KXMLQLCRGBMatrixDimmerControl)
×
551
        {
552
            setDimmerControl(root.readElementText().toInt());
×
553
        }
554
        else
555
        {
556
            qWarning() << Q_FUNC_INFO << "Unknown RGB matrix tag:" << root.name();
×
557
            root.skipCurrentElement();
×
558
        }
559
    }
560

561
    return true;
1✔
562
}
563

564
bool RGBMatrix::saveXML(QXmlStreamWriter *doc) const
1✔
565
{
566
    Q_ASSERT(doc != NULL);
1✔
567

568
    /* Function tag */
569
    doc->writeStartElement(KXMLQLCFunction);
2✔
570

571
    /* Common attributes */
572
    saveXMLCommon(doc);
1✔
573

574
    /* Tempo type */
575
    saveXMLTempoType(doc);
1✔
576

577
    /* Speeds */
578
    saveXMLSpeed(doc);
1✔
579

580
    /* Direction */
581
    saveXMLDirection(doc);
1✔
582

583
    /* Run order */
584
    saveXMLRunOrder(doc);
1✔
585

586
    /* Algorithm */
587
    if (m_algorithm != NULL)
1✔
588
        m_algorithm->saveXML(doc);
1✔
589

590
    /* LEGACY - Dimmer Control */
591
    if (dimmerControl())
1✔
592
        doc->writeTextElement(KXMLQLCRGBMatrixDimmerControl, QString::number(dimmerControl()));
×
593

594
    /* Colors */
595
    for (int i = 0; i < m_rgbColors.count(); i++)
6✔
596
    {
597
        if (m_rgbColors.at(i).isValid() == false)
5✔
598
            continue;
×
599

600
        doc->writeStartElement(KXMLQLCRGBMatrixColor);
10✔
601
        doc->writeAttribute(KXMLQLCRGBMatrixColorIndex, QString::number(i));
10✔
602
        doc->writeCharacters(QString::number(m_rgbColors.at(i).rgb()));
5✔
603
        doc->writeEndElement();
5✔
604
    }
605

606
    /* Control Mode */
607
    doc->writeTextElement(KXMLQLCRGBMatrixControlMode, RGBMatrix::controlModeToString(m_controlMode));
2✔
608

609
    /* Fixture Group */
610
    doc->writeTextElement(KXMLQLCRGBMatrixFixtureGroup, QString::number(fixtureGroup()));
2✔
611

612
    /* Properties */
613
    QMapIterator<QString, QString> it(m_properties);
1✔
614
    while (it.hasNext())
1✔
615
    {
616
        it.next();
×
617
        doc->writeStartElement(KXMLQLCRGBMatrixProperty);
×
618
        doc->writeAttribute(KXMLQLCRGBMatrixPropertyName, it.key());
×
619
        doc->writeAttribute(KXMLQLCRGBMatrixPropertyValue, it.value());
×
620
        doc->writeEndElement();
×
621
    }
622

623
    /* End the <Function> tag */
624
    doc->writeEndElement();
1✔
625

626
    return true;
1✔
627
}
1✔
628

629
/****************************************************************************
630
 * Running
631
 ****************************************************************************/
632

633
void RGBMatrix::tap()
×
634
{
635
    if (stopped() == false)
×
636
    {
637
        FixtureGroup *grp = doc()->fixtureGroup(fixtureGroup());
×
638
        // Filter out taps that are too close to each other
639
        if (grp != NULL && uint(m_roundTime.elapsed()) >= (duration() / 4))
×
640
        {
641
            roundCheck();
×
642
            resetElapsed();
×
643
        }
644
    }
645
}
×
646

647
void RGBMatrix::checkEngineCreation()
×
648
{
649
    m_runAlgorithm = m_algorithm;
×
650
    m_requestEngineCreation = false;
×
651
}
×
652

653
void RGBMatrix::preRun(MasterTimer *timer)
×
654
{
655
    {
656
        QMutexLocker algorithmLocker(&m_algorithmMutex);
×
657

658
        m_group = doc()->fixtureGroup(m_fixtureGroupID);
×
659
        if (m_group == NULL)
×
660
        {
661
            // No fixture group to control
662
            stop(FunctionParent::master());
×
663
            return;
×
664
        }
665

666
        if (m_algorithm != NULL)
×
667
        {
668
            checkEngineCreation();
×
669

670
            // Copy direction from parent class direction
671
            m_stepHandler->initializeDirection(direction(), m_rgbColors[0], m_rgbColors[1], m_stepsCount, m_runAlgorithm);
×
672

673
            // Update continuous phase when starting playback
674
            if (m_stepsCount > 0)
×
675
                m_continuousPhase = double(m_stepHandler->currentStepIndex()) / double(m_stepsCount);
×
676

677
            if (m_runAlgorithm->type() == RGBAlgorithm::Script)
×
678
            {
679
                RGBScript *script = static_cast<RGBScript*> (m_runAlgorithm);
×
680
                QMapIterator<QString, QString> it(m_properties);
×
681
                while (it.hasNext())
×
682
                {
683
                    it.next();
×
684
                    script->setProperty(it.key(), it.value());
×
685
                }
686
            }
×
687
            else if (m_runAlgorithm->type() == RGBAlgorithm::Image)
×
688
            {
689
                RGBImage *image = static_cast<RGBImage*> (m_runAlgorithm);
×
690
                if (image->animatedSource())
×
691
                    image->rewindAnimation();
×
692
            }
693
        }
694
    }
×
695

696
    m_roundTime.restart();
×
697

698
    Function::preRun(timer);
×
699
}
700

701
void RGBMatrix::write(MasterTimer *timer, QList<Universe *> universes)
×
702
{
703
    Q_UNUSED(timer);
704

705
    {
706
        QMutexLocker algorithmLocker(&m_algorithmMutex);
×
707
        if (m_group == NULL)
×
708
        {
709
            // No fixture group to control
710
            stop(FunctionParent::master());
×
711
            return;
×
712
        }
713

714
        // No time to do anything.
715
        if (duration() == 0)
×
716
            return;
×
717

718
        if (m_algorithm != NULL && m_requestEngineCreation)
×
719
            checkEngineCreation();
×
720

721
        // Invalid/nonexistent script
722
        if (m_runAlgorithm == NULL || m_runAlgorithm->apiVersion() == 0)
×
723
            return;
×
724

725
        if (isPaused() == false)
×
726
        {
727
            // Get a new map every time elapsed is reset to zero
728
            if (elapsed() < MasterTimer::tick())
×
729
            {
730
                if (tempoType() == Beats)
×
731
                    m_stepBeatDuration = beatsToTime(duration(), timer->beatTimeDuration());
×
732

733
                //qDebug() << "RGBMatrix step" << m_stepHandler->currentStepIndex() << ", color:" << QString::number(m_stepHandler->stepColor().rgb(), 16);
734
                m_runAlgorithm->rgbMap(m_group->size(), m_stepHandler->stepColor().rgb(),
×
735
                                       m_stepHandler->currentStepIndex(), m_stepHandler->m_map);
×
736
                updateMapChannels(m_stepHandler->m_map, m_group, universes);
×
737
            }
738
        }
739
    }
×
740

741
    if (isPaused() == false)
×
742
    {
743
        // Increment the ms elapsed time
744
        incrementElapsed();
×
745

746
        /* Check if we need to change direction, stop completely or go to next step
747
         * The cases are:
748
         * 1- time tempo type: act normally, on ms elapsed time
749
         * 2- beat tempo type, beat occurred: check if the elapsed beats is a multiple of
750
         *    the step beat duration. If so, proceed to the next step
751
         * 3- beat tempo type, not beat: if the ms elapsed time reached the step beat
752
         *    duration in ms, and the ms time to the next beat is not less than 1/16 of
753
         *    the step beat duration in ms, then proceed to the next step. If the ms time to the
754
         *    next beat is less than 1/16 of the step beat duration in ms, then defer the step
755
         *    change to case #2, to resync the matrix to the next beat
756
         */
757
        if (tempoType() == Time && elapsed() >= duration())
×
758
        {
759
            roundCheck();
×
760
        }
761
        else if (tempoType() == Beats)
×
762
        {
763
            if (timer->isBeat())
×
764
            {
765
                incrementElapsedBeats();
×
766
                qDebug() << "Elapsed beats:" << elapsedBeats() << ", time elapsed:" << elapsed() << ", step time:" << m_stepBeatDuration;
×
767
                if (elapsedBeats() % duration() == 0)
×
768
                {
769
                    roundCheck();
×
770
                    resetElapsed();
×
771
                }
772
            }
773
            else if (elapsed() >= m_stepBeatDuration && (uint)timer->timeToNextBeat() > m_stepBeatDuration / 16)
×
774
            {
775
                qDebug() << "Elapsed exceeded";
×
776
                roundCheck();
×
777
            }
778
        }
779
    }
780
}
781

782
void RGBMatrix::postRun(MasterTimer *timer, QList<Universe *> universes)
×
783
{
784
    uint fadeout = overrideFadeOutSpeed() == defaultSpeed() ? fadeOutSpeed() : overrideFadeOutSpeed();
×
785

786
    /* If no fade out is needed, dismiss all the requested faders.
787
     * Otherwise, set all the faders to fade out and let Universe dismiss them
788
     * when done */
789
    if (fadeout == 0)
×
790
    {
791
        dismissAllFaders();
×
792
    }
793
    else
794
    {
795
        if (tempoType() == Beats)
×
796
            fadeout = beatsToTime(fadeout, timer->beatTimeDuration());
×
797

798
        foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
×
799
        {
800
            if (!fader.isNull())
×
801
                fader->setFadeOut(true, fadeout);
×
802
        }
×
803
    }
804

805
    m_fadersMap.clear();
×
806

807
    {
808
        QMutexLocker algorithmLocker(&m_algorithmMutex);
×
809
        checkEngineCreation();
×
810
        if (m_runAlgorithm != NULL)
×
811
            m_runAlgorithm->postRun();
×
812
    }
×
813

814
    Function::postRun(timer, universes);
×
815
}
×
816

817
void RGBMatrix::roundCheck()
×
818
{
819
    QMutexLocker algorithmLocker(&m_algorithmMutex);
×
820
    if (m_algorithm == NULL)
×
821
        return;
×
822

823
    if (m_stepHandler->checkNextStep(runOrder(), m_rgbColors[0], m_rgbColors[1], m_stepsCount) == false)
×
824
        stop(FunctionParent::master());
×
825

826
    // Update continuous phase based on current step index (prevents cumulative rounding errors)
827
    // This is analogous to how EFX uses m_currentAngle for phase scaling
828
    if (m_stepsCount > 0)
×
829
        m_continuousPhase = double(m_stepHandler->currentStepIndex()) / double(m_stepsCount);
×
830

831
    m_roundTime.restart();
×
832

833
    if (tempoType() == Beats)
×
834
        roundElapsed(m_stepBeatDuration);
×
835
    else
836
        roundElapsed(duration());
×
837
}
×
838

839
QSharedPointer<GenericFader> RGBMatrix::getFader(Universe *universe)
×
840
{
841
    // get the universe Fader first. If doesn't exist, create it
842
    if (universe == NULL)
×
843
        return QSharedPointer<GenericFader>();
×
844

845
    QSharedPointer<GenericFader> fader = m_fadersMap.value(universe->id(), QSharedPointer<GenericFader>());
×
846
    if (fader.isNull())
×
847
    {
848
        fader = universe->requestFader();
×
849
        fader->adjustIntensity(getAttributeValue(Intensity));
×
850
        fader->setBlendMode(blendMode());
×
851
        fader->setName(name());
×
852
        fader->setParentFunctionID(id());
×
853
        m_fadersMap[universe->id()] = fader;
×
854
    }
855

856
    return fader;
×
857
}
×
858

859
void RGBMatrix::updateFaderValues(FadeChannel &fc, uchar value, uint fadeTime)
×
860
{
861
    fc.setStart(fc.current());
×
862
    fc.setTarget(value);
×
863
    fc.setElapsed(0);
×
864
    fc.setReady(false);
×
865
    // fade in/out depends on target value
866
    if (value == 0)
×
867
        fc.setFadeTime(fadeOutSpeed());
×
868
    else
869
        fc.setFadeTime(fadeTime);
×
870
}
×
871

872
void RGBMatrix::updateMapChannels(const RGBMap& map, const FixtureGroup *grp, QList<Universe *> universes)
×
873
{
874
    uint fadeTime = (overrideFadeInSpeed() == defaultSpeed()) ? fadeInSpeed() : overrideFadeInSpeed();
×
875

876
    // Create/modify fade channels for ALL heads in the group
877
    QMapIterator<QLCPoint, GroupHead> it(grp->headsMap());
×
878
    while (it.hasNext())
×
879
    {
880
        it.next();
×
881
        QLCPoint pt = it.key();
×
882
        GroupHead grpHead = it.value();
×
883
        Fixture *fxi = doc()->fixture(grpHead.fxi);
×
884
        if (fxi == NULL)
×
885
            continue;
×
886

887
        QLCFixtureHead head = fxi->head(grpHead.head);
×
888

889
        if (pt.y() >= map.count() || pt.x() >= map[pt.y()].count())
×
890
            continue;
×
891

892
        uint col = map[pt.y()][pt.x()];
×
893
        QVector<quint32> channelList;
×
894
        QVector<uchar> valueList;
×
895

896
        if (m_controlMode == ControlModeRgb)
×
897
        {
898
            channelList = head.rgbChannels();
×
899

900
            if (channelList.size() == 3)
×
901
            {
902
                valueList.append(qRed(col));
×
903
                valueList.append(qGreen(col));
×
904
                valueList.append(qBlue(col));
×
905
            }
906
            else
907
            {
908
                channelList = head.cmyChannels();
×
909

910
                if (channelList.size() == 3)
×
911
                {
912
                    // CMY color mixing
913
                    QColor cmyCol(col);
×
914
                    valueList.append(cmyCol.cyan());
×
915
                    valueList.append(cmyCol.magenta());
×
916
                    valueList.append(cmyCol.yellow());
×
917
                }
918
            }
919
        }
920
        else if (m_controlMode == ControlModeShutter)
×
921
        {
922
            channelList = head.shutterChannels();
×
923

924
            if (channelList.size())
×
925
            {
926
                // make sure only one channel is in the list
927
                channelList.resize(1);
×
928
                valueList.append(rgbToGrey(col));
×
929
            }
930
        }
931
        else if (m_controlMode == ControlModeDimmer || m_dimmerControl)
×
932
        {
933
            // Collect all dimmers that affect current head:
934
            // They are the master dimmer (affects whole fixture)
935
            // and per-head dimmer.
936
            //
937
            // If there are no RGB or CMY channels, the least important* dimmer channel
938
            // is used to create grayscale image.
939
            //
940
            // The rest of the dimmer channels are set to full if dimmer control is
941
            // enabled and target color is > 0 (see
942
            // https://www.qlcplus.org/forum/viewtopic.php?f=29&t=11090)
943
            //
944
            // Note: If there is only one head, and only one dimmer channel,
945
            // make it a master dimmer in fixture definition.
946
            //
947
            // *least important - per head dimmer if present,
948
            // otherwise per fixture dimmer if present
949

950
            quint32 masterDim = fxi->masterIntensityChannel();
×
951
            quint32 headDim = head.channelNumber(QLCChannel::Intensity, QLCChannel::MSB);
×
952

953
            if (masterDim != QLCChannel::invalid())
×
954
            {
955
                channelList.append(masterDim);
×
956
                valueList.append(rgbToGrey(col));
×
957
            }
958

959
            if (headDim != QLCChannel::invalid() && headDim != masterDim)
×
960
            {
961
                channelList.append(headDim);
×
962
                // If a master dimmer is present, it carries the greyscale fade and the
963
                // per-head dimmer is just opened fully (on/off). With no master dimmer
964
                // (e.g. generic single-channel dimmers), the head dimmer must carry the
965
                // greyscale value itself, otherwise it would only ever output 0 or 255.
966
                if (masterDim != QLCChannel::invalid())
×
967
                    valueList.append(rgbToGrey(col) == 0 ? 0 : 255);
×
968
                else
969
                    valueList.append(rgbToGrey(col));
×
970
            }
971
        }
×
972
        else
973
        {
974
            if (m_controlMode == ControlModeWhite)
×
975
                channelList.append(head.channelNumber(QLCChannel::White, QLCChannel::MSB));
×
976
            else if (m_controlMode == ControlModeAmber)
×
977
                channelList.append(head.channelNumber(QLCChannel::Amber, QLCChannel::MSB));
×
978
            else if (m_controlMode == ControlModeUV)
×
979
                channelList.append(head.channelNumber(QLCChannel::UV, QLCChannel::MSB));
×
980

981
            valueList.append(rgbToGrey(col));
×
982
        }
983

984
        quint32 absAddress = fxi->universeAddress();
×
985

986
        for (int i = 0; i < channelList.count(); i++)
×
987
        {
988
            if (channelList.at(i) == QLCChannel::invalid())
×
989
                continue;
×
990

991
            quint32 universeIndex = floor((absAddress + channelList.at(i)) / 512);
×
992
            Universe *universe = universes.at(universeIndex);
×
993
            QSharedPointer<GenericFader> fader = getFader(universe);
×
994
            if (fader.isNull())
×
995
                continue;
×
996

997
            const quint32 fixtureID = grpHead.fxi;
×
998
            const quint32 channel = channelList.at(i);
×
999
            const uchar value = valueList.at(i);
×
1000
            fader->updateChannel(doc(), universe, fixtureID, channel, [this, value, fadeTime](FadeChannel &fc)
×
1001
            {
1002
                updateFaderValues(fc, value, fadeTime);
×
1003
            });
×
1004
        }
×
1005
    }
×
1006
}
×
1007

1008
uchar RGBMatrix::rgbToGrey(uint col)
×
1009
{
1010
    uchar r = qRed(col);
×
1011
    uchar g = qGreen(col);
×
1012
    uchar b = qBlue(col);
×
1013

1014
    // Special case: if R=G=B (grayscale), return the value directly.
1015
    // This avoids floating-point precision issues.
1016
    if (r == g && g == b)
×
1017
        return r;
×
1018

1019
    // the weights are taken from
1020
    // https://en.wikipedia.org/wiki/YUV#SDTV_with_BT.601
1021
    return uchar(round(0.299 * r + 0.587 * g + 0.114 * b));
×
1022
}
1023

1024
/*********************************************************************
1025
 * Attributes
1026
 *********************************************************************/
1027

1028
int RGBMatrix::adjustAttribute(qreal fraction, int attributeId)
×
1029
{
1030
    int attrIndex = Function::adjustAttribute(fraction, attributeId);
×
1031

1032
    if (attrIndex == Intensity)
×
1033
    {
1034
        foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
×
1035
        {
1036
            if (!fader.isNull())
×
1037
                fader->adjustIntensity(getAttributeValue(Function::Intensity));
×
1038
        }
×
1039
    }
1040
    else if (attrIndex >= Color1Attr && attrIndex <= ColorLastAttr)
×
1041
    {
1042
        applyColorAttribute(attrIndex - Color1Attr, getAttributeValue(attrIndex));
×
1043
    }
1044
    else if (attrIndex == PatternAttr)
×
1045
    {
1046
        applyPatternAttribute(getAttributeValue(PatternAttr));
×
1047
    }
1048

1049
    return attrIndex;
×
1050
}
1051

1052
void RGBMatrix::applyStyleAttributes()
×
1053
{
1054
    for (int i = 0; i < ColorAttributeCount; ++i)
×
1055
        applyColorAttribute(i, getAttributeValue(Color1Attr + i));
×
1056

1057
    applyPatternAttribute(getAttributeValue(PatternAttr));
×
1058
}
×
1059

1060
void RGBMatrix::applyColorAttribute(int colorIndex, qreal packedColor)
×
1061
{
1062
    if (colorIndex < 0 || colorIndex >= ColorAttributeCount)
×
1063
        return;
×
1064

1065
    int packed = qRound(packedColor);
×
1066
    QColor targetColor = packed < 0 ? QColor() :
1067
                                      QColor::fromRgb(static_cast<QRgb>((packed & RGBMatrixColorMask) | 0xFF000000));
×
1068
    if (getColor(colorIndex) == targetColor)
×
1069
        return;
×
1070

1071
    bool previous = m_applyingStyleAttributes;
×
1072
    m_applyingStyleAttributes = true;
×
1073
    setColor(colorIndex, targetColor);
×
1074
    m_applyingStyleAttributes = previous;
×
1075
}
1076

1077
void RGBMatrix::applyPatternAttribute(qreal patternIndex)
×
1078
{
1079
    if (doc() == NULL)
×
1080
        return;
×
1081

1082
    QStringList algoList = RGBAlgorithm::algorithms(doc());
×
1083
    if (algoList.isEmpty())
×
1084
        return;
×
1085

1086
    int idx = qRound(patternIndex);
×
1087
    if (idx < 0)
×
1088
        idx = 0;
×
1089
    else if (idx >= algoList.count())
×
1090
        idx = algoList.count() - 1;
×
1091

1092
    RGBAlgorithm *algo = RGBAlgorithm::algorithm(doc(), algoList.at(idx));
×
1093
    if (algo == NULL)
×
1094
        return;
×
1095

1096
    if (m_algorithm != NULL && m_algorithm->name() == algo->name())
×
1097
    {
1098
        delete algo;
×
1099
        return;
×
1100
    }
1101

1102
    algo->setColors(getColors());
×
1103

1104
    bool previous = m_applyingStyleAttributes;
×
1105
    m_applyingStyleAttributes = true;
×
1106
    setAlgorithm(algo);
×
1107
    m_applyingStyleAttributes = previous;
×
1108
}
×
1109

1110
/*************************************************************************
1111
 * Blend mode
1112
 *************************************************************************/
1113

1114
void RGBMatrix::setBlendMode(Universe::BlendMode mode)
×
1115
{
1116
    if (mode == blendMode())
×
1117
        return;
×
1118

1119
    foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
×
1120
    {
1121
        if (!fader.isNull())
×
1122
            fader->setBlendMode(mode);
×
1123
    }
×
1124

1125
    Function::setBlendMode(mode);
×
1126
    emit changed(id());
×
1127
}
1128

1129
/*************************************************************************
1130
 * Control Mode
1131
 *************************************************************************/
1132

1133
RGBMatrix::ControlMode RGBMatrix::controlMode() const
2✔
1134
{
1135
    return m_controlMode;
2✔
1136
}
1137

1138
void RGBMatrix::setControlMode(RGBMatrix::ControlMode mode)
3✔
1139
{
1140
    m_controlMode = mode;
3✔
1141
    emit changed(id());
3✔
1142
}
3✔
1143

1144
RGBMatrix::ControlMode RGBMatrix::stringToControlMode(QString mode)
1✔
1145
{
1146
    if (mode == KXMLQLCRGBMatrixControlModeRgb)
1✔
1147
        return ControlModeRgb;
1✔
1148
    else if (mode == KXMLQLCRGBMatrixControlModeAmber)
×
1149
        return ControlModeAmber;
×
1150
    else if (mode == KXMLQLCRGBMatrixControlModeWhite)
×
1151
        return ControlModeWhite;
×
1152
    else if (mode == KXMLQLCRGBMatrixControlModeUV)
×
1153
        return ControlModeUV;
×
1154
    else if (mode == KXMLQLCRGBMatrixControlModeDimmer)
×
1155
        return ControlModeDimmer;
×
1156
    else if (mode == KXMLQLCRGBMatrixControlModeShutter)
×
1157
        return ControlModeShutter;
×
1158

1159
    return ControlModeRgb;
×
1160
}
1161

1162
QString RGBMatrix::controlModeToString(RGBMatrix::ControlMode mode)
1✔
1163
{
1164
    switch(mode)
1✔
1165
    {
1166
        default:
1✔
1167
        case ControlModeRgb:
1168
            return QString(KXMLQLCRGBMatrixControlModeRgb);
1✔
1169
        break;
1170
        case ControlModeAmber:
×
1171
            return QString(KXMLQLCRGBMatrixControlModeAmber);
×
1172
        break;
1173
        case ControlModeWhite:
×
1174
            return QString(KXMLQLCRGBMatrixControlModeWhite);
×
1175
        break;
1176
        case ControlModeUV:
×
1177
            return QString(KXMLQLCRGBMatrixControlModeUV);
×
1178
        break;
1179
        case ControlModeDimmer:
×
1180
            return QString(KXMLQLCRGBMatrixControlModeDimmer);
×
1181
        break;
1182
        case ControlModeShutter:
×
1183
            return QString(KXMLQLCRGBMatrixControlModeShutter);
×
1184
        break;
1185
    }
1186
}
1187

1188

1189
/*************************************************************************
1190
 *************************************************************************
1191
 *                          RGBMatrixStep class
1192
 *************************************************************************
1193
 *************************************************************************/
1194

1195
RGBMatrixStep::RGBMatrixStep()
10✔
1196
    : m_direction(Function::Forward)
10✔
1197
    , m_currentStepIndex(0)
10✔
1198
    , m_stepColor(QColor())
10✔
1199
    , m_crDelta(0)
10✔
1200
    , m_cgDelta(0)
10✔
1201
    , m_cbDelta(0)
10✔
1202
{
1203

1204
}
10✔
1205

1206
void RGBMatrixStep::setCurrentStepIndex(int index)
×
1207
{
1208
    m_currentStepIndex = index;
×
1209
}
×
1210

1211
int RGBMatrixStep::currentStepIndex() const
1✔
1212
{
1213
    return m_currentStepIndex;
1✔
1214
}
1215

1216
void RGBMatrixStep::calculateColorDelta(const QColor& startColor, const QColor& endColor, const RGBAlgorithm *algorithm)
16✔
1217
{
1218
    m_crDelta = 0;
16✔
1219
    m_cgDelta = 0;
16✔
1220
    m_cbDelta = 0;
16✔
1221

1222
    if (endColor.isValid() && algorithm != NULL && algorithm->acceptColors() > 1)
16✔
1223
    {
1224
        m_crDelta = endColor.red() - startColor.red();
10✔
1225
        m_cgDelta = endColor.green() - startColor.green();
10✔
1226
        m_cbDelta = endColor.blue() - startColor.blue();
10✔
1227

1228
        //qDebug() << "Color deltas:" << m_crDelta << m_cgDelta << m_cbDelta;
1229
    }
1230
}
16✔
1231

1232
void RGBMatrixStep::setStepColor(QColor color)
×
1233
{
1234
    m_stepColor = color;
×
1235
}
×
1236

1237
QColor RGBMatrixStep::stepColor() const
6✔
1238
{
1239
    return m_stepColor;
6✔
1240
}
1241

1242
void RGBMatrixStep::updateStepColor(int stepIndex, QColor startColor, int stepsCount)
×
1243
{
1244
    if (stepsCount <= 0)
×
1245
        return;
×
1246

1247
    if (stepsCount == 1)
×
1248
    {
1249
        m_stepColor = startColor;
×
1250
    }
1251
    else
1252
    {
1253
        m_stepColor.setRed(startColor.red() + (m_crDelta * stepIndex / (stepsCount - 1)));
×
1254
        m_stepColor.setGreen(startColor.green() + (m_cgDelta * stepIndex / (stepsCount - 1)));
×
1255
        m_stepColor.setBlue(startColor.blue() + (m_cbDelta * stepIndex / (stepsCount - 1)));
×
1256
    }
1257

1258
    //qDebug() << "RGBMatrix step" << stepIndex << ", color:" << QString::number(m_stepColor.rgb(), 16);
1259
}
1260

1261
void RGBMatrixStep::initializeDirection(Function::Direction direction, const QColor& startColor, const QColor& endColor, int stepsCount, const RGBAlgorithm *algorithm)
×
1262
{
1263
    m_direction = direction;
×
1264

1265
    if (m_direction == Function::Forward)
×
1266
    {
1267
        setCurrentStepIndex(0);
×
1268
        setStepColor(startColor);
×
1269
    }
1270
    else
1271
    {
1272
        setCurrentStepIndex(stepsCount - 1);
×
1273

1274
        if (endColor.isValid())
×
1275
            setStepColor(endColor);
×
1276
        else
1277
            setStepColor(startColor);
×
1278
    }
1279

1280
    calculateColorDelta(startColor, endColor, algorithm);
×
1281
}
×
1282

1283
bool RGBMatrixStep::checkNextStep(Function::RunOrder order,
×
1284
                                  QColor startColor, QColor endColor, int stepsNumber)
1285
{
1286
    if (order == Function::PingPong)
×
1287
    {
1288
        if (m_direction == Function::Forward && (m_currentStepIndex + 1) == stepsNumber)
×
1289
        {
1290
            m_direction = Function::Backward;
×
1291
            m_currentStepIndex = stepsNumber - 2;
×
1292
            if (endColor.isValid())
×
1293
                m_stepColor = endColor;
×
1294

1295
            updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1296
        }
1297
        else if (m_direction == Function::Backward && (m_currentStepIndex - 1) < 0)
×
1298
        {
1299
            m_direction = Function::Forward;
×
1300
            m_currentStepIndex = 1;
×
1301
            m_stepColor = startColor;
×
1302
            updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1303
        }
1304
        else
1305
        {
1306
            if (m_direction == Function::Forward)
×
1307
                m_currentStepIndex++;
×
1308
            else
1309
                m_currentStepIndex--;
×
1310
            updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1311
        }
1312
    }
1313
    else if (order == Function::SingleShot)
×
1314
    {
1315
        if (m_direction == Function::Forward)
×
1316
        {
1317
            if (m_currentStepIndex >= stepsNumber - 1)
×
1318
                return false;
×
1319
            else
1320
            {
1321
                m_currentStepIndex++;
×
1322
                updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1323
            }
1324
        }
1325
        else
1326
        {
1327
            if (m_currentStepIndex <= 0)
×
1328
                return false;
×
1329
            else
1330
            {
1331
                m_currentStepIndex--;
×
1332
                updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1333
            }
1334
        }
1335
    }
1336
    else
1337
    {
1338
        if (m_direction == Function::Forward)
×
1339
        {
1340
            if (m_currentStepIndex >= stepsNumber - 1)
×
1341
            {
1342
                m_currentStepIndex = 0;
×
1343
                m_stepColor = startColor;
×
1344
            }
1345
            else
1346
            {
1347
                m_currentStepIndex++;
×
1348
                updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1349
            }
1350
        }
1351
        else
1352
        {
1353
            if (m_currentStepIndex <= 0)
×
1354
            {
1355
                m_currentStepIndex = stepsNumber - 1;
×
1356
                if (endColor.isValid())
×
1357
                    m_stepColor = endColor;
×
1358
            }
1359
            else
1360
            {
1361
                m_currentStepIndex--;
×
1362
                updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1363
            }
1364
        }
1365
    }
1366

1367
    return true;
×
1368
}
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