Hej alle:
Den nuværende konfiguration af Halfmachine's LED klodser er en stack på 3 x 4, forbundet i kolonner - det nye daisy chain system virker åbenbart fint!
http://www.flickr.com/photos/25649704@N06/2459834777/Jeg har snakket med Jabstarr og Nicolas@HM om muligheden for at vise video på LED stacken ved at bruge Arduino som buffer/serial interface, med Processing til at koge video ned til noget der bruges. Her følger en stump rodet Processing kode der indeholder en primitiv simuleret visning af hvordan det kunne se ud, med en Floyd-Steinberg skravering der sætter lidt liv i pixelsne og tillader gråtoner i nogen forstand (bliver bedre med frameraten!). Kodens setup er lidt ambitiøst sat op til at køre med 4x6 klodser, men det er let at ændre disse parametre.
/* Video To Arduino To Half Machine LED bricks */
/* Hack and slash crap code by Sonny Windstrup */
/* For 8-bit Klubben and M/S Half Machine */
import processing.opengl.*;
import processing.serial.*;
import processing.video.*;
Capture video;
DiodeStack stack;
int scrWidth = screen.width;
int scrHeight = screen.height;
int pixWidth;
int pixHeight;
int pixRotSize;
int pixRows;
int pixCols;
float greenRot;
int numPixels;
int[] scanOrder;
int[][] targetPixMap;
int[][] actualPixMap;
void TransmitPixels()
{
// pseudocode: serial transmit frame sync pulse (0xff)
for (int i=0;i<numPixels;i++)
{
int coord = scanOrder[i]; // reads pixel coordinate from scan order table
int px = coord & 0xff;e
int py = (coord >> 8) & 0xff;
int value = actualPixMap[px][py];
int numeric = (int) map(value,0,256,0,3);
// should produce 0,1,2 (0 = black, 1 = 50%luma (red), 2 = 100% luma (red+green)
if (numeric == 2) numeric = 3; // bit pattern patch
// pseudocode: transmit numeric as byte (bit 0 : red; bit 1: green)
}
}
public void draw()
{
GrabPixels();
DitherPixels();
PaintPixels(); // omit in case of speed problems ?
TransmitPixels();
}
void setup()
{
println("SETUP");
size(scrWidth, scrHeight, OPENGL);
stack = new DiodeStack(4,6);
scanOrder = stack.GetCoordinates();
pixCols = stack._w;
pixRows = stack._h;
numPixels = stack.GetNumPixels();
targetPixMap = new int[pixCols][pixRows];
actualPixMap = new int[pixCols][pixRows];
greenRot = radians(45);
int pixSize = Math.min( (int) (scrWidth/pixCols), (int) (scrHeight/pixRows));
pixWidth = pixSize-1;
pixHeight = pixSize-1;
pixRotSize = pixSize * 2 / 3;
println(pixCols + " x " + pixRows );
println( (pixWidth * pixCols ) + " x " + (pixHeight * pixRows));
video = new Capture(this,pixCols,pixRows,30);
// video = new Capture(this,320,240,30);
println(numPixels + " video pixels defined.");
}
int LumaCurve(int source)
{
return (int) map(source, 15,240,0,255);
}
void GrabPixels()
{
if (video.available())
{
video.read();
video.loadPixels();
for (int y=0;y<pixRows;y++)
{
for (int x=0;x<pixCols;x++)
{
int coord = y*pixCols + x;
int videoPixel = video.pixels[coord];
int r = (videoPixel >> 16) & 0xff;
int g = (videoPixel >> 8) & 0xff;
int b = (videoPixel) & 0xff;
int luma = ((g * 59 + b * 11 + r * 30) / 100) & 0xff;
targetPixMap[x][y] = LumaCurve(luma);
}
}
}
}
int FindNearestColor(int source)
{
return (int) Math.floor(source * 3 / 256)*127;
}
void DitherPixels()
{
// floyd-steinberg
for (int y=0;y<pixRows-1;y++)
{
for(int x=1;x<pixCols-1;x++)
{
int coord = y*pixCols + x;
int oldpixel = targetPixMap[x][y];
int newpixel = FindNearestColor(oldpixel);
int quant_error = oldpixel - newpixel;
actualPixMap[x][y] = newpixel;
targetPixMap[x+1][y] += 7 * quant_error / 16;
targetPixMap[x-1][y+1] += 3 * quant_error / 16;
targetPixMap[x][y+1] += 5 * quant_error / 16;
targetPixMap[x+1][y+1] += 1 * quant_error / 16;
}
}
}
void PaintPixels()
{
background(0);
noStroke();
rectMode(CENTER);
translate(pixWidth/2,pixHeight/2,0);
for (int y=0;y<pixRows;y++)
{
for (int x=0;x<pixCols;x++)
{
int pixR = 0x00000000;
int pixG = 0x00000000;
int pixVal = actualPixMap[x][y];
if (pixVal > 84) pixR = 0xffff0000;
if (pixVal > 191) pixG = 0xff00ff00;
pushMatrix();
translate(x*pixWidth,y*pixHeight,0);
fill(pixR);
rect(0,0,pixWidth-2,pixHeight-2);
pushMatrix();
rotateZ(greenRot);
fill(pixG);
rect(0,0,pixRotSize-1,pixRotSize-1);
popMatrix();
popMatrix();
}
}
}
// diodebox classes - used for relatively opague modeling of scan pixel order
class DiodePixel
{
int _px;
int _py;
DiodePixel(int px, int py)
{
this._px = px;
this._py = py;
}
int GetCoordinate()
{
return (int) (this._py << 8) + this._px;
}
}
class DiodeQuad
{
int _px;
int _py;
DiodePixel[] _pixels;
DiodeQuad(int px, int py)
{
this._px = px;
this._py = py;
this._pixels = new DiodePixel[4];
for (int y=0;y<2;y++)
{
for (int x=0;x<2;x++)
{
int n = y*2+x;
this._pixels[n] = new DiodePixel(px+x,py+y);
}
}
}
int[] GetCoordinates()
{
int[] out = new int[4];
for (int i=0;i<4;i++)
{
out[i] = this._pixels[i].GetCoordinate();
}
return out;
}
}
class DiodeModule
{
int _px;
int _py;
DiodeQuad[] _quads;
DiodeModule(int px, int py)
{
this._px = px;
this._py = py;
this._quads = new DiodeQuad[8];
for (int y=0;y<4;y++)
{
for (int x=0;x<2;x++)
{
int n = y*2+x;
this._quads[n] = new DiodeQuad(px+x*2,py+y*2);
}
}
}
int[] GetCoordinates()
{
int[] out = new int[32];
for (int i=0;i<8;i++)
{
int[] tmp = this._quads[i].GetCoordinates();
for (int j=0;j<4;j++)
{
out[i*4+j] = tmp[j];
}
}
return out;
}
}
class DiodeBox
{
int _px;
int _py;
DiodeModule[] _modules;
DiodeBox(int px, int py)
{
this._px = px;
this._py = py;
this._modules = new DiodeModule[4];
for (int i=0;i<4;i++)
{
this._modules[i] = new DiodeModule(px+4*i,py);
}
}
int[] GetCoordinates()
{
int[] out = new int[128];
for (int i=0;i<4;i++)
{
int[] tmp = this._modules[i].GetCoordinates();
for (int j=0;j<32;j++)
{
out[i*32+j] = tmp[j];
}
}
return out;
}
}
class DiodeStack
{
int _w;
int _h;
int _numX;
int _numY;
int _numBoxes;
DiodeBox[] _boxes;
DiodeStack(int numX, int numY)
{
this._w = numX * 16;
this._h = numY * 8;
this._numX = numX;
this._numY = numY;
this._numBoxes = numX * numY;
this._boxes = new DiodeBox[this._numBoxes];
for (int y=0;y<_numY;y++)
{
for (int x=0;x<numX;x++)
{
// int n = y*numX + x; // stack linked by rows
int n = x * numY + y; // stack linked by columns
this._boxes[n] = new DiodeBox(x*16,y*8);
}
}
}
int[] GetCoordinates()
{
int[] out = new int[this.GetNumPixels()];
for (int i=0;i<this._numBoxes;i++)
{
int[] tmp = this._boxes[i].GetCoordinates();
for (int j=0;j<128;j++)
{
out[i*128+j] = tmp[j];
}
}
return out;
}
int GetNumBoxes()
{
return this._numBoxes;
}
int GetNumPixels()
{
return this._w * this._h;
}
DiodeBox GetBox(int i)
{
if ((i>=0) && (i<this._numBoxes))
{
return _boxes[i];
}
else
{
throw new RuntimeException("Invalid box reference");
}
}
}