DIY TV Ambilight Using Arduino – Ozilight Part 1

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

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.

22 thoughts on “DIY TV Ambilight Using Arduino – Ozilight Part 1

  1. Brent

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

    Reply
  2. Adnan

    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
  3. Adnan

    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
      1. Adnan

        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?

  4. Germain LECOURTOIS

    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
  5. Mr.Yourself

    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
  6. Mr.Yourself

    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
  7. Mr.Yourself

    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
  8. Diane Cluness

    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
  9. schwizer

    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
  10. Josh

    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
    1. Oscar Post author

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

      Reply
  11. Tomza

    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
      1. Tomza

        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?

  12. Chris

    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

Leave a Reply

Your email address will not be published. Required fields are marked *

Are you Robot? *

I only check blog comments once or twice a week, if you want a quick reply you can post your question on this forum IntoFPV.com... You might get a faster response from me there (multirotor related only).