Skip to content

Commit 6ecdcd2

Browse files
committed
refactor(pixeloverlay): remove unused colorOrder parameter and update related logic for channel handling
1 parent deafb0d commit 6ecdcd2

6 files changed

Lines changed: 185 additions & 207 deletions

File tree

src/channeloutput/PixelString.cpp

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -897,20 +897,31 @@ void PixelString::AutoCreateOverlayModels(const std::vector<PixelString*>& strin
897897
uint32_t strings = vs.size();
898898
uint32_t strands = 1;
899899
uint32_t maxChan = 0;
900+
uint32_t minChannelsPerNode = UINT32_MAX;
900901
int8_t rn = -1;
901902
for (auto& a : vs) {
902903
if (a) {
903904
startChannel = std::min(startChannel, (uint32_t)a->startChannel);
904905
maxChan = std::max(maxChan, (uint32_t)a->startChannel + (a->pixelCount * a->channelsPerNode() / (a->groupCount ? a->groupCount : 1)));
905906
channelsPerNode = std::max(channelsPerNode, (uint32_t)a->channelsPerNode());
907+
minChannelsPerNode = std::min(minChannelsPerNode, (uint32_t)a->channelsPerNode());
906908
rn = std::max(rn, a->receiverNum);
907909
} else {
908910
--strings;
909911
}
910912
}
911-
// Channel data is always in R,G,B[,W] order; the output driver remaps to the
912-
// hardware wire order. The model's ColorOrder describes the channel data.
913-
std::string colorOrder = (channelsPerNode >= 4) ? "RGBW" : "RGB";
913+
if (strings && minChannelsPerNode != channelsPerNode) {
914+
// One model cannot describe two node widths. Taking the wider of
915+
// them mis-addresses every pixel on the narrower strings, and the
916+
// channel order reported for the model is wrong for them too, so
917+
// say so rather than creating a silently broken model.
918+
LogWarn(VB_CHANNELOUT, "Overlay model %s spans strings of %u and %u channels per node; using %u - pixels on the narrower strings will not line up\n",
919+
name.c_str(), minChannelsPerNode, channelsPerNode, channelsPerNode);
920+
}
921+
// The model's channel order is derived from channelsPerNode by
922+
// PixelOverlayModel - the virtual string's own colour order describes
923+
// the wire, which is applied later by prepareOutput(), and must not be
924+
// copied onto the model or it would be applied twice.
914925
int32_t channelCount = maxChan - startChannel;
915926

916927
if (name.find("Tree") != std::string::npos || name.find("TREE") != std::string::npos || name.find("tree") != std::string::npos || name.find("Vert") != std::string::npos || name.find("vert") != std::string::npos) {
@@ -921,7 +932,7 @@ void PixelString::AutoCreateOverlayModels(const std::vector<PixelString*>& strin
921932
if ((channelCount > 0) && (rn == -1)) {
922933
autoModelNames.push_back(name);
923934
PixelOverlayManager::INSTANCE.addAutoOverlayModel(name, startChannel, channelCount, channelsPerNode, orientation,
924-
startLocation, strings, strands, colorOrder);
935+
startLocation, strings, strands);
925936
}
926937
}
927938
}

src/overlays/PixelOverlay.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1888,6 +1888,9 @@ void PixelOverlayManager::RegisterCommands() {
18881888
CommandManager::INSTANCE.addCategorizedCommand(new ApplyEffectOverlayCommand(this), "Pixel Overlay", 1);
18891889
}
18901890

1891+
// colorOrder is accepted for source compatibility with existing channel output
1892+
// plugins but is no longer used: a model addresses fppd's channel data, which is
1893+
// always R,G,B[,W], so PixelOverlayModel derives the order from the node width.
18911894
void PixelOverlayManager::addAutoOverlayModel(const std::string& name,
18921895
uint32_t startChannel, uint32_t channelCount, uint32_t channelPerNode,
18931896
const std::string& orientation, const std::string& startLocation,
@@ -1902,7 +1905,6 @@ void PixelOverlayManager::addAutoOverlayModel(const std::string& name,
19021905
val["Orientation"] = orientation;
19031906
val["StartCorner"] = startLocation;
19041907
val["ChannelCountPerNode"] = channelPerNode;
1905-
val["ColorOrder"] = colorOrder;
19061908
val["autoCreated"] = true;
19071909

19081910
addModel(val);

src/overlays/PixelOverlay.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ class PixelOverlayManager {
5858
bool isAutoCreatePixelOverlayModels() const {
5959
return autoCreate;
6060
}
61+
// colorOrder is ignored - the model's channel order follows from channelPerNode.
62+
// The parameter is retained so plugins built against older headers still link.
6163
void addAutoOverlayModel(const std::string& name,
6264
uint32_t startChannel, uint32_t channelCount, uint32_t channelPerNode,
6365
const std::string& orientation, const std::string& startLocation,

src/overlays/PixelOverlayModel.cpp

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,24 @@
5656
#include "PixelOverlayEffects.h"
5757
#include "PixelOverlayModel.h"
5858

59+
// The channel order a model reads and writes. This is the order of fppd's
60+
// channel data, not of the wire: setPixelValue() stores r, then g, then b,
61+
// then w into consecutive channels, and the output driver is what permutes
62+
// those into the hardware's colour order. It therefore follows entirely from
63+
// how many channels a node occupies.
64+
static const char* ChannelDataOrder(int channelsPerNode) {
65+
switch (channelsPerNode) {
66+
case 1:
67+
return "W";
68+
case 2:
69+
return "RG";
70+
case 3:
71+
return "RGB";
72+
default:
73+
return "RGBW";
74+
}
75+
}
76+
5977
static uint8_t* createChannelDataMemory(const std::string& dataName, uint32_t size) {
6078
mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
6179
int f = shm_open(dataName.c_str(), O_RDWR | O_CREAT, mode);
@@ -122,11 +140,12 @@ PixelOverlayModel::PixelOverlayModel(const Json::Value& c) :
122140
}
123141
bytesPerPixel = (channelsPerNode >= 4) ? 4 : 3;
124142

125-
// Color order is consumed by the web UI (testing.php) via the model's JSON
126-
// config; ensure it always has a value so the API exposes it.
127-
if (!config.isMember("ColorOrder")) {
128-
config["ColorOrder"] = "RGB";
129-
}
143+
// A model addresses fppd's channel data, which is always R,G,B[,W] - the
144+
// output driver is what applies the hardware wire order. So the order the
145+
// model sees is fully determined by the node width and is derived here
146+
// rather than stored: any ColorOrder in the saved config is overwritten.
147+
// Exported read-only through the API so the UI can show it.
148+
config["ColorOrder"] = ChannelDataOrder(channelsPerNode);
130149

131150
if (config["Type"].asString() != "Channel") {
132151
if (config.isMember("PixelSize") && config["PixelSize"].asInt() > 1) {

www/pixeloverlaymodels.php

Lines changed: 39 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -220,20 +220,30 @@ function GetOrientationInput(currentValue, attr) {
220220
return str;
221221
}
222222

223-
function GetColorOrderSelect(currentValue, attr) {
224-
var options = [
225-
"RGB", "RBG", "GRB", "GBR", "BRG", "BGR",
226-
"RGBW", "RBGW", "GRBW", "GBRW", "BRGW", "BGRW"
227-
];
228-
if (!currentValue) currentValue = "RGB";
229-
var str = "<select class='colorOrder'" + attr + ">";
223+
// 1, 3 and 4 are the node widths FPP's outputs actually produce - a plain
224+
// 1-4 spinner also offers 2, which no output can drive and which silently
225+
// discards the blue channel. A width already saved in the config is kept
226+
// as an extra choice rather than quietly rewritten.
227+
function GetChannelsPerNodeInput(currentValue, attr) {
228+
var options = [1, 3, 4];
229+
var current = parseInt(currentValue);
230+
if (isNaN(current) || current < 1) {
231+
current = 3;
232+
}
233+
if (options.indexOf(current) == -1) {
234+
options.push(current);
235+
options.sort(function (a, b) { return a - b; });
236+
}
237+
238+
var str = "<select class='form-select cpn'" + attr + ">";
230239
for (var i = 0; i < options.length; i++) {
231240
str += "<option value='" + options[i] + "'";
232-
if (currentValue == options[i])
241+
if (current == options[i])
233242
str += " selected";
234243
str += ">" + options[i] + "</option>";
235244
}
236245
str += "</select>";
246+
237247
return str;
238248
}
239249

@@ -334,12 +344,10 @@ function PopulateChannelMemMapTable(data) {
334344

335345
switch (model.Type) {
336346
case "Channel":
337-
var colorOrder = model.ColorOrder || "RGB";
338347
postr += "<td><span class='hidden type'>" + model.Type + "</span>" + model.Type + "</td>" +
339348
"<td><input class='start' type='text' size='6' maxlength='6' value='" + model.StartChannel + "'" + attr + "></td>" +
340349
"<td><input class='cnt' type='text' size='6' maxlength='6' value='" + model.ChannelCount + "'" + attr + "></td>" +
341-
"<td><input class='cpn' type='number' min='1' max='4' value='" + ChannelCountPerNode + "'" + attr + "></td>" +
342-
"<td>" + GetColorOrderSelect(colorOrder, attr) + "</td>" +
350+
"<td>" + GetChannelsPerNodeInput(ChannelCountPerNode, attr) + "</td>" +
343351
"<td style=\"white-space: nowrap;\">" + GetOrientationInput(model.Orientation + orientationDetails(model), attr) + "</td>";
344352
if (model.Orientation != "custom") {
345353
postr += "<td>" + GetStartingCornerInput(model.StartCorner, attr) + "</td>" +
@@ -350,13 +358,21 @@ function PopulateChannelMemMapTable(data) {
350358
"<td><input class='strcnt' type='hidden' value='" + model.StringCount + "'></td>" +
351359
"<td><input class='strands' type='hidden' value='" + model.StrandsPerString + "'></td>";
352360
}
353-
var xlchecked = "";
354-
if (model.xLights) {
355-
xlchecked = " checked";
356-
}
357-
postr += "<td><input class='xlights' type='checkbox'" + xlchecked + " disabled>";
358-
if (model.xLights) {
359-
postr += " <i class='fas fa-eye' style='cursor:pointer; color:#5bc0de;' title='Preview model layout' onclick='showModelPreview(" + JSON.stringify(model.Name) + ")'></i>";
361+
// This column is headed "xLights Generated" on the editable
362+
// table and "Submodels" on the auto created one. The checkbox
363+
// is meaningless for an auto created model - it is never an
364+
// xLights import - but the submodel expander still applies, so
365+
// the cell is always emitted and only its contents differ.
366+
postr += "<td>";
367+
if (!model.autoCreated) {
368+
var xlchecked = "";
369+
if (model.xLights) {
370+
xlchecked = " checked";
371+
}
372+
postr += "<input class='xlights' type='checkbox'" + xlchecked + " disabled>";
373+
if (model.xLights) {
374+
postr += " <i class='fas fa-eye' style='cursor:pointer; color:#5bc0de;' title='Preview model layout' onclick='showModelPreview(" + JSON.stringify(model.Name) + ")'></i>";
375+
}
360376
}
361377
var subModels = getSubModels(model.Name);
362378
if (subModels && subModels.length > 0) {
@@ -579,8 +595,7 @@ function SetChannelMemMaps() {
579595
model.StartCorner = $this.find("select.corner").val();
580596
model.StringCount = parseInt($this.find("input.strcnt").val());
581597
model.StrandsPerString = parseInt($this.find("input.strands").val());
582-
model.ChannelCountPerNode = parseInt($this.find("input.cpn").val());
583-
model.ColorOrder = $this.find("select.colorOrder").val();
598+
model.ChannelCountPerNode = parseInt($this.find(".cpn").val());
584599
model.xLights = $this.find("input.xlights").is(':checked');
585600

586601
if ((model.StartChannel > 0) &&
@@ -656,8 +671,7 @@ function AddNewChannelModel() {
656671
"<td><span class='hidden type'>Channel</span>Channel</td>" +
657672
"<td><input class='start' type='text' size='6' maxlength='6' value='1'></td>" +
658673
"<td><input class='cnt' type='text' size='6' maxlength='6' value='150'></td>" +
659-
"<td><input class='cpn' type='number' min='1' max='4' value='3'></td>" +
660-
"<td>" + GetColorOrderSelect("RGB", "") + "</td>" +
674+
"<td>" + GetChannelsPerNodeInput(3, "") + "</td>" +
661675
"<td>" + GetOrientationInput("", "") + "</td>" +
662676
"<td>" + GetStartingCornerInput('') + "</td>" +
663677
"<td><input class='strcnt' type='text' size='3' maxlength='3' value='1'></td>" +
@@ -987,38 +1001,6 @@ function pageSpecific_PageLoad_PostDOMLoad_ActionsSetup() {
9871001
DisableButtonClass('btnDelete');
9881002
}
9891003
});
990-
991-
// Keep Ch./Node consistent with the selected Color Order: RGBW orders are
992-
// 4 channels per node, RGB orders 3. Leave 1/2-channel models alone when
993-
// a plain RGB order is selected since color order doesn't apply to them.
994-
$('#channelMemMaps').on('change', 'select.colorOrder', function () {
995-
var cpnInput = $(this).closest('tr').find('input.cpn');
996-
if (cpnInput.length == 0) {
997-
return;
998-
}
999-
var isRGBW = $(this).val().indexOf('W') != -1;
1000-
if (isRGBW) {
1001-
cpnInput.val(4);
1002-
} else if (parseInt(cpnInput.val()) == 4) {
1003-
cpnInput.val(3);
1004-
}
1005-
});
1006-
1007-
// And the reverse: dropping Ch./Node below 4 on an RGBW model reverts the
1008-
// Color Order to a 3-channel one, raising it to 4 selects RGBW.
1009-
$('#channelMemMaps').on('change', 'input.cpn', function () {
1010-
var orderSelect = $(this).closest('tr').find('select.colorOrder');
1011-
if (orderSelect.length == 0) {
1012-
return;
1013-
}
1014-
var order = orderSelect.val() || 'RGB';
1015-
var isRGBW = order.indexOf('W') != -1;
1016-
if (parseInt($(this).val()) == 4 && !isRGBW) {
1017-
orderSelect.val(order + 'W');
1018-
} else if (parseInt($(this).val()) != 4 && isRGBW) {
1019-
orderSelect.val(order.replace('W', ''));
1020-
}
1021-
});
10221004
}
10231005

10241006
</script>
@@ -1073,8 +1055,7 @@ class='buttons btn-success ml-1'>
10731055
<th><span title='Type'>Type</span></th>
10741056
<th><span title='Start Channel'>Start Ch.</span></th>
10751057
<th><span title='Channel Count'>Ch. Count</span></th>
1076-
<th><span title='Chan Per Node'>Ch./Node</span></th>
1077-
<th><span title='Color Order (RGB, RGBW, etc.)'>Color Order</span></th>
1058+
<th><span title='Channels each node occupies. 1, 3 and 4 are the widths FPP&apos;s outputs produce.'>Ch./Node</span></th>
10781059
<th><span title='String Orientation'>Orientation</span></th>
10791060
<th><span title='Starting Corner'>Start Corner</span></th>
10801061
<th><span title='Number of Strings or Width of FB/X11/Sub-Model'>Strings</span></th>
@@ -1108,14 +1089,14 @@ class='buttons btn-success ml-1'>
11081089
<th><span title='Type'>Type</span></th>
11091090
<th><span title='Start Channel'>Start Ch.</span></th>
11101091
<th><span title='Channel Count'>Ch. Count</span></th>
1111-
<th><span title='Chan Per Node'>Ch./Node</span></th>
1112-
<th><span title='Color Order (RGB, RGBW, etc.)'>Color Order</span></th>
1092+
<th><span title='Channels each node occupies. 1, 3 and 4 are the widths FPP&apos;s outputs produce.'>Ch./Node</span></th>
11131093
<th><span title='String Orientation'>Orientation</span></th>
11141094
<th><span title='Starting Corner'>Start Corner</span></th>
11151095
<th><span title='Number of Strings or Width of FB/X11/Sub-Model'>Strings</span></th>
11161096
<th><span
11171097
title='Number of Strands Per String or Height of FB/X11/Sub-Model'>Strands</span>
11181098
</th>
1099+
<th><span title='xLights submodels defined for this model'>Submodels</span></th>
11191100
<th><span title='Running Effect'>Running Effect</span></th>
11201101
</tr>
11211102
</thead>

0 commit comments

Comments
 (0)