DIY TV Ambilight Using Arduino – Ozilight Part 1

by Oscar

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.

Some of the links on this page are affiliate links. I receive a commission (at no extra cost to you) if you make a purchase after clicking on one of these affiliate links. This helps support the free content for the community on this website. Please read our Affiliate Link Policy for more information.

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).

Untitled

And this is how it looks like when working on a colour image.

ambilight-capture-screen-edge-colour

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!

rgb-led-strip-ws2811

rgb-led-strip-ws2811-working

rgb-led-strip-ws2811-hot-glue

rgb-led-strip-ws2811-soldered

This post is getting long enough now. I will finish the rest of the project off in the next article.

Leave a Comment

By using this form, you agree with the storage and handling of your data by this website. Note that all comments are held for moderation before appearing.

25 comments

ambilight philips 10th March 2022 - 4:34 pm

Muchas gracias por la información, me sera de gran utilidad…

Reply
Cris 3rd October 2020 - 6:46 pm

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 !!!

Reply
Zack 18th March 2018 - 7:23 am

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

Reply
Brent 31st May 2017 - 5:52 pm

hello,
i have tried the arduino code and the processing code but neither of them are working. can you help me out please?

Reply
Adnan 11th January 2016 - 11:07 am

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.

Reply
Adnan 17th December 2015 - 5:43 am

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?

Reply
Oscar 17th December 2015 - 5:57 pm

doesn’t matter what you do on the PC, it will screenshot it, and display the colour through the LEDs :)

Reply
Adnan 8th January 2016 - 8:40 pm

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?

Reply
DanBK 7th December 2015 - 11:13 pm

Thanks buddy for inspiration
https://www.youtube.com/watch?v=os24RDtdHEY

Reply
Germain LECOURTOIS 15th November 2015 - 8:21 pm

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

Reply
Oscar 16th November 2015 - 9:57 am

Hi Germain,
1. Currently it only works on a computer…
2. ebay has lots of choices :)

Reply
Mr.Yourself 24th February 2015 - 5:35 pm

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();

Reply
Mr.Yourself 24th February 2015 - 5:27 pm

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() {
}

Reply
Mr.Yourself 24th February 2015 - 5:17 pm

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() {
}

Reply
Diane Cluness 26th November 2014 - 2:29 pm

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.

Reply
schwizer 31st October 2014 - 3:51 am

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.

Reply
Oscar 31st October 2014 - 9:28 am

Great, cheers!

Reply
Josh 16th April 2014 - 4:08 pm

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.

Reply
Oscar 17th April 2014 - 9:37 am

You are compiling the Processing code in Arduino IDE? make sure you are compiling in Processing IDE if that’s the case.

Reply
Tomza 1st April 2014 - 6:27 am

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

Reply
Oscar 1st April 2014 - 9:50 am

have you downloaded the Adafruit_NeoPixel library and place it in your Arduino library directory as I told you?

Reply
Tomza 1st April 2014 - 1:21 pm

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?

Reply
imagider 28th February 2014 - 3:37 pm

Hello,
if I use WS2812b this work?
what is the difference between WS2811 and WS2812b?
Thank you.

Reply
Oscar 28th February 2014 - 5:49 pm

I don’t know, google it.

Reply
Chris 22nd January 2014 - 4:46 am

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.

Reply