173 lines
6.7 KiB
Plaintext
173 lines
6.7 KiB
Plaintext
// "Adalight" is a do-it-yourself facsimile of the Philips Ambilight concept
|
|
// for desktop computers and home theater PCs. This is the host PC-side code
|
|
// written in Processing; intended for use with a USB-connected Arduino
|
|
// microcontroller running the accompanying LED streaming code. Requires one
|
|
// strand of Digital RGB LED Pixels (Adafruit product ID #322, specifically
|
|
// the newer WS2801-based type, strand of 25) and a 5 Volt power supply (such
|
|
// as Adafruit #276). You may need to adapt the code and the hardware
|
|
// arrangement for your specific display configuration.
|
|
// Screen capture adapted from code by Cedrik Kiefer (processing.org forum)
|
|
|
|
import java.awt.*;
|
|
import java.awt.image.*;
|
|
import processing.serial.*;
|
|
|
|
// This array contains the 2D image coordinates corresponding to each pixel
|
|
// in the LED strand, which forms a ring around the perimeter of the screen
|
|
// (with a one pixel gap at the bottom to accommodate the monitor stand).
|
|
|
|
static final int coord[][] = new int[][] {
|
|
{3,5}, {2,5}, {1,5}, {0,5}, // Bottom edge, left half
|
|
{0,4}, {0,3}, {0,2}, {0,1}, // Left edge
|
|
{0,0}, {1,0}, {2,0}, {3,0}, {4,0}, {5,0}, {6,0}, {7,0}, {8,0}, // Top edge
|
|
{8,1}, {8,2}, {8,3}, {8,4}, // Right edge
|
|
{8,5}, {7,5}, {6,5}, {5,5} // Bottom edge, right half
|
|
};
|
|
|
|
static final int arrayWidth = 9, // Width of Adalight array, in LED pixels
|
|
arrayHeight = 6, // Height of Adalight array, in LED pixels
|
|
imgScale = 20, // Size of displayed preview
|
|
samples = 20, // Samples (per axis) when down-scaling
|
|
s2 = samples * samples;
|
|
|
|
byte[] buffer = new byte[6 + coord.length * 3];
|
|
byte[][] gamma = new byte[256][3];
|
|
GraphicsDevice[] gs;
|
|
PImage preview = createImage(arrayWidth, arrayHeight, RGB);
|
|
Rectangle bounds;
|
|
Serial port;
|
|
DisposeHandler dh; // For disabling LEDs on exit
|
|
|
|
void setup() {
|
|
GraphicsEnvironment ge;
|
|
DisplayMode mode;
|
|
int i;
|
|
float f;
|
|
|
|
dh = new DisposeHandler(this);
|
|
port = new Serial(this, Serial.list()[0], 115200);
|
|
|
|
size(arrayWidth * imgScale, arrayHeight * imgScale, JAVA2D);
|
|
|
|
// Initialize capture code for full screen dimensions:
|
|
ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
|
|
gs = ge.getScreenDevices();
|
|
mode = gs[0].getDisplayMode();
|
|
bounds = new Rectangle(0, 0, screen.width, screen.height);
|
|
|
|
// 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:
|
|
buffer[0] = 'A'; // Magic word
|
|
buffer[1] = 'd';
|
|
buffer[2] = 'a';
|
|
buffer[3] = byte((coord.length - 1) >> 8); // LED count high byte
|
|
buffer[4] = byte((coord.length - 1) & 0xff); // LED count low byte
|
|
buffer[5] = byte(buffer[3] ^ buffer[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 draw () {
|
|
BufferedImage desktop;
|
|
PImage screenShot;
|
|
int i, j, c;
|
|
|
|
// Capture screen
|
|
try {
|
|
desktop = new Robot(gs[0]).createScreenCapture(bounds);
|
|
}
|
|
catch(AWTException e) {
|
|
System.err.println("Screen capture failed.");
|
|
return;
|
|
}
|
|
screenShot = new PImage(desktop); // Convert Image to PImage
|
|
screenShot.loadPixels(); // Make pixel array readable
|
|
|
|
// Downsample blocks of interest into LED output buffer:
|
|
preview.loadPixels(); // Also display in preview image
|
|
j = 6; // Data follows LED header / magic word
|
|
for(i = 0; i < coord.length; i++) { // For each LED...
|
|
c = block(screenShot, coord[i][0], coord[i][1]);
|
|
buffer[j++] = gamma[(c >> 16) & 0xff][0];
|
|
buffer[j++] = gamma[(c >> 8) & 0xff][1];
|
|
buffer[j++] = gamma[ c & 0xff][2];
|
|
preview.pixels[coord[i][1] * arrayWidth + coord[i][0]] = c;
|
|
}
|
|
preview.updatePixels();
|
|
|
|
// Show preview image
|
|
scale(imgScale);
|
|
image(preview,0,0);
|
|
println(frameRate);
|
|
|
|
port.write(buffer);
|
|
}
|
|
|
|
// This method 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, in practice this didn't look very good -- the
|
|
// extreme downsampling, coupled with the native interpolation mode, results
|
|
// in excessive scintillation with video content. An alternate approach
|
|
// using the Java AWT AreaAveragingScaleFilter filter produces wonderfully
|
|
// smooth results, but is too slow for filtering full-screen video. So
|
|
// instead, a "manual" downsampling method is used here. In the interest of
|
|
// speed, it doesn't actually sample every pixel within a block, just a 20x20
|
|
// grid...the results still look reasonably smooth and are handled quickly
|
|
// enough for video. Scaling the full screen image also wastes a lot of
|
|
// cycles on center pixels that are never used for the LED output; this
|
|
// method gets called only for perimeter pixels. Even then, you may want to
|
|
// set your monitor for a lower resolution before running this sketch.
|
|
|
|
color block(PImage image, int x, int y) {
|
|
int c, r, g, b, row, col, rowOffset;
|
|
float startX, curX, curY, incX, incY;
|
|
|
|
startX = float(screen.width / arrayWidth ) *
|
|
(float(x) + (0.5 / float(samples)));
|
|
curY = float(screen.height / arrayHeight) *
|
|
(float(y) + (0.5 / float(samples)));
|
|
incX = float(screen.width / arrayWidth ) / float(samples);
|
|
incY = float(screen.height / arrayHeight) / float(samples);
|
|
|
|
r = g = b = 0;
|
|
for(row = 0; row < samples; row++) {
|
|
rowOffset = int(curY) * screen.width;
|
|
curX = startX;
|
|
for(col = 0; col < samples; col++) {
|
|
c = image.pixels[rowOffset + int(curX)];
|
|
r += (c >> 16) & 0xff;
|
|
g += (c >> 8) & 0xff;
|
|
b += c & 0xff;
|
|
curX += incX;
|
|
}
|
|
curY += incY;
|
|
}
|
|
|
|
return color(r / s2, g / s2, b / s2);
|
|
}
|
|
|
|
// 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 buffer (after header) with 0's, and issue to Arduino...
|
|
Arrays.fill(buffer, 6, buffer.length, byte(0));
|
|
port.write(buffer);
|
|
}
|
|
}
|
|
|