# Fake TV Light for Engineers

## Overview

https://vine.co/v/i2i0Lz7Aea3

This is a very minor and could-have-been-simplistic project, but makes for an amusing story about obsessiveness. Before getting into computers, I'd planned to follow in my dad’s footsteps in graphic design. The vestiges of this interest sometimes flare up at the oddest times…

A commercial product called **_FakeTV_** simulates the changing light cast by a television set, making a home appear occupied at night and thus (in theory) a less appealing target to burglars.

![leds_FakeTV.jpg](https://cdn-learn.adafruit.com/assets/assets/000/032/418/medium640/leds_FakeTV.jpg?1462943807)

As _FakeTV_ involves microcontrollers and RGB LEDs, it’s inevitable then that **fake _FakeTV_-like projects have appeared on various DIY web sites.**

Having a one-time need for such an item, and with scads&nbsp;of microcontroler boards and NeoPixels already on-hand, I gave a few of these DIY projects a try.

All suffered from extreme…well…_fakeness._ Some cycled predictably through a canned sequence&nbsp;of colors. Others switched among random RGB values. I don’t know if burglars would even notice such a thing, but it immediately struck me as “off.” **_Way_ off.**

**_Movies are not&nbsp;random!_** &nbsp;Whether animated or live-action, they have Art Directors and Colorists&nbsp;and a specific color&nbsp;palette. Color sets the mood and defines locales and characters and is very _un-_random.

For example, the cinematic gem&nbsp;_Thunderpants_&nbsp;focuses&nbsp;almost exclusively&nbsp;on&nbsp;shades of green and brown.

![leds_thunderpants.jpg](https://cdn-learn.adafruit.com/assets/assets/000/032/419/medium640/leds_thunderpants.jpg?1462945853)

The _FakeTV_ web site claims their product — unlike DIY clones — began by **analyzing actual TV programming** , then developing an algorithm that realistically approximates these sorts of color changes **mathematically**.

Pretty cool idea, but I lacked both the time and inclination to develop a TV&nbsp;simulation algorithm. An idea struck me though…microcontrollers have a fair amount of **flash memory** …suppose we just stored color palettes of some **actual art-directed scenes from actual movies**? We don’t need to store whole&nbsp;image frames, just the average&nbsp;screen color over time.

Okay then…but how to acquire&nbsp;this kind of data? I was still in a hurry and didn’t want to process tons of source video.

 **Turns out there are folks who study just this sort of thing.** &nbsp;[This Washington Post article](https://www.washingtonpost.com/news/wonk/wp/2015/11/10/quiz-can-you-guess-the-movie-based-only-on-its-colors)&nbsp;highlights the work of Dillon Baker in creating color timelines from films, and links to other sites with similar visualizations. “Scraping” these sites might be one option.

![leds_dillon-baker.jpg](https://cdn-learn.adafruit.com/assets/assets/000/032/423/medium640/leds_dillon-baker.jpg?1462985863)

&nbsp;

The&nbsp;**_[Disney Animated](https://itunes.apple.com/us/app/disney-animated/id632312737)_**&nbsp;app for iPad includes **color timelines for 54 Disney films**. Mostly animated, but some with live action segments, others with more “natural” tones.

A couple of screen captures later and I had dozens of hours of&nbsp;source data to work with…

![leds_disney-animated.jpg](https://cdn-learn.adafruit.com/assets/assets/000/032/422/medium640/leds_disney-animated.jpg?1462985535)

But wait a minute…if we’d simply&nbsp;used random RGB colors…is&nbsp;any would-be thief&nbsp;_really_ gonna stop and say, **“Hang on…no director would cut between those two colors…that’s a fake TV!”** It no longer mattered at this point. The crust was&nbsp;broken, both the artist and engineer sides of the brain had been tickled. It’s an unstoppable force of nature. We’re doomed.

Some folks obsess over audio. I obsess over images.

# Fake TV Light for Engineers

## Crunching the Numbers

The iPad retina screen capture contains over **_nine megabytes_** of&nbsp;data.

There’s **32&nbsp;_kilo_bytes** &nbsp;of flash memory on an ATmega328P-based microcontroller board. Leaving room for playback code and bootloader, it’s more like a maximum of **26K** &nbsp;or so for our data. There are other boards with more flash space, or could have used an SD card, but I wanted to keep it simple and use a Metro 328 board…a 40-pixel **NeoPixel Shield** would neatly fit atop it for a FakeTV-like form-factor…but it can work just as well with NeoPixel strip, if that’s what you’ve got.

To make the data&nbsp;fit, something will have to go. Several&nbsp;orders of magnitude…

Looking closely at the screen capture, you’ll see these aren’t simple frame averages. The app describes their use of “clusters” in the visualization. To change the clusters into averages we’ll do some image editing with **Photoshop**.

First, a small black band at the bottom of the screen was cropped off.

There are **54 films** in the visualization, each with one horizontal band. To produce an average for each one, the image is simply resized to **54 pixels tall** (keeping the same width) specifically using&nbsp;_ **bilinear** _ interpolation mode (which will average all the pixels in each band, unlike _bicubic_ which&nbsp;assigns different pixel weights and will “bleed over” from adjacent rows).

Strictly for the sake of illustration here…not done to the source data…the image was re-stretched to the original size in _nearest neighbor_ mode, so you can see the color stripes.

![leds_clusters1.png](https://cdn-learn.adafruit.com/assets/assets/000/032/424/medium640/leds_clusters1.png?1462987995)

![leds_clusters2.png](https://cdn-learn.adafruit.com/assets/assets/000/032/425/medium640/leds_clusters2.png?1462988005)

Scaling down the image vertically this way provides a _massive_&nbsp;28X reduction. But the iPad screen is huge (2048 pixels across), and this still represents about&nbsp; **330 kilobytes** of color&nbsp;data. A few more reductions are needed…

 **Culling the herd:** Some color timelines aren’t as “lively” as others…they might dwell on one color for a very long time, or are just very dark overall…or too bright, or saturated, or desaturated. Three out of four timelines were discarded, keeping&nbsp;the 13 most visually interesting. These were selected manually.

Another 4X reduction. Now about **80K** of data.

![leds_culling.png](https://cdn-learn.adafruit.com/assets/assets/000/032/449/medium640/leds_culling.png?1463005368)

Representing each film in its entirety didn’t seem necessary…titles and end credits, for example, were usually very plain…so I cropped just a **1,000-pixel-wide** section from the larger image. If we assume each film has about a 100-ish minute average running time, this represents a roughly 45-ish minute chunk from each. That’s about 2.7-ish seconds-ish per pixel-ish.

Slightly over 2X reduction, now at **39 kilobytes**. Here’s the resulting image:

![](https://cdn-learn.adafruit.com/assets/assets/000/032/450/medium800/leds_crop.png?1463005646)

This is still too large to fit in the Metro’s flash space, but one more reduction is going to take place…

A **Python** script will process this image into an Arduino header file. Along the way, this will quantize the 24-bit color data down to 16-bits per pixel (5 bits red, 6 bits green, 5 bits blue)…a 2/3 reduction, or **26 kilobytes** total.

Though we lose some color fidelity, these are the least-significant bits…and there are plenty of other factors (LED color balance, diffuse interreflection with a room’s walls and other objects) that will tinge the resulting colors anyway. The playback code will do some shenanigans that should recover some intermediary shades.

Here’s the Python script that converts the image (named crop.png) to a .h file. It requires the Python Imaging Library or Pillow:

```auto
from PIL import Image
import sys

# Output one hex byte with C formatting & line wrap ------------------------

numBytes = 0  # Total bytes to be output in table
byteNum  = 0  # Current byte number (0 to numBytes-1)
cols     = 12 # Current column number in output (force indent on first one)

def writeByte(n):
        global cols, byteNum, numBytes

        cols += 1                      # Increment column #
        if cols >= 12:                 # If max column exceeded...
                print                  # end current line
                sys.stdout.write("  ") # and start new one
                cols = 0               # Reset counter
        sys.stdout.write("{0:#0{1}X}".format(n, 4))
        byteNum += 1
        if byteNum < numBytes:
                sys.stdout.write(",")
                if cols < 11:
                        sys.stdout.write(" ")

# Mainline code ------------------------------------------------------------

# Output 8-bit gamma-correction table:
sys.stdout.write("const uint8_t PROGMEM gamma8[] = {")
numBytes = 256
for i in range(256):
        base     = 1 + (i / 3)  # LCD, CRT contrast is never pure black
        overhead = 255.0 - base
        writeByte(base + int(pow(i / 255.0, 2.7) * overhead + 0.5))
print " },"


# Output color data (2 bytes per pixel):
sys.stdout.write("colors[] = {")
image        = Image.open("crop.png")
image.pixels = image.load()
numBytes     = image.size[0] * image.size[1] * 2
byteNum      = 0
cols         = 12
for y in range(image.size[1]):
        for x in range(image.size[0]):
                r = image.pixels[x, y][0]
                g = image.pixels[x, y][1]
                b = image.pixels[x, y][2]
                # Convert 8/8/8 (24-bit) RGB to 5/6/5 (16-bit):
                writeByte((r & 0xF8) | (g >> 5))
                writeByte(((g & 0x1C) << 3) | (b >> 3))
print " };"
```

The output of this program is redirected to a file (e.g. data.h) and can then be #included by the Arduino code on the next page…

# Fake TV Light for Engineers

## Arduino Sketch

In addition to the code below, you’ll need the output from the Python program on the prior page (or we also include it later on this page).

```auto
#include <Adafruit_NeoPixel.h>
#include "data.h" // Output of Python script

#define NUM_LEDS 40
#define PIN       6
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_LEDS, PIN, NEO_GRB);

#define  numPixels (sizeof(colors) / sizeof(colors[0]))
uint32_t pixelNum;
uint16_t pr = 0, pg = 0, pb = 0; // Prev R, G, B

void setup() {
  strip.begin();
  randomSeed(analogRead(A0));
  pixelNum = random(numPixels); // Begin at random point
}

void loop() {
  uint32_t totalTime, fadeTime, holdTime, startTime, elapsed;
  uint16_t nr, ng, nb, r, g, b, i;
  uint8_t  hi, lo, r8, g8, b8, frac;

  // Read next 16-bit (5/6/5) color
  hi = pgm_read_byte(&colors[pixelNum * 2    ]);
  lo = pgm_read_byte(&colors[pixelNum * 2 + 1]);
  if(++pixelNum >= numPixels) pixelNum = 0;

  // Expand to 24-bit (8/8/8)
  r8 = (hi & 0xF8) | (hi >> 5);
  g8 = (hi << 5) | ((lo & 0xE0) >> 3) | ((hi & 0x06) >> 1);
  b8 = (lo << 3) | ((lo & 0x1F) >> 2);
  // Apply gamma correction, further expand to 16/16/16
  nr = (uint8_t)pgm_read_byte(&gamma8[r8]) * 257; // New R/G/B
  ng = (uint8_t)pgm_read_byte(&gamma8[g8]) * 257;
  nb = (uint8_t)pgm_read_byte(&gamma8[b8]) * 257;

  totalTime = random(250, 2500);    // Semi-random pixel-to-pixel time
  fadeTime  = random(0, totalTime); // Pixel-to-pixel transition time
  if(random(10) < 3) fadeTime = 0;  // Force scene cut 30% of time
  holdTime  = totalTime - fadeTime; // Non-transition time

  startTime = millis();
  for(;;) {
    elapsed = millis() - startTime;
    if(elapsed >= fadeTime) elapsed = fadeTime;
    if(fadeTime) {
      r = map(elapsed, 0, fadeTime, pr, nr); // 16-bit interp
      g = map(elapsed, 0, fadeTime, pg, ng);
      b = map(elapsed, 0, fadeTime, pb, nb);
    } else { // Avoid divide-by-zero in map()
      r = nr;
      g = ng;
      b = nb;
    }
    for(i=0; i<NUM_LEDS; i++) {
      r8   = r >> 8; // Quantize to 8-bit
      g8   = g >> 8;
      b8   = b >> 8;
      frac = (i << 8) / NUM_LEDS; // LED index scaled to 0-255
      if((r8 < 255) && ((r & 0xFF) >= frac)) r8++; // Boost some fraction
      if((g8 < 255) && ((g & 0xFF) >= frac)) g8++; // of LEDs to handle
      if((b8 < 255) && ((b & 0xFF) >= frac)) b8++; // interp > 8bit
      strip.setPixelColor(i, r8, g8, b8);
    }
    strip.show();
    if(elapsed >= fadeTime) break;
  }
  delay(holdTime);

  pr = nr; // Prev RGB = new RGB
  pg = ng;
  pb = nb;
}
```

Points of interest in the Arduino sketch:

- The colors are from actual films, but the timing is semi-random…the goal isn’t to match specific scene tempos, just needed&nbsp;believable&nbsp;_color sequences._&nbsp;Some transitions are abrupt cuts, others fade (implying a camera pan or something moving in or out of frame).
- Color interpolation takes place in 16-bit space…LEDs are only 8-bit, but some fraction of the LEDs is used to get in-between shades.
- The NeoPixel library periodically disables interrupts and is known to mess with timekeeping functions like millis(). That’s okay for this application…we’re not precisely beat-matching any source material, just measuring relative time.
- Need something less bright? Change NUM\_LEDS to a smaller value and some NeoPixels will remain off.

Upload sketch to a Metro board (or Arduino Uno or compatible) with NeoPixel shield installed. Power from a quality&nbsp;USB supply. This can then be plugged into a basic lamp timer. The sketch starts at a random point in the color data, so it’s not repeating the same sequence every time it powers up.

Aim it at the ceiling to wash a room in color, similar to the glow from a TV, or directly toward curtains if you need more brightness.

https://vine.co/v/i2i0Lz7Aea3

If Python is unavailable on your system, or if it’s just easier this way, here are the entire gamma and color tables. Create a **new tab** in the Arduino sketch, name it “ **data.h** ”, then cut and paste this whole&nbsp;thing:

https://gist.github.com/jwcooper/a1456897880079a9da84cc35a72ade83


## Related Guides

- [Your First Power Switch Relay Project - Circuit Playground Smart Plug](https://learn.adafruit.com/beginner-first-project-power-switch-relay-circuit-playground-smart-plug.md)
- [Adafruit IO Basics: Schedule Actions](https://learn.adafruit.com/adafruit-io-basics-scheduled-triggers.md)
- [Set up Home Assistant with a Raspberry Pi](https://learn.adafruit.com/set-up-home-assistant-with-a-raspberry-pi.md)
- [Matrix Portal Creature Eyes](https://learn.adafruit.com/matrix-portal-creature-eyes.md)
- [Cardboard Window Skull With Animated LED Eyes](https://learn.adafruit.com/cardboard-window-skull-animated-led-eyes.md)
- [Expressive Pixels for Adafruit Matrix Portal](https://learn.adafruit.com/expressive-pixels-for-matrix-portal.md)
- [Dancing Inflatables: Make your Holiday Display Move](https://learn.adafruit.com/dancing-inflatables-make-your-holiday-display-move.md)
- [Purple Air AQI Display](https://learn.adafruit.com/purple-air-aqi-display.md)
- [Matrix Portal Scoreboard](https://learn.adafruit.com/matrix-portal-scoreboard.md)
- [No-Code IoT Door Alarm with WipperSnapper](https://learn.adafruit.com/no-code-iot-door-alarm-with-wippersnapper.md)
- [Coffee Rater](https://learn.adafruit.com/coffee-rater.md)
- [Milk Jug Glow Skull](https://learn.adafruit.com/milk-jug-glow-skull.md)
- [Chilled Drinkibot](https://learn.adafruit.com/chilled-drinkibot.md)
- [Weather Display Matrix](https://learn.adafruit.com/weather-display-matrix.md)
- [How to Convert a JVC Videosphere into a Monitor for Raspberry Pi and Desktop Computers](https://learn.adafruit.com/using-a-videosphere-as-a-digital-monitor.md)
