Inverting Red and Blue in an LED array
in
Contributed Library Questions
•
5 months ago
Hey guys,
So have kind of an off the wall question. I recently purchased a number of LED addressable strips from Sparkfun. The things are amazing, however, it seems that the red and blue inputs are reversed. When I run a test sketch and flood the wall with red, then green, then blue, it actually comes out blue, then green, then red. I'm think this is a hardware issue with the strips but am wondering how I can invert those two colors in my processing sketch.
The sketch itself is utilizing Syphon (info can be found here if you are not familiar with it
http://syphon.v002.info), and a MAX patch to take video information captured from my desktop and making it available as a Syphon server, which is then grabbed by the Processing sketch and piped out to an Arduino Uno that pushes that video information out to the LED array mentioned early.
Long story short, what is the best way to invert just the red and blue values so that way my video information actually looks like what is being captured from the desktop?
Any information would be grand.
Here are the pieces:
Processing:
- import codeanticode.syphon.*;
- import processing.serial.*;
- import java.awt.*;
- import java.awt.image.*;
- // GLOBAL VARIABLES ---- You probably won't need to modify any of this -------
- byte[] serialData = new byte[6 + leds.length * 3];
- short[][] ledColor = new short[leds.length][3],
- prevColor = new short[leds.length][3];
- byte[][] gamma = new byte[256][3];
- int nDisplays = displays.length;
- Robot[] bot = new Robot[displays.length];
- Rectangle[] dispBounds = new Rectangle[displays.length],
- ledBounds; // Alloc'd only if per-LED captures
- int[][] pixelOffset = new int[leds.length][256],
- screenData; // Alloc'd only if full-screen captures
- DisposeHandler dh; // For disabling LEDs on exit
- PGraphics S_img;
- SyphonClient client;
- Serial port;
- static final short minBrightness = 0;
- static final short fade = 75;
- static final int pixelSize = 20;
- static final boolean useFullScreenCaps = true;
- static final int timeout = 5000; // 5 seconds
- static final int displays[][] = new int[][] { {0,32,16} };
- static final int leds[][] = new int[][] {
- {0,0,0}, {0,1,0}, {0,2,0}, {0,3,0}, {0,4,0}, {0,5,0}, {0,6,0}, {0,7,0}, {0,8,0}, {0,9,0}, {0,10,0}, {0,11,0}, {0,12,0}, {0,13,0}, {0,14,0}, {0,15,0}, {0,16,0}, {0,17,0}, {0,18,0}, {0,19,0}, {0,20,0}, {0,21,0}, {0,22,0}, {0,23,0}, {0,24,0}, {0,25,0}, {0,26,0}, {0,27,0}, {0,28,0}, {0,29,0}, {0,30,0}, {0,31,0},
- {0,31,1}, {0,30,1}, {0,29,1}, {0,28,1}, {0,27,1}, {0,26,1}, {0,25,1}, {0,24,1}, {0,23,1}, {0,22,1}, {0,21,1}, {0,20,1}, {0,19,1}, {0,18,1}, {0,17,1}, {0,16,1}, {0,15,1}, {0,14,1}, {0,13,1}, {0,12,1}, {0,11,1}, {0,10,1}, {0,9,1}, {0,8,1}, {0,7,1}, {0,6,1}, {0,5,1}, {0,4,1}, {0,3,1}, {0,2,1}, {0,1,1}, {0,0,1},
- {0,0,2}, {0,1,2}, {0,2,2}, {0,3,2}, {0,4,2}, {0,5,2}, {0,6,2}, {0,7,2}, {0,8,2}, {0,9,2}, {0,10,2}, {0,11,2}, {0,12,2}, {0,13,2}, {0,14,2}, {0,15,2}, {0,16,2}, {0,17,2}, {0,18,2}, {0,19,2}, {0,20,2}, {0,21,2}, {0,22,2}, {0,23,2}, {0,24,2}, {0,25,2}, {0,26,2}, {0,27,2}, {0,28,2}, {0,29,2}, {0,30,2}, {0,31,2},
- {0,31,3}, {0,30,3}, {0,29,3}, {0,28,3}, {0,27,3}, {0,26,3}, {0,25,3}, {0,24,3}, {0,23,3}, {0,22,3}, {0,21,3}, {0,20,3}, {0,19,3}, {0,18,3}, {0,17,3}, {0,16,3}, {0,15,3}, {0,14,3}, {0,13,3}, {0,12,3}, {0,11,3}, {0,10,3}, {0,9,3}, {0,8,3}, {0,7,3}, {0,6,3}, {0,5,3}, {0,4,3}, {0,3,3}, {0,2,3}, {0,1,3}, {0,0,3},
- {0,0,4}, {0,1,4}, {0,2,4}, {0,3,4}, {0,4,4}, {0,5,4}, {0,6,4}, {0,7,4}, {0,8,4}, {0,9,4}, {0,10,4}, {0,11,4}, {0,12,4}, {0,13,4}, {0,14,4}, {0,15,4}, {0,16,4}, {0,17,4}, {0,18,4}, {0,19,4}, {0,20,4}, {0,21,4}, {0,22,4}, {0,23,4}, {0,24,4}, {0,25,4}, {0,26,4}, {0,27,4}, {0,28,4}, {0,29,4}, {0,30,4}, {0,31,4},
- {0,31,5}, {0,30,5}, {0,29,5}, {0,28,5}, {0,27,5}, {0,26,5}, {0,25,5}, {0,24,5}, {0,23,5}, {0,22,5}, {0,21,5}, {0,20,5}, {0,19,5}, {0,18,5}, {0,17,5}, {0,16,5}, {0,15,5}, {0,14,5}, {0,13,5}, {0,12,5}, {0,11,5}, {0,10,5}, {0,9,5}, {0,8,5}, {0,7,5}, {0,6,5}, {0,5,5}, {0,4,5}, {0,3,5}, {0,2,5}, {0,1,5}, {0,0,5},
- {0,0,6}, {0,1,6}, {0,2,6}, {0,3,6}, {0,4,6}, {0,5,6}, {0,6,6}, {0,7,6}, {0,8,6}, {0,9,6}, {0,10,6}, {0,11,6}, {0,12,6}, {0,13,6}, {0,14,6}, {0,15,6}, {0,16,6}, {0,17,6}, {0,18,6}, {0,19,6}, {0,20,6}, {0,21,6}, {0,22,6}, {0,23,6}, {0,24,6}, {0,25,6}, {0,26,6}, {0,27,6}, {0,28,6}, {0,29,6}, {0,30,6}, {0,31,6},
- {0,31,7}, {0,30,7}, {0,29,7}, {0,28,7}, {0,27,7}, {0,26,7}, {0,25,7}, {0,24,7}, {0,23,7}, {0,22,7}, {0,21,7}, {0,20,7}, {0,19,7}, {0,18,7}, {0,17,7}, {0,16,7}, {0,15,7}, {0,14,7}, {0,13,7}, {0,12,7}, {0,11,7}, {0,10,7}, {0,9,7}, {0,8,7}, {0,7,7}, {0,6,7}, {0,5,7}, {0,4,7}, {0,3,7}, {0,2,7}, {0,1,7}, {0,0,7},
- {0,0,8}, {0,1,8}, {0,2,8}, {0,3,8}, {0,4,8}, {0,5,8}, {0,6,8}, {0,7,8}, {0,8,8}, {0,9,8}, {0,10,8}, {0,11,8}, {0,12,8}, {0,13,8}, {0,14,8}, {0,15,8}, {0,16,8}, {0,17,8}, {0,18,8}, {0,19,8}, {0,20,8}, {0,21,8}, {0,22,8}, {0,23,8}, {0,24,8}, {0,25,8}, {0,26,8}, {0,27,8}, {0,28,8}, {0,29,8}, {0,30,8}, {0,31,8},
- {0,31,9}, {0,30,9}, {0,29,9}, {0,28,9}, {0,27,9}, {0,26,9}, {0,25,9}, {0,24,9}, {0,23,9}, {0,22,9}, {0,21,9}, {0,20,9}, {0,19,9}, {0,18,9}, {0,17,9}, {0,16,9}, {0,15,9}, {0,14,9}, {0,13,9}, {0,12,9}, {0,11,9}, {0,10,9}, {0,9,9}, {0,8,9}, {0,7,9}, {0,6,9}, {0,5,9}, {0,4,9}, {0,3,9}, {0,2,9}, {0,1,9}, {0,0,9},
- {0,0,10}, {0,1,10}, {0,2,10}, {0,3,10}, {0,4,10}, {0,5,10}, {0,6,10}, {0,7,10}, {0,8,10}, {0,9,10}, {0,10,10}, {0,11,10}, {0,12,10}, {0,13,10}, {0,14,10}, {0,15,10}, {0,16,10}, {0,17,10}, {0,18,10}, {0,19,10}, {0,20,10}, {0,21,10}, {0,22,10}, {0,23,10}, {0,24,10}, {0,25,10}, {0,26,10}, {0,27,10}, {0,28,10}, {0,29,10}, {0,30,10}, {0,31,10},
- {0,31,11}, {0,30,11}, {0,29,11}, {0,28,11}, {0,27,11}, {0,26,11}, {0,25,11}, {0,24,11}, {0,23,11}, {0,22,11}, {0,21,11}, {0,20,11}, {0,19,11}, {0,18,11}, {0,17,11}, {0,16,11}, {0,15,11}, {0,14,11}, {0,13,11}, {0,12,11}, {0,11,11}, {0,10,11}, {0,9,11}, {0,8,11}, {0,7,11}, {0,6,11}, {0,5,11}, {0,4,11}, {0,3,11}, {0,2,11}, {0,1,11}, {0,0,11},
- {0,0,12}, {0,1,12}, {0,2,12}, {0,3,12}, {0,4,12}, {0,5,12}, {0,6,12}, {0,7,12}, {0,8,12}, {0,9,12}, {0,10,12}, {0,11,12}, {0,12,12}, {0,13,12}, {0,14,12}, {0,15,12}, {0,16,12}, {0,17,12}, {0,18,12}, {0,19,12}, {0,20,12}, {0,21,12}, {0,22,12}, {0,23,12}, {0,24,12}, {0,25,12}, {0,26,12}, {0,27,12}, {0,28,12}, {0,29,12}, {0,30,12}, {0,31,12},
- {0,31,13}, {0,30,13}, {0,29,13}, {0,28,13}, {0,27,13}, {0,26,13}, {0,25,13}, {0,24,13}, {0,23,13}, {0,22,13}, {0,21,13}, {0,20,13}, {0,19,13}, {0,18,13}, {0,17,13}, {0,16,13}, {0,15,13}, {0,14,13}, {0,13,13}, {0,12,13}, {0,11,13}, {0,10,13}, {0,9,13}, {0,8,13}, {0,7,13}, {0,6,13}, {0,5,13}, {0,4,13}, {0,3,13}, {0,2,13}, {0,1,13}, {0,0,13},
- {0,0,14}, {0,1,14}, {0,2,14}, {0,3,14}, {0,4,14}, {0,5,14}, {0,6,14}, {0,7,14}, {0,8,14}, {0,9,14}, {0,10,14}, {0,11,14}, {0,12,14}, {0,13,14}, {0,14,14}, {0,15,14}, {0,16,14}, {0,17,14}, {0,18,14}, {0,19,14}, {0,20,14}, {0,21,14}, {0,22,14}, {0,23,14}, {0,24,14}, {0,25,14}, {0,26,14}, {0,27,14}, {0,28,14}, {0,29,14}, {0,30,14}, {0,31,14},
- {0,31,15}, {0,30,15}, {0,29,15}, {0,28,15}, {0,27,15}, {0,26,15}, {0,25,15}, {0,24,15}, {0,23,15}, {0,22,15}, {0,21,15}, {0,20,15}, {0,19,15}, {0,18,15}, {0,17,15}, {0,16,15}, {0,15,15}, {0,14,15}, {0,13,15}, {0,12,15}, {0,11,15}, {0,10,15}, {0,9,15}, {0,8,15}, {0,7,15}, {0,6,15}, {0,5,15}, {0,4,15}, {0,3,15}, {0,2,15}, {0,1,15}, {0,0,15},
- };
- public void setup() {
- size(32, 16, P3D);
- GraphicsEnvironment ge;
- GraphicsConfiguration[] gc;
- GraphicsDevice[] gd;
- int d, i, totalWidth, maxHeight, row, col, rowOffset;
- int[] x = new int[16], y = new int[16];
- float f, range, step, start;
- dh = new DisposeHandler(this); // Init DisposeHandler ASAP
- println("Available Syphon servers:");
- println(SyphonClient.listServers());
- port = new Serial(this, Serial.list()[0], 115200);
- client = new SyphonClient(this, "Desktop_to_syphon_2");
- background(0);
- dispBounds = new Rectangle[displays.length];
- if(useFullScreenCaps == true) {
- screenData = new int[displays.length][];
- // ledBounds[] not used
- } else {
- ledBounds = new Rectangle[leds.length];
- // screenData[][] not used
- }
- ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
- gd = ge.getScreenDevices();
- if(nDisplays > gd.length) nDisplays = gd.length;
- totalWidth = maxHeight = 0;
- for(d=0; d<nDisplays; d++) { // For each display...
- try {
- bot[d] = new Robot(gd[displays[d][0]]);
- }
- catch(AWTException e) {
- System.out.println("new Robot() failed");
- continue;
- }
- gc = gd[displays[d][0]].getConfigurations();
- dispBounds[d] = gc[0].getBounds();
- dispBounds[d].x = dispBounds[d].y = 0;
- totalWidth += displays[d][1];
- if(d > 0) totalWidth++;
- if(displays[d][2] > maxHeight) maxHeight = displays[d][2];
- }
- // Precompute locations of every pixel to read when downsampling.
- // Saves a bunch of math on each frame, at the expense of a chunk
- // of RAM. Number of samples is now fixed at 256; this allows for
- // some crazy optimizations in the downsampling code.
- for(i=0; i<leds.length; i++) { // For each LED...
- d = leds[i][0]; // Corresponding display index
- // Precompute columns, rows of each sampled point for this LED
- range = (float)dispBounds[d].width / (float)displays[d][1];
- step = range / 16.0;
- start = range * (float)leds[i][1] + step * 0.5;
- for(col=0; col<16; col++) x[col] = (int)(start + step * (float)col);
- range = (float)dispBounds[d].height / (float)displays[d][2];
- step = range / 16.0;
- start = range * (float)leds[i][2] + step * 0.5;
- for(row=0; row<16; row++) y[row] = (int)(start + step * (float)row);
- if(useFullScreenCaps == true) {
- // Get offset to each pixel within full screen capture
- for(row=0; row<16; row++) {
- for(col=0; col<16; col++) {
- pixelOffset[i][row * 16 + col] =
- y[row] * dispBounds[d].width + x[col];
- }
- }
- } else {
- // Calc min bounding rect for LED, get offset to each pixel within
- ledBounds[i] = new Rectangle(x[0], y[0], x[15]-x[0]+1, y[15]-y[0]+1);
- for(row=0; row<16; row++) {
- for(col=0; col<16; col++) {
- pixelOffset[i][row * 16 + col] =
- (y[row] - y[0]) * ledBounds[i].width + x[col] - x[0];
- }
- }
- }
- }
- for(i=0; i<prevColor.length; i++) {
- prevColor[i][0] = prevColor[i][1] = prevColor[i][2] =
- minBrightness / 3;
- }
- // Preview window shows all screens side-by-side
- // A special header / magic word is expected by the corresponding LED
- // streaming code running on the Arduino. This only needs to be initialized
- // once (not in draw() loop) because the number of LEDs remains constant:
- serialData[0] = 'A'; // Magic word
- serialData[1] = 'd';
- serialData[2] = 'a';
- serialData[3] = (byte)((leds.length - 1) >> 8); // LED count high byte
- serialData[4] = (byte)((leds.length - 1) & 0xff); // LED count low byte
- serialData[5] = (byte)(serialData[3] ^ serialData[4] ^ 0x55); // Checksum
- // Pre-compute gamma correction table for LED brightness levels:
- for(i=0; i<256; i++) {
- f = pow((float)i / 255.0, 2.8);
- gamma[i][0] = (byte)(f * 255.0);
- gamma[i][1] = (byte)(f * 240.0);
- gamma[i][2] = (byte)(f * 220.0);
- }
- }
- void keyPressed() {
- if (key == ' ') {
- client.stop();
- } else if (key == 'd') {
- println(client.getServerName());
- }
- }
- // Open and return serial connection to Arduino running LEDstream code. This
- // attempts to open and read from each serial device on the system, until the
- // matching "Ada\n" acknowledgement string is found. Due to the serial
- // timeout, if you have multiple serial devices/ports and the Arduino is late
- // in the list, this can take seemingly forever...so if you KNOW the Arduino
- // will always be on a specific port (e.g. "COM6"), you might want to comment
- // out most of this to bypass the checks and instead just open that port
- // directly! (Modify last line in this method with the serial port name.)
- Serial openPort() {
- String[] ports;
- String ack;
- int i, start;
- Serial s;
- ports = Serial.list(); // List of all serial ports/devices on system.
- for(i=0; i<ports.length; i++) { // For each serial port...
- System.out.format("Trying serial port %s\n",ports[i]);
- try {
- s = new Serial(this, ports[i], 115200);
- }
- catch(Exception e) {
- // Can't open port, probably in use by other software.
- continue;
- }
- // Port open...watch for acknowledgement string...
- start = millis();
- while((millis() - start) < timeout) {
- if((s.available() >= 4) &&
- ((ack = s.readString()) != null) &&
- ack.contains("Ada\n")) {
- return s; // Got it!
- }
- }
- // Connection timed out. Close port and move on to the next.
- s.stop();
- }
- // Didn't locate a device returning the acknowledgment string.
- // Maybe it's out there but running the old LEDstream code, which
- // didn't have the ACK. Can't say for sure, so we'll take our
- // changes with the first/only serial device out there...
- return new Serial(this, ports[0], 115200);
- }
- // PER_FRAME PROCESSING ------------------------------------------------------
- void draw () {
- int d, i, j, o, c, weight, rb, g, sum, deficit, s2;
- int[] pxls, offs;
- if (client.available()) {
- // The first time getImage() is called with
- // a null argument, it will initialize the PImage
- // object with the correct size.
- S_img = client.getGraphics(S_img); // load the pixels array with the updated image info (slow)
- //S_img = client.getImage(S_img, false); // does not load the pixels array (faster)
- image(S_img, 0, 0, width, height);
- }
- weight = 257 - fade; // 'Weighting factor' for new frame vs. old
- j = 6; // Serial led data follows header / magic word
- // This computes a single pixel value filtered down from a rectangular
- // section of the screen. While it would seem tempting to use the native
- // image scaling in Processing/Java, in practice this didn't look very
- // good -- either too pixelated or too blurry, no happy medium. So
- // instead, a "manual" downsampling is done here. In the interest of
- // speed, it doesn't actually sample every pixel within a block, just
- // a selection of 256 pixels spaced within the block...the results still
- // look reasonably smooth and are handled quickly enough for video.
- loadPixels();
- for(i=0; i<leds.length; i++) { // For each LED...
- color ipix=pixels[leds[i][1]+(leds[i][2]*32)];
- if (((i>63)&&(i<192))||((i>255)&&(i<320))){
- // Blend new pixel value with the value from the prior frame
- ledColor[i][1] = (short)((((ipix >> 16) & 0xff) * weight +
- prevColor[i][0] * fade) >> 8);
- ledColor[i][2] = (short)(((( ipix >> 8) & 0xff) * weight +
- prevColor[i][1] * fade) >> 8);
- ledColor[i][0] = (short)((((ipix) & 0xff) * weight +
- prevColor[i][2] * fade) >> 8);
- } else {
- ledColor[i][1] = (short)((((ipix >> 16) & 0xff) * weight +
- prevColor[i][0] * fade) >> 8);
- ledColor[i][0] = (short)(((( ipix >> 8) & 0xff) * weight +
- prevColor[i][1] * fade) >> 8);
- ledColor[i][2] = (short)((((ipix) & 0xff) * weight +
- prevColor[i][2] * fade) >> 8);
- }
- // Boost pixels that fall below the minimum brightness
- sum = ledColor[i][0] + ledColor[i][1] + ledColor[i][2];
- if(sum < minBrightness) {
- if(sum == 0) { // To avoid divide-by-zero
- deficit = minBrightness / 3; // Spread equally to R,G,B
- ledColor[i][0] += deficit;
- ledColor[i][1] += deficit;
- ledColor[i][2] += deficit;
- } else {
- deficit = minBrightness - sum;
- s2 = sum * 2;
- // Spread the "brightness deficit" back into R,G,B in proportion to
- // their individual contribition to that deficit. Rather than simply
- // boosting all pixels at the low end, this allows deep (but saturated)
- // colors to stay saturated...they don't "pink out."
- ledColor[i][0] += deficit * (sum - ledColor[i][0]) / s2;
- ledColor[i][1] += deficit * (sum - ledColor[i][1]) / s2;
- ledColor[i][2] += deficit * (sum - ledColor[i][2]) / s2;
- }
- }
- // Apply gamma curve and place in serial output buffer
- serialData[j++] = gamma[ledColor[i][0]][0];
- serialData[j++] = gamma[ledColor[i][1]][1];
- serialData[j++] = gamma[ledColor[i][2]][2];
- // Update pixels in preview image
- // preview[d].pixels[leds[i][2] * displays[d][1] + leds[i][1]] =
- // (ledColor[i][0] << 16) | (ledColor[i][1] << 8) | ledColor[i][2];
- }
- if(port != null) port.write(serialData); // Issue data to Arduino
- // Show live preview image(s)
- //scale(pixelSize);
- // for(i=d=0; d<nDisplays; d++) {
- // preview[d].updatePixels();
- // image(preview[d], i, 0);
- // i += displays[d][1] + 1;
- // }
- //
- //println(frameRate); // How are we doing?
- // Copy LED color data to prior frame array for next pass
- arraycopy(ledColor, 0, prevColor, 0, ledColor.length);
- }
- // CLEANUP -------------------------------------------------------------------
- // The DisposeHandler is called on program exit (but before the Serial library
- // is shutdown), in order to turn off the LEDs (reportedly more reliable than
- // stop()). Seems to work for the window close box and escape key exit, but
- // not the 'Quit' menu option. Thanks to phi.lho in the Processing forums.
- public class DisposeHandler {
- DisposeHandler(PApplet pa) {
- pa.registerDispose(this);
- }
- public void dispose() {
- // Fill serialData (after header) with 0's, and issue to Arduino...
- // Arrays.fill(serialData, 6, serialData.length, (byte)0);
- java.util.Arrays.fill(serialData, 6, serialData.length, (byte)0);
- if(port != null) port.write(serialData);
- }
- }
I can also attach the Arduino sketch if people wanted to see it. The MAX patch is here:
and the app:
And Syphon is available for download from the link at the top of the post.
1