Skip to content

Commit

Permalink
Add 16-bit colour support to LXOutput.
Browse files Browse the repository at this point in the history
  • Loading branch information
zestyping committed Apr 28, 2018
1 parent 5b59044 commit 1975603
Show file tree
Hide file tree
Showing 10 changed files with 373 additions and 121 deletions.
2 changes: 1 addition & 1 deletion src/heronarts/lx/color/LXColor16.java
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ public static long hsb(float h, float s, float b) {
* @param brightness Brightness from 0.0 - 1.0
* @return RGB color value
*/
private static long scaledHsbToRgb(float hue, float saturation, float brightness) {
public static long scaledHsbToRgb(float hue, float saturation, float brightness) {
int r = 0, g = 0, b = 0;
if (saturation == 0) {
r = g = b = (int) (brightness*65535.0f + 0.5f);
Expand Down
44 changes: 44 additions & 0 deletions src/heronarts/lx/output/LXDatagram.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

package heronarts.lx.output;

import heronarts.lx.color.LXColor16;
import heronarts.lx.parameter.BooleanParameter;

import java.net.DatagramPacket;
Expand Down Expand Up @@ -146,6 +147,35 @@ protected LXDatagram copyPoints(int[] colors, int[] pointIndices, int offset) {
return this;
}

/**
* Helper for subclasses to copy 16-bit colors into the data buffer at a
* specified offset. For many subclasses which wrap RGB buffers, onSend16() will
* be a simple call to this method with the right parameters.
*
* @param colors16 Array of color values
* @param pointIndices Array of point indices
* @param offset Offset in buffer to write
* @return this
*/
protected LXDatagram copyPoints16(long[] colors16, int[] pointIndices, int offset) {
int i = offset;
int[] byteOffset = BYTE_ORDERING[this.byteOrder.ordinal()];
for (int index : pointIndices) {
long c = (index >= 0) ? colors16[index] : 0;
int red = LXColor16.red(c);
int green = LXColor16.green(c);
int blue = LXColor16.blue(c);
this.buffer[i + byteOffset[0]] = (byte) (red >>> 8);
this.buffer[i + byteOffset[0] + 1] = (byte) (red & 0xff);
this.buffer[i + byteOffset[1]] = (byte) (green >>> 8);
this.buffer[i + byteOffset[1] + 1] = (byte) (green & 0xff);
this.buffer[i + byteOffset[2]] = (byte) (blue >>> 8);
this.buffer[i + byteOffset[2] + 1] = (byte) (blue & 0xff);
i += 6;
}
return this;
}

/**
* Invoked by engine to send this packet when new color data is available. The
* LXDatagram should update the packet object accordingly to contain the
Expand All @@ -154,4 +184,18 @@ protected LXDatagram copyPoints(int[] colors, int[] pointIndices, int offset) {
* @param colors Color buffer
*/
public abstract void onSend(int[] colors);

/**
* Invoked by engine to send this packet when new 16-bit color data is available.
* The default implementation just converts to 8-bit color; subclasses that can
* handle 16-bit colors should implement this method to update the packet object
* with the appropriate buffer and populate the buffer.
*
* @param colors16 Color buffer
*/
public void onSend16(long[] colors16) {
int[] colors = new int[colors16.length];
LXColor16.longsToInts(colors16, colors);
onSend(colors);
}
}
86 changes: 57 additions & 29 deletions src/heronarts/lx/output/LXDatagramOutput.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,55 +73,83 @@ public LXDatagramOutput addDatagrams(LXDatagram[] datagrams) {

/**
* Subclasses may override. Invoked before datagrams are sent.
*
* @param colors Color values
*/
protected /* abstract */ void beforeSend(int[] colors) {}

/**
* Subclasses may override. Invoked after datagrams are sent.
*
* @param colors Color values
*/
protected /* abstract */ void afterSend(int[] colors) {}

/**
* Core method which sends the datagrams.
* Subclasses may override. Invoked before datagrams are sent.
* @param colors16 Color values
*/
protected /* abstract */ void beforeSend16(long[] colors16) {}

/**
* Subclasses may override. Invoked after datagrams are sent.
* @param colors16 Color values
*/
protected /* abstract */ void afterSend16(long[] colors16) {}

/** Populates the datagrams with 8-bit color data and sends them out. */
@Override
protected final void onSend(int[] colors) {
protected void onSend(int[] colors) {
long now = System.currentTimeMillis();
beforeSend(colors);
for (LXDatagram datagram : this.datagrams) {
if (datagram.enabled.isOn() && (now > datagram.destination.sendAfter)) {
datagram.onSend(colors);
try {
this.socket.send(datagram.packet);
if (datagram.destination.failureCount > 0) {
System.out.println(this.date.format(now) + " Recovered connectivity to " + datagram.packet.getAddress());
}
datagram.destination.error.setValue(false);
datagram.destination.failureCount = 0;
datagram.destination.sendAfter = 0;
} catch (IOException iox) {
if (datagram.destination.failureCount == 0) {
System.out.println(this.date.format(now) + " IOException sending to "
+ datagram.packet.getAddress() + " (" + iox.getLocalizedMessage()
+ "), will initiate backoff after 3 consecutive failures");
}
++datagram.destination.failureCount;
if (datagram.destination.failureCount >= 3) {
int pow = Math.min(5, datagram.destination.failureCount - 3);
long waitFor = (long) (50 * Math.pow(2, pow));
System.out.println(this.date.format(now) + " Retrying " + datagram.packet.getAddress()
+ " in " + waitFor + "ms" + " (" + datagram.destination.failureCount
+ " consecutive failures)");
datagram.destination.sendAfter = now + waitFor;
datagram.destination.error.setValue(true);
}
}
transmit(datagram);
}
}
afterSend(colors);
}

/** Populates the datagrams with 16-bit color data and sends them out. */
@Override
protected void onSend16(long[] colors16) {
long now = System.currentTimeMillis();
beforeSend16(colors16);
for (LXDatagram datagram : this.datagrams) {
if (datagram.enabled.isOn() && (now > datagram.destination.sendAfter)) {
datagram.onSend16(colors16);
transmit(datagram);
}
}
afterSend16(colors16);
}

/** Attempts to transmit a datagram, while keeping track of failed attempts. */
protected void transmit(LXDatagram datagram) {
long now = System.currentTimeMillis();
try {
this.socket.send(datagram.packet);
if (datagram.destination.failureCount > 0) {
System.out.println(this.date.format(now) + " Recovered connectivity to " + datagram.packet.getAddress());
}
datagram.destination.error.setValue(false);
datagram.destination.failureCount = 0;
datagram.destination.sendAfter = 0;
} catch (IOException iox) {
if (datagram.destination.failureCount == 0) {
System.out.println(this.date.format(now) + " IOException sending to "
+ datagram.packet.getAddress() + " (" + iox.getLocalizedMessage()
+ "), will initiate backoff after 3 consecutive failures");
}
++datagram.destination.failureCount;
if (datagram.destination.failureCount >= 3) {
int pow = Math.min(5, datagram.destination.failureCount - 3);
long waitFor = (long) (50 * Math.pow(2, pow));
System.out.println(this.date.format(now) + " Retrying " + datagram.packet.getAddress()
+ " in " + waitFor + "ms" + " (" + datagram.destination.failureCount
+ " consecutive failures)");
datagram.destination.sendAfter = now + waitFor;
datagram.destination.error.setValue(true);
}
}
}
}
151 changes: 90 additions & 61 deletions src/heronarts/lx/output/LXOutput.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@

import heronarts.lx.LX;
import heronarts.lx.LXComponent;
import heronarts.lx.HybridBuffer;
import heronarts.lx.color.LXColor;
import heronarts.lx.color.LXColor16;
import heronarts.lx.model.LXFixture;
import heronarts.lx.model.LXPoint;
import heronarts.lx.parameter.BoundedParameter;
Expand All @@ -32,6 +34,7 @@

import java.awt.Color;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
Expand All @@ -56,7 +59,7 @@ static int[] fixtureToIndices(LXFixture fixture) {
/**
* Buffer with colors for this output, gamma-corrected
*/
private final int[] outputColors;
private final HybridBuffer outputBuffer;

/**
* Local array for color-conversions
Expand Down Expand Up @@ -110,23 +113,13 @@ public enum Mode {
*/
private long lastFrameMillis = 0;

private final int[] allWhite;

private final int[] allOff;

protected LXOutput(LX lx) {
this(lx, "Output");
}

protected LXOutput(LX lx, String label) {
super(lx, label);
this.outputColors = new int[lx.total];
this.allWhite = new int[lx.total];
this.allOff = new int[lx.total];
for (int i = 0; i < lx.total; ++i) {
this.allWhite[i] = LXColor.WHITE;
this.allOff[i] = LXColor.BLACK;
}
this.outputBuffer = new HybridBuffer(lx);
addParameter("enabled", this.enabled);
addParameter("mode", this.mode);
addParameter("fps", this.framesPerSecond);
Expand Down Expand Up @@ -164,70 +157,106 @@ public LXOutput removeChild(LXOutput child) {
* @return this
*/
public LXOutput send(int[] colors) {
if (!this.enabled.isOn()) {
return this;
}
long now = System.currentTimeMillis();
double fps = this.framesPerSecond.getValue();
if ((fps == 0) || ((now - this.lastFrameMillis) > (1000. / fps))) {
int[] colorsToSend;
double fps = framesPerSecond.getValue();
if (enabled.isOn() && (fps == 0 || now > lastFrameMillis + 1000/fps)) {
int[] colorsToSend = processOutput(colors);
onSend(colorsToSend);
for (LXOutput child : children) {
child.send(colorsToSend);
}
lastFrameMillis = now;
}
return this;
}

switch (this.mode.getEnum()) {
protected int[] processOutput(int[] colors) {
int[] out = outputBuffer.getArray();
switch (mode.getEnum()) {
case WHITE:
int white = LXColor.hsb(0, 0, 100 * this.brightness.getValuef());
for (int i = 0; i < this.allWhite.length; ++i) {
this.allWhite[i] = white;
}
colorsToSend = this.allWhite;
break;

Arrays.fill(out, LXColor.gray(100*brightness.getValue()));
return out;
case OFF:
colorsToSend = this.allOff;
break;

case RAW:
colorsToSend = colors;
break;

default:
Arrays.fill(out, 0);
return out;
case NORMAL:
colorsToSend = colors;
int gamma = this.gammaCorrection.getValuei();
double brt = this.brightness.getValuef();
int gamma = gammaCorrection.getValuei();
float brt = brightness.getValuef();
if (gamma > 0 || brt < 1) {
int r, g, b, rgb;
for (int i = 0; i < colorsToSend.length; ++i) {
rgb = colorsToSend[i];
r = (rgb >> 16) & 0xff;
g = (rgb >> 8) & 0xff;
b = rgb & 0xff;
Color.RGBtoHSB(r, g, b, this.hsb);
float scaleBrightness = this.hsb[2];
for (int x = 0; x < gamma; ++x) {
scaleBrightness *= this.hsb[2];
for (int i = 0; i < colors.length; ++i) {
LXColor.RGBtoHSB(colors[i], hsb);
float newBrightness = brt * hsb[2];
for (int x = 0; x < gamma; x++) {
newBrightness *= hsb[2];
}
scaleBrightness *= brt;
this.outputColors[i] = Color.HSBtoRGB(hsb[0], hsb[1], scaleBrightness);
out[i] = Color.HSBtoRGB(hsb[0], hsb[1], newBrightness);
}
colorsToSend = this.outputColors;
return out;
}
break;
}

this.onSend(colorsToSend);
}
return colors;
}

for (LXOutput child : this.children) {
child.send(colorsToSend);
/**
* Sends data to this output, after applying throttle and color correction
*
* @param colors16 Array of color values
* @return this
*/
public LXOutput send16(long[] colors16) {
long now = System.currentTimeMillis();
double fps = framesPerSecond.getValue();
if (enabled.isOn() && (fps == 0 || now > lastFrameMillis + 1000/fps)) {
long[] colorsToSend16 = processOutput16(colors16);
this.onSend16(colorsToSend16);
for (LXOutput child : children) {
child.send16(colorsToSend16);
}
this.lastFrameMillis = now;
lastFrameMillis = now;
}
return this;
}

protected long[] processOutput16(long[] colors16) {
long[] out = outputBuffer.getArray16();
switch (mode.getEnum()) {
case WHITE:
Arrays.fill(out, LXColor.gray(100*brightness.getValue()));
return out;
case OFF:
Arrays.fill(out, 0);
return out;
case NORMAL:
int gamma = gammaCorrection.getValuei();
float brt = brightness.getValuef();
if (gamma > 0 || brt < 1) {
for (int i = 0; i < colors16.length; ++i) {
LXColor16.RGBtoHSB(colors16[i], hsb);
float newBrightness = brightness.getValuef()*hsb[2];
for (int x = 0; x < gamma; x++) {
newBrightness *= hsb[2];
}
out[i] = LXColor16.scaledHsbToRgb(hsb[0], hsb[1], newBrightness);
}
return out;
}
}
return colors16;
}

/**
* Subclasses implement this to send the data.
*
* @param colors Color values
* Subclasses implement this to send 8-bit color data.
* @param colors 8-bit color values
*/
protected abstract void onSend(int[] colors);
}

/**
* Subclasses implement this to send 16-bit color data.
* @param colors16 16-bit color values
*/
protected void onSend16(long[] colors16) {
int[] colors = new int[colors16.length];
LXColor16.longsToInts(colors16, colors);
onSend(colors);
}
}
Loading

0 comments on commit 1975603

Please sign in to comment.