Ambilight is basically the background light effect projected from some RGB LEDs mounted on the back of the TV or monitor screen, which automatically follows the color and brightness of the video content on the TV screen in real-time. This can create some very impressive visual effects and can also use for eye-easing purposes. The ambilight concept was developed and introduced by Phillips. There are ambilight TVs or ambilight systems commercially available but they tend to be very expensive.
Before I had this idea, I found this post about the possibility of building something similar using Arduino and Processing, and I decided to make one for my 24 inch TV. The goal of this ambilight system should be cheap, easy to modify, no “pass-through” device and useful to different sizes of TV or monitors. It provides a ambient light around the flat panel TV with the colors reflecting the color captured on screen. I name this project “Ozilight” just for fun. It might not look as good as the picture on the top of this post, but it will certainly make your TV or monitor screen look much cooler! :-D
Here is the result
I will be using these hardware:
- A computer
- Arduino Uno, Nano and Mega will also work (Acutally any microcontroller that supports SPI)
- Digital WS2811 SMD RGB LED strips
- DC power supply for the LEDs (5V 2A in my case, rating depends on how many LEDs you want to drive)
Be careful when you are buying the RGB LED strips, remember it is better to buy “Digital” RGB Strips for example the WS2811 or WS2801. Some cheap RGB LED strips cannot change color individual on each LED but all together, so it’s not something we want in this project. You can also buy single RGB LEDs and wire them to your circuit, but the cables would be a nightmare, try to imagine there are 4 cables come out of each LED, and resistors, etc. The Arduino Uno can only handle 4 RGB LED at most using PWM anyway (by itself and without any other external devices). So if you are trying to build everything from scratch, it will require a lot more time and effort.
I am going to be using WS2811 LED strips which all the LEDs can be controlled by Arduino with only one wire connection (excluding Vcc and GND).
I divided this project into these tasks.
- Learn how to use Processing to take Screenshot and analyse the colours (Part 1)
- Analyse multiple desired areas around the edges of the screen (Part 1)
- Build the RGB LED strip (Part 1)
- Learn how to drive it with Arduino (Part 2)
- Design the communication protocol between Arduino and Processing application (Part 2)
- Finally, combine everything to make our TV Ambilight System! (Part 2)
However, this ambilight system relies on a computer. Because I will be using a computer program to capture and analyse the colours on the screen, that means the TV or monitor can only input from a computer, and not from any other media sournce such as a DVD player or a TV channel.
How to Capture and Analyse Screen for color
I have written a tutorial on how to take screen shot and analyse colour, with the help of some JAVA libraries, it’s become very simple and efficient.
I am using Processing as the programming environment because
- it’s cross-platform (can run on Windows, Mac and Linux)
- uses C++ syntax (my favorite language)
- has the same IDE/programming convention as the Arduino (actually the Arduino IDE was based on the Processing IDE :-D )
- Supports some very powerful and handy Java libraries
- it’s Free!
Even if you have not used Processing before, it’s very easy to pick up as long as you have programming experience. This is actually only the second time I used it, the last time was using it to display data from an Accelerometer in a 3D model.
Analyse Colours around the edges of the Screen
Once I learnt how to capture and down-sampling the pixels in one particular area to get the average colour of a region, I moved on further to do this simultaneously for multiple regions around the edge of the screen. Ultimately we can use the colour of each region to control the RGB LEDs.
The led labelling system in the code is acoording to this pattern. In this case we have 25 LEDs, so we are dividing the screen edges into 25 small boxes (26 to be exact, but that’s usually where the TV holder is, so we cannot put a LED there, so I am ignoring it).
And this is how it looks like when working on a colour image.
Here is the pseudo-code how this works.
- Declare Java Libraries
- Work out size of screen
- pre-calculate the locations for each sampling area
- loop
- capture screen
- sample pixel colours and compute
Here is my source code:
import java.awt.*; import java.awt.image.*; /* // using 12 RGB LEDs static final int led_num_x = 4; static final int led_num_y = 4; static final int leds[][] = new int[][] { {1,3}, {0,3}, // Bottom edge, left half {0,2}, {0,1}, // Left edge {0,0}, {1,0}, {2,0}, {3,0}, // Top edge {3,1}, {3,2}, // Right edge {3,3}, {2,3}, // Bottom edge, right half }; */ // using 25 RGB LEDs static final int led_num_x = 9; static final int led_num_y = 6; static final int leds[][] = 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 short fade = 70; // Preview windows int window_width, window_height, preview_pixel_width, preview_pixel_height; int[][] pixelOffset = new int[leds.length][256]; int[] screenData; // RGB values for each LED short[][] ledColor = new short[leds.length][3]; //creates object from java library that lets us take screenshots Robot bot; // bounds area for screen capture, by default the whole screen Rectangle dispBounds; // Monitor Screen information GraphicsEnvironment ge; GraphicsConfiguration[] gc; GraphicsDevice[] gd; void setup(){ int[] x = new int[16]; int[] y = new int[16]; // ge - Grasphics Environment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); // gd - Grasphics Device gd = ge.getScreenDevices(); DisplayMode mode = gd[0].getDisplayMode(); dispBounds = new Rectangle(0, 0, mode.getWidth(), mode.getHeight()); // Preview windows window_width = mode.getWidth()/5; window_height = mode.getHeight()/5; preview_pixel_width = window_width/led_num_x; preview_pixel_height = window_height/led_num_y; size(window_width, window_height); //standard Robot class error check try { bot = new Robot(gd[0]); } catch (AWTException e) { println("Robot class not supported by your system!"); exit(); } float range, step, start; for(int i=0; i<leds.length; i++) { // For each LED... // Precompute columns, rows of each sampled point for this LED // --- for columns ----- range = (float)dispBounds.width / led_num_x; // we only want 256 samples, and 16*16 = 256 step = range / 16.0; start = range * (float)leds[i][0] + step * 0.5; for(int col=0; col<16; col++) { x[col] = (int)(start + step * (float)col); } // ----- for rows ----- range = (float)dispBounds.height / led_num_y; step = range / 16.0; start = range * (float)leds[i][1] + step * 0.5; for(int row=0; row<16; row++) { y[row] = (int)(start + step * (float)row); } // ---- Store sample locations ----- // Get offset to each pixel within full screen capture for(int row=0; row<16; row++) { for(int col=0; col<16; col++) { pixelOffset[i][row * 16 + col] = y[row] * dispBounds.width + x[col]; } } } } void draw(){ //get screenshot into object "screenshot" of class BufferedImage BufferedImage screenshot = bot.createScreenCapture(dispBounds); // Pass all the ARGB values of every pixel into an array screenData = ((DataBufferInt)screenshot.getRaster().getDataBuffer()).getData(); for(int i=0; i<leds.length; i++) { // For each LED... int r = 0; int g = 0; int b = 0; for(int o=0; o<256; o++) { //ARGB variable with 32 int bytes where int pixel = screenData[ pixelOffset[i][o] ]; r += pixel & 0x00ff0000; g += pixel & 0x0000ff00; b += pixel & 0x000000ff; } ledColor[i][0] = (short)(r>>24 & 0xff); ledColor[i][1] = (short)(g>>16 & 0xff); ledColor[i][2] = (short)(b>>8 & 0xff); float preview_pixel_left = (float)dispBounds.width /5 / led_num_x * leds[i][0] ; float preview_pixel_top = (float)dispBounds.height /5 / led_num_y * leds[i][1] ; color rgb = color(ledColor[i][0], ledColor[i][1], ledColor[i][2]); fill(rgb); rect(preview_pixel_left, preview_pixel_top, preview_pixel_width, preview_pixel_height); } // Benchmark, how are we doing? println(frameRate); }
Build the RGB LED strip
I didn’t actually “build” it, but because all the LED came connected as one piece, and each LED was too close to each other, so I needed to cut them up and solder some wires in between to stretch it out.
It was such a nightmare soldering them, the connection pads are so small, and the solder just doesn’t stay on the pad! I also damaged about 5 of them becuase of over heat I think. I spent a long long time to finish this!
This post is getting long enough now. I will finish the rest of the project off in the next article.
25 comments
Muchas gracias por la información, me sera de gran utilidad…
I get an issue with the
size(window_width, window_height);
the Processing IDE tells me to fix the size() line to continue
I don’t know what to do…
Please Help !!!
Hey Oscar, hope I’m not digging this up after too long.
Everything is setup correctly as far as I can tell. However, after uncommenting the port code, it never seems to send data. The preview screen remains grey with no boarder blocks.
I have some experience using the exact same neopixel LEDs with arduino IDE and processing so I know it *can* work. However it looks like there is a snag.
Let me know your thoughts. Thanks
hello,
i have tried the arduino code and the processing code but neither of them are working. can you help me out please?
hello;
i have successfully made it work with arduino uno, ws2912b and kodi on pc, but i think processing software result is much better as i can see in your video, i have used 70 leds, 27 top, 12 each in right and left, bottom right 10 leds and bottom left 9 leds, total becomes 70 leds, so what will be the code in processing and arduino?
looking forward.
i have already ordered ws2812b led strip from aliexpress and this is on its way.
i have a question.
how these leds recieve video signal?
i mean after i run the code what should i do on pc, run a video in vlc or any other media player?
doesn’t matter what you do on the PC, it will screenshot it, and display the colour through the LEDs :)
thank you for the answer, but one last question.
if i want to use this ambilight for my samsung LED tv, then i will have to play the video on pc and play the same video on the tv also through usb flash drive or any medium? is that the idea to make it work?
Thanks buddy for inspiration
https://www.youtube.com/watch?v=os24RDtdHEY
Veeery interesting thing ! I thanks for all your tutos about arduino and FPV. I have 2 questions :
1. I recent TV, where I cound get Video Signal ? ( HDMI output ? )
2. Do you have a store link for WS2811 leds ?
Thanks again !
Germain
Hi Germain,
1. Currently it only works on a computer…
2. ebay has lots of choices :)
Oscar, it is impossible to copy the whole text right, this is the changed part, could you change it in the first text:
for (int i = 0; i < (3 * NUM_LED); i++) {
int led_index = i * 3 + 2;
strip.setPixelColor(i, strip.Color(led_color[led_index], led_color[led_index + 1], led_color[led_index + 2]));
i++;
strip.setPixelColor(i, strip.Color(led_color[led_index], led_color[led_index + 1], led_color[led_index + 2]));
i++;
strip.setPixelColor(i, strip.Color(led_color[led_index], led_color[led_index + 1], led_color[led_index + 2]));
} strip.show();
Sorry, I copied wrong program, this is the right one:
————————————————————————————————————————————————————-
#include
#define PIN 6
#define NUM_LED 2
#define NUM_DATA 8 // NUM_LED * 3 + 2
#define RECON_TIME 2000 // after x seconds idle time, send afk again.
// Parameter 1 = number of pixels in strip
// Parameter 2 = pin number (most are valid)
// Parameter 3 = pixel type flags, add together as needed:
// NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
// NEO_KHZ400 400 KHz (classic ‘v1’ (not v2) FLORA pixels, WS2811 drivers)
// NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products)
// NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_LED, PIN, NEO_GRB + NEO_KHZ800);
uint8_t led_color[NUM_DATA];
int index = 0;
unsigned long last_afk = 0;
unsigned long cur_time = 0;
void setup() {
strip.begin();
strip.show(); // Initialize all pixels to ‘off’
Serial.begin(115200);
Serial.print(“ozy”); // Send ACK string to host
for (;;) {
if (Serial.available() > 0) {
led_color[index++] = (uint8_t)Serial.read();
if (index >= NUM_DATA) {
Serial.write(‘y’);
last_afk = millis();
index = 0;
if ((led_color[0] == ‘o’) && (led_color[1] == ‘z’)) {
// update LEDs
for (int i = 0; i RECON_TIME) {
Serial.write(‘y’);
last_afk = cur_time;
index = 0;
}
}
}
}
void loop() {
}
Hi everybody,
thanks for good idea how to improve home entertainment. Everything works well (for now only with 8pcs of 2812 for testing) I already ordered strip with 150 pcs of these LEDs. As I know that to drive every LED separatelly would take amount of PC capacity, I tried to change Arduino code to send every incoming byte to 3 neighbouring LEDs. (in the processing I would use just 50 rectangles) Is anybody able to check what I have done wrong? With this program I can not light up more than 50 LEDs. Is the limit the line with “#define NUM_LED 50” ? When I change it to 150, Arduino expect bytes for 150 LEDs and receive only 50. Thanks.
***************************************************************************************************************************
#include
#define PIN 6
#define NUM_LED 50
#define NUM_DATA 152 // NUM_LED * 3 + 2
#define RECON_TIME 2000 // after x seconds idle time, send afk again.
uint8_t led_color[NUM_DATA];
int index = 0;
unsigned long last_afk = 0;
unsigned long cur_time = 0;
void setup() {
strip.begin();
strip.show(); // Initialize all pixels to ‘off’
Serial.begin(115200);
Serial.print(“ozy”); // Send ACK string to host
for (;;) {
if (Serial.available() > 0) {
led_color[index++] = (uint8_t)Serial.read();
if (index >= NUM_DATA) {
Serial.write(‘y’);
last_afk = millis();
index = 0;
if ((led_color[0] == ‘o’) && (led_color[1] == ‘z’)) {
// update LEDs
for (int i = 0; i RECON_TIME) {
Serial.write(‘y’);
last_afk = cur_time;
index = 0;
}
}
}
}
void loop() {
}
I have just found this post as my boyfriend has previously mentioned his desire to do the project however I am a complete and utter technophobe and have no understanding of these things!! I was looking to buy all the components for him as a surprise, can anyone give me a shopping list and some idea of costs?
Thanks.
If you’re having some of the errors above including com ports being out of range and what not, try installing 32 bit version of processing.
I have 64 bit windows with 64 bit processor and as soon as I tried 32 bit Processing software it worked right away. No code changes needed other than removing the two “//” before the serial port line.
Figure that might help some people out.
Great, cheers!
sketch_apr16a:1: error: ‘import’ does not name a type
sketch_apr16a:2: error: ‘import’ does not name a type
sketch_apr16a:24: error: ‘Rectangle’ does not name a type
sketch_apr16a:26: error: ‘Robot’ does not name a type
sketch_apr16a.ino: In function ‘void setup()’:
sketch_apr16a:31: error: ‘dispBounds’ was not declared in this scope
sketch_apr16a:31: error: expected type-specifier before ‘Rectangle’
sketch_apr16a:31: error: expected `;’ before ‘Rectangle’
sketch_apr16a:33: error: ‘size’ was not declared in this scope
sketch_apr16a:37: error: ‘bot’ was not declared in this scope
sketch_apr16a:37: error: expected type-specifier before ‘Robot’
sketch_apr16a:37: error: expected `;’ before ‘Robot’
sketch_apr16a:39: error: expected type-specifier before ‘AWTException’
sketch_apr16a:39: error: exception handling disabled, use -fexceptions to enable
sketch_apr16a:39: error: expected `)’ before ‘e’
sketch_apr16a:39: error: expected `{‘ before ‘e’
sketch_apr16a:39: error: ‘e’ was not declared in this scope
sketch_apr16a:39: error: expected `;’ before ‘)’ token
sketch_apr16a.ino: In function ‘void draw()’:
sketch_apr16a:55: error: ‘BufferedImage’ was not declared in this scope
sketch_apr16a:55: error: expected `;’ before ‘screenshot’
sketch_apr16a:58: error: expected unqualified-id before ‘[‘ token
sketch_apr16a:64: error: ‘screenData’ was not declared in this scope
sketch_apr16a:77: error: ‘color’ was not declared in this scope
sketch_apr16a:77: error: expected `;’ before ‘rgb’
sketch_apr16a:78: error: ‘rgb’ was not declared in this scope
sketch_apr16a:78: error: ‘fill’ was not declared in this scope
sketch_apr16a:79: error: ‘rect’ was not declared in this scope
sketch_apr16a:81: error: ‘frameRate’ was not declared in this scope
sketch_apr16a:81: error: ‘println’ was not declared in this scope
Can anyone explain why i get this list of errors? Very new to arduino and would love some help.
You are compiling the Processing code in Arduino IDE? make sure you are compiling in Processing IDE if that’s the case.
Hi
GREAT JOB i like it very much, but i have a litle problem with arduino code.
When i copy/paste arduino code to arduino software, he give me this errors:
strip_1m:15: error: Adafruit_NeoPixel does not name a type
strip_1m.ino: In function void setup():
strip_1m:23: error: strip was not declared in this scope
I real like to do this project for myself, i have allready all done , only this probem is left.
Maybe please someone send me Adruino code to mai email please : [email protected]
Thanks
have you downloaded the Adafruit_NeoPixel library and place it in your Arduino library directory as I told you?
Yes, thanks.
now working arduino and surce code, but when i start process, all working fine but LED strip dont turn ON
What should I do?
Hello,
if I use WS2812b this work?
what is the difference between WS2811 and WS2812b?
Thank you.
I don’t know, google it.
So dope. I will so have to try this. I have an awesome soldering station at work with all the right tools so hopefully I wont damage to many parts. Thank you for posting this.