From c2c16457345ccc67f5fea2540dfe55f9a9f176f3 Mon Sep 17 00:00:00 2001 From: Micah Elizabeth Scott <micah@scanlime.org> Date: Sat, 10 May 2014 02:14:44 -0700 Subject: [PATCH] New mapping command supports reordering color channels --- doc/fc_server_config.md | 10 ++++-- server/src/enttecdmxdevice.cpp | 31 ++++++----------- server/src/fcdevice.cpp | 62 ++++++++++++++++++++++++++++++++-- server/src/opc.h | 31 +++++++++++++++++ 4 files changed, 109 insertions(+), 25 deletions(-) diff --git a/doc/fc_server_config.md b/doc/fc_server_config.md index 13a8987..57d95dd 100644 --- a/doc/fc_server_config.md +++ b/doc/fc_server_config.md @@ -90,9 +90,13 @@ Fadecandy Devices Supported mapping objects for Fadecandy devices: -* [ *OPC Channel*, *First OPC Pixel*, *First output pixel*, *pixel count* ] +* [ *OPC Channel*, *First OPC Pixel*, *First output pixel*, *Pixel count* ] * Map a contiguous range of pixels from the specified OPC channel to the current device * For Fadecandy devices, output pixels are numbered from 0 through 511. Strand 1 begins at index 0, strand 2 begins at index 64, etc. +* [ *OPC Channel*, *First OPC Pixel*, *First output pixel*, *Pixel count*, *Color channels* ] + * As above, but the mapping between color channels and WS2811 output channels can be changed. + * The "Color channels" must be a 3-letter string, where each letter corresponds to one of the WS2811 outputs. + * Each letter can be "r", "g", or "b" to choose the red, green, or blue channel respectively, or "l" to use the average luminosity. Other settings for Fadecandy devices: @@ -102,7 +106,7 @@ led | true / false / null | null | Is the LED on, off, or under aut dither | true / false | true | Is dithering enabled? interpolate | true / false | true | Is inter-frame interpolation enabled? -The following example config file supports two Fadecandy devices with distinct serial numbers. They both receive data from OPC channel #0. The first 512 pixels map to the first Fadecandy device. The next 64 pixels map to the entire first strand of the second Fadecandy device, and the next 32 pixels map to the beginning of the third strand. +The following example config file supports two Fadecandy devices with distinct serial numbers. They both receive data from OPC channel #0. The first 512 pixels map to the first Fadecandy device. The next 64 pixels map to the entire first strand of the second Fadecandy device, and the next 32 pixels map to the beginning of the third strand with the color channels in Blue, Green, Red order. { "listen": ["127.0.0.1", 7890], @@ -127,7 +131,7 @@ The following example config file supports two Fadecandy devices with distinct s "serial": "FFFFFFFFFFFF0021003B200314134D44", "map": [ [ 0, 512, 0, 64 ], - [ 0, 576, 128, 32 ] + [ 0, 576, 128, 32, "bgr" ] ] } ] diff --git a/server/src/enttecdmxdevice.cpp b/server/src/enttecdmxdevice.cpp index b20bf4d..ebae713 100644 --- a/server/src/enttecdmxdevice.cpp +++ b/server/src/enttecdmxdevice.cpp @@ -22,6 +22,9 @@ */ #include "enttecdmxdevice.h" +#include "rapidjson/stringbuffer.h" +#include "rapidjson/writer.h" +#include "opc.h" #include <sstream> #include <iostream> @@ -305,32 +308,20 @@ void EnttecDMXDevice::opcMapPixelColors(const OPC::Message &msg, const Value &in } const uint8_t *pixel = msg.data + (pixelIndex * 3); + uint8_t value; - switch (pixelColor[0]) { - - case 'r': - setChannel(dmxChannel, pixel[0]); - break; - - case 'g': - setChannel(dmxChannel, pixel[1]); - break; - - case 'b': - setChannel(dmxChannel, pixel[2]); - break; - - case 'l': - setChannel(dmxChannel, (unsigned(pixel[0]) + unsigned(pixel[1]) + unsigned(pixel[2])) / 3); - break; - + if (OPC::pickColorChannel(value, pixelColor[0], pixel)) { + setChannel(dmxChannel, value); + return; } - return; } } // Still haven't found a match? if (mVerbose) { - std::clog << "Unsupported JSON mapping instruction\n"; + rapidjson::GenericStringBuffer<rapidjson::UTF8<>> buffer; + rapidjson::Writer<rapidjson::GenericStringBuffer<rapidjson::UTF8<>>> writer(buffer); + inst.Accept(writer); + std::clog << "Unsupported JSON mapping instruction: " << buffer.GetString() << "\n"; } } diff --git a/server/src/fcdevice.cpp b/server/src/fcdevice.cpp index 9b9e868..d953c03 100644 --- a/server/src/fcdevice.cpp +++ b/server/src/fcdevice.cpp @@ -22,6 +22,8 @@ */ #include "fcdevice.h" +#include "rapidjson/stringbuffer.h" +#include "rapidjson/writer.h" #include "opc.h" #include <math.h> #include <iostream> @@ -523,7 +525,8 @@ void FCDevice::opcMapPixelColors(const OPC::Message &msg, const Value &inst) * into our framebuffer. This looks for any mapping instructions that we * recognize: * - * [ OPC Channel, First OPC Pixel, First output pixel, pixel count ] + * [ OPC Channel, First OPC Pixel, First output pixel, Pixel count ] + * [ OPC Channel, First OPC Pixel, First output pixel, Color channels ] */ unsigned msgPixelCount = msg.length() / 3; @@ -568,9 +571,64 @@ void FCDevice::opcMapPixelColors(const OPC::Message &msg, const Value &inst) } } + if (inst.IsArray() && inst.Size() == 5) { + // Map a range from an OPC channel to our framebuffer, with color channel swizzling + + const Value &vChannel = inst[0u]; + const Value &vFirstOPC = inst[1]; + const Value &vFirstOut = inst[2]; + const Value &vCount = inst[3]; + const Value &vColorChannels = inst[4]; + + if (vChannel.IsUint() && vFirstOPC.IsUint() && vFirstOut.IsUint() && vCount.IsUint() + && vColorChannels.IsString() && vColorChannels.GetStringLength() == 3) { + + unsigned channel = vChannel.GetUint(); + unsigned firstOPC = vFirstOPC.GetUint(); + unsigned firstOut = vFirstOut.GetUint(); + unsigned count = vCount.GetUint(); + const char *colorChannels = vColorChannels.GetString(); + + if (channel != msg.channel) { + return; + } + + // Clamping, overflow-safe + firstOPC = std::min<unsigned>(firstOPC, msgPixelCount); + firstOut = std::min<unsigned>(firstOut, unsigned(NUM_PIXELS)); + count = std::min<unsigned>(count, msgPixelCount - firstOPC); + count = std::min<unsigned>(count, NUM_PIXELS - firstOut); + + // Copy pixels + const uint8_t *inPtr = msg.data + (firstOPC * 3); + unsigned outIndex = firstOut; + bool success = true; + + while (count--) { + uint8_t *outPtr = fbPixel(outIndex++); + + for (int channel = 0; channel < 3; channel++) { + if (!OPC::pickColorChannel(outPtr[channel], colorChannels[channel], inPtr)) { + success = false; + break; + } + } + + inPtr += 3; + } + + if (success) { + return; + } + } + } + // Still haven't found a match? if (mVerbose) { - std::clog << "Unsupported JSON mapping instruction\n"; + rapidjson::GenericStringBuffer<rapidjson::UTF8<>> buffer; + rapidjson::Writer<rapidjson::GenericStringBuffer<rapidjson::UTF8<>>> writer(buffer); + inst.Accept(writer); + std::clog << "Unsupported JSON mapping instruction: " << buffer.GetString() << "\n"; } } diff --git a/server/src/opc.h b/server/src/opc.h index ebf16d9..5e3ba39 100644 --- a/server/src/opc.h +++ b/server/src/opc.h @@ -58,4 +58,35 @@ namespace OPC { static const unsigned HEADER_BYTES = 4; typedef void (*callback_t)(Message &msg, void *context); + + // Common idiom for choosing color channels based on a character string + inline bool pickColorChannel(uint8_t &output, char selector, const uint8_t *rgb) + { + switch (selector) { + + case 'r': + case 'R': + output = rgb[0]; + return true; + + case 'g': + case 'G': + output = rgb[1]; + return true; + + case 'b': + case 'B': + output = rgb[2]; + return true; + + case 'l': + case 'L': + output = (unsigned(rgb[0]) + unsigned(rgb[1]) + unsigned(rgb[2])) / 3; + return true; + + default: + return false; + } + } + } -- GitLab