The best tools to make your project dreams come true

Login or Signup


HalloWing Light Paintstick

By Adafruit Industries

Courtesy of Adafruit

Guide by John Park

Overview

Figure 1

Figure 1a

We love light painting and over the years we have created a number of Learn Guides on the topic, ranging from simple to highly sophisticated.

With this project, painting with light in long exposure photographs has never been easier. You can explore this beautiful artform with a digital camera that has manual setting, or even a smart phone with an app, and build your own Light Paintstick to draw bitmapped images in midair!

Look below each light painting photo in this guide to see the bitmap image that was running on the Light Paintstick.

Figure 2

Figure 3

Parts

Alternative microcontroller and NeoPixel strip option:

Materials

In addition to the parts listed above, you'll need the following materials:

  • Wooden yardstick (available at any hardware store)
  • Zip ties
  • Optional saw to cut the yardstick length down a bit
  • Optional sandpaper for shaping the yardstick handle
  • Optional matte gray spray paint for the yardstick

And, a camera with manual control of shutter speed and exposure aperture, or a long exposure/"light trails" application for your smart phone, as well as a tripod or other way to secure your camera. It helps (and is lots of fun!) to have a friend to help you out during photo shoots.

Build the Light Paintstick

Figure 4

This is a very straightforward build -- all you need is a yardstick and some zip ties to attach the NeoPixel strip, board, battery, and potentiometer!

Figure 5

 

Figure 6

We'll customize the build a little bit to give it a nice fit and finish.

Cut the Yardstick

Figure 7

You can skip this step if you like, but it's nice to trim off some of the excess length of the yardstick to make the Light Paintstick a bit more compact and easier to swing around in midair.

No need to measure! Just use a hand saw or power saw (bandsaw, chop saw, miter saw, jig saw, or table saw) to cut the yardstick at the 28-1/2" mark.

Sanding

Figure 8

To make the Light Paintstick more comfortable to hold, you can use some sandpaper to shape the handle section a bit and round off the edges.

Drilling

Figure 9

You may want to mount the Light Paintstick onto a bicycle or scooter to create some large-scale light paintings -- you can add some holes near the base of the handle to accommodate this.

Mark and drill two 1/4" holes as shown.

It's helpful to drill over a sacrificial piece of wood to prevent splintery blow-out on the back-side of the hole as the drill bit breaks through.

Figure 10

Figure 10a

Paint the Yardstick

Figure 11

In a well ventilated area, set down some newspaper and a couple of blocks for standoffs.

Spray the yardstick with even coats of spray paint.

Allow to dry for 1/2 hour, then flip the stick and paint the other side.

Two full coats should be sufficient.

Marvel at how professional and awesome your Light Paintstick is now looking!

Figure 12

Now that he stick is prepped, it's time to add the electronics!

Figure 13

Connect the Components

Figure 13a

Potentiometer

The potentiometer will be used to control the playback speed of the bitmap image. Connect it to the three sockets of the sensor cable as shown. The green wire goes in the middle to send potentiometer wiper position data, while red and black go to the outer pins for voltage and ground.

Connect the other end of the cable to the JST sensor port on the HalloWing.

This port is marked "SENSE".

Figure 15

NeoPixel Strip

Plug the NeoPixel strip connector into the NeoPixel JST port on the HalloWing.

This port is marked "NEOPIX".

Figure 16

Battery

You'll power the HalloWing Light Paintstick with a 3.7V lithium polymer battery.

The HalloWing even has a charging circuit built in, so you can recharge the battery at any time by simply plugging in a micro USB cable!

Plug the battery into the port marked "LiPoly Battery Only!"

You can secure the potentiometer-to-cable connection with a bit of tape to prevent it from disconnecting.

Figure 17

Now we can connect the HalloWing and components to the stick.

Figure 18

Attach NeoPixel Strip

Figure 19

Figure 20

Lay the NeoPixel strip on one side of the stick, with the LEDs facing up.

Use zip ties to secure the strip to the stick.

Trim off the excess zip tie ends with diagonal cutters or scissors.

Connect the Potentiometer

Connect the potentiometer to the Light Paintstick with a zip tie as shown.

Figure 21

Add the Battery

Secure the battery just below the potentiometer with a zip tie.

Figure 22

Attach the HalloWing

Figure 23

Use four thin zip ties to secure the board to the stick.

Feed one zip tie through the two mounting holes as shown.

Use a second zip tie connected on both ends to the first zip tie on the underside of the board to join them.

Pull them snug but not too tight (don't want to put too much pressure on the TFT display!).

Trim the excess ends.

Here's what the finished HalloWing Light Paintstick looks like!

Figure 24

Figure 25

Alternate CPX version

You can create a Circuit Playground Express Light Paintstick instead if you like! For this, we'll use a 3x AAA battery box instead of a LiPoly, a NeoPixel strip with alligator clips, and we won't use a potentiometer, instead controlling playback speed with the CPX buttons.

Figure 26

Figure 27

Figure 28

Use double-stick foam tape to connect the CPX to the stick, and to connect the battery box to the back side of the stick, behind the CPX.

Figure 29

Figure 30

Figure 31

Figure 33

Code with CircuitPython

Figure 34

The CircuitPython code we'll use was designed to display light painting art based upon bitmap images. There are two display modes: looping and one-shot.

Looping will run the image continuously. This is great for streaking images all around, or for "stamping" copies of your image into mid-air.

One-shot images will only display once each time you trigger them by pressing the capacitive touch sensor assigned -- in the case of HalloWing this is pad A2.

CircuitPython Setup

To get started, you'll want to set up your HalloWing or Circuit Playground Express for use with CircuitPython by following this guide for HalloWing or by following this guide for CPX. When you're ready, and can upload code to the board return here.

Adafruit really likes using the Mu editor to edit the CircuitPython code. See this guide on loading and using Mu.

HalloWing Code

You can copy the code here and then paste it into Mu. Save it to your HalloWing as code.py

 

Copy Code
"""HalloWing Light Paintbrush"""
# Single images only. Filename is set in code,
# potentiometer is used to tune playback SPEED
# images should be 30px high, up to 100px wide, 24-bit .bmp files

import gc
import time
import board
import touchio
import digitalio
from analogio import AnalogIn
from neopixel_write import neopixel_write

# uncomment one line only here to select bitmap
FILENAME = "bats.bmp" # BMP file to load from flash filesystem
#FILENAME = "digikey.bmp"
#FILENAME = "burger.bmp"
#FILENAME = "afbanner.bmp"
#FILENAME = "blinka.bmp"
#FILENAME = "ghost04.bmp"
#FILENAME = "ghost07.bmp"
#FILENAME = "ghost02.bmp"
#FILENAME = "helix-32x30.bmp"
#FILENAME = "wales2-107x30.bmp"
#FILENAME = "pumpkin.bmp"
#FILENAME = "rainbow.bmp"
#FILENAME = "rainbowRoad.bmp"
#FILENAME = "rainbowZig.bmp"
#FILENAME = "skull.bmp"
#FILENAME = "adabot.bmp"
#FILENAME = "green_stripes.bmp"
#FILENAME = "red_blue.bmp"
#FILENAME = "minerva.bmp"

TOUCH = touchio.TouchIn(board.A2) # Rightmost capacitive touch pad
ANALOG = AnalogIn(board.SENSE) # Potentiometer on SENSE pin
BRIGHTNESS = 1.0 # NeoPixel brightness 0.0 (min) to 1.0 (max)
GAMMA = 2.7 # Adjusts perceived brighthess linearity
NUM_PIXELS = 30 # NeoPixel strip length (in pixels)
LOOP = False #set to True for looping
# Switch off onboard NeoPixel...
NEOPIXEL_PIN = digitalio.DigitalInOut(board.NEOPIXEL)
NEOPIXEL_PIN.direction = digitalio.Direction.OUTPUT
neopixel_write(NEOPIXEL_PIN, bytearray(3))
# ...then assign NEOPIXEL_PIN to the external NeoPixel connector:
NEOPIXEL_PIN = digitalio.DigitalInOut(board.EXTERNAL_NEOPIXEL)
NEOPIXEL_PIN.direction = digitalio.Direction.OUTPUT
neopixel_write(NEOPIXEL_PIN, bytearray(NUM_PIXELS * 3))

def read_le(value):
"""Interpret multi-byte value from file as little-endian value"""
result = 0
shift = 0
for byte in value:
result += byte << shift
shift += 8
return result

class BMPError(Exception):
"""Error handler for BMP-loading function"""
pass

def load_bmp(filename):
"""Load BMP file, return as list of column buffers"""
# pylint: disable=too-many-locals, too-many-branches
try:
print("Loading", filename)
with open("/" + filename, "rb") as bmp:
print("File opened")
if bmp.read(2) != b'BM': # check signature
raise BMPError("Not BitMap file")

bmp.read(8) # Read & ignore file size and creator bytes

bmp_image_offset = read_le(bmp.read(4)) # Start of image data
bmp.read(4) # Read & ignore header size
bmp_width = read_le(bmp.read(4))
bmp_height = read_le(bmp.read(4))
# BMPs are traditionally stored bottom-to-top.
# If bmp_height is negative, image is in top-down order.
# This is not BMP canon but has been observed in the wild!
flip = True
if bmp_height < 0:
bmp_height = -bmp_height
flip = False

print("WxH: (%d,%d)" % (bmp_width, bmp_height))

if read_le(bmp.read(2)) != 1:
raise BMPError("Not single-plane")
if read_le(bmp.read(2)) != 24: # bits per pixel
raise BMPError("Not 24-bit")
if read_le(bmp.read(2)) != 0:
raise BMPError("Compressed file")

print("Image format OK, reading data...")

row_size = (bmp_width * 3 + 3) & ~3 # 32-bit line boundary

# Constrain rows loaded to pixel strip length
clipped_height = min(bmp_height, NUM_PIXELS)

# Allocate per-column pixel buffers, sized for NeoPixel strip:
columns = [bytearray(NUM_PIXELS * 3) for _ in range(bmp_width)]

# Image is displayed at END (not start) of NeoPixel strip,
# this index works incrementally backward in column buffers...
idx = (NUM_PIXELS - 1) * 3
for row in range(clipped_height): # For each scanline...
if flip: # Bitmap is stored bottom-to-top order (normal BMP)
pos = bmp_image_offset + (bmp_height - 1 - row) * row_size
else: # Bitmap is stored top-to-bottom
pos = bmp_image_offset + row * row_size
bmp.seek(pos) # Start of scanline
for column in columns: # For each pixel of scanline...
# BMP files use BGR color order
blue, green, red = bmp.read(3)
# Rearrange into NeoPixel strip's color order,
# while handling brightness & gamma correction:
column[idx] = int(pow(green / 255, GAMMA) * BRIGHTNESS * 255 + 0.5)
column[idx+1] = int(pow(red / 255, GAMMA) * BRIGHTNESS * 255 + 0.5)
column[idx+2] = int(pow(blue / 255, GAMMA) * BRIGHTNESS * 255 + 0.5)
idx -= 3 # Advance (back) one pixel

# Add one more column with no color data loaded. This is used
# to turn the strip off at the end of the painting operation.
if not LOOP:
columns.append(bytearray(NUM_PIXELS * 3))

print("Loaded OK!")
gc.collect() # Garbage-collect now so playback is smoother
return columns

except OSError as err:
if err.args[0] == 28:
raise OSError("OS Error 28 0.25")
else:
raise OSError("OS Error 0.5")
except BMPError as err:
print("Failed to parse BMP: " + err.args[0])


# Load BMP image, return 'COLUMNS' array:
COLUMNS = load_bmp(FILENAME)

print("Mem free:", gc.mem_free())

COLUMN_DELAY = ANALOG.value / 65535.0 / 10.0 # 0.0 to 0.1 seconds
while LOOP:
for COLUMN in COLUMNS:
neopixel_write(NEOPIXEL_PIN, COLUMN)
time.sleep(COLUMN_DELAY)

while True:
# Wait for touch pad input:
while not TOUCH.value:
continue

COLUMN_DELAY = ANALOG.value / 65535.0 / 10.0 # 0.0 to 0.1 seconds
# print(COLUMN_DELAY)

# Play back color data loaded into each column:
for COLUMN in COLUMNS:
neopixel_write(NEOPIXEL_PIN, COLUMN)
time.sleep(COLUMN_DELAY)
# Last column is all 0's, no need to explicitly clear strip

# Wait for touch pad release, just in case:
while TOUCH.value:
continue

 

Circuit Playground Express Code

If you're using the Circuit Playground Express, copy and paste this code instead.

 

Copy Code
"""Circuit Playground Express Light Paintbrush"""
# Single images only. Filename is set in code,
# CPX buttons A&B are used to increase and decrease playback speed
# Touch pad A5 to trigger playback in one-shot mode
# Switch on CPX goes from one-shot to looping mode, requires reset
# images should be 30px high, up to 100px wide, 24-bit .bmp files

import gc
import time
import board
import touchio
import digitalio
from digitalio import DigitalInOut, Direction, Pull
from neopixel_write import neopixel_write

# uncomment one line only here to select bitmap
FILENAME = "bats.bmp" # BMP file to load from flash filesystem
#FILENAME = "jpw01.bmp"
#FILENAME = "digikey.bmp"
#FILENAME = "burger.bmp"
#FILENAME = "afbanner.bmp"
#FILENAME = "blinka.bmp"
#FILENAME = "ghost.bmp"
#FILENAME = "helix-32x30.bmp"
#FILENAME = "wales2-107x30.bmp"
#FILENAME = "pumpkin.bmp"
#FILENAME = "rainbow.bmp"
#FILENAME = "rainbowRoad.bmp"
#FILENAME = "rainbowZig.bmp"
#FILENAME = "skull.bmp"
#FILENAME = "adabot.bmp"
#FILENAME = "green_stripes.bmp"
#FILENAME = "red_blue.bmp"
#FILENAME = "minerva.bmp"

TOUCH = touchio.TouchIn(board.A5) # capacitive touch pad
SPEED = 10000
SPEED_ADJUST = 2500 # This value changes the increment for button speed adjustments
BRIGHTNESS = 1.0 # Set brightness here, NOT in NeoPixel constructor
GAMMA = 2.7 # Adjusts perceived brighthess linearity
NUM_PIXELS = 30 # NeoPixel strip length (in pixels)
NEOPIXEL_PIN = board.A1 # Pin where NeoPixels are connected
DELAY_TIME = 0.01 # Timer delay before it starts
LOOP = False # Set to True for looping

# button setup
button_a = DigitalInOut(board.BUTTON_A)
button_a.direction = Direction.INPUT
button_a.pull = Pull.DOWN

button_b = DigitalInOut(board.BUTTON_B)
button_b.direction = Direction.INPUT
button_b.pull = Pull.DOWN

# switch setup
switch = DigitalInOut(board.SLIDE_SWITCH)
switch.direction = Direction.INPUT
switch.pull = Pull.UP

#status led setup
led = DigitalInOut(board.D13)
led.direction = Direction.OUTPUT


if switch.value:
LOOP = True
else:
LOOP = False


# Enable NeoPixel pin as output and clear the strip
NEOPIXEL_PIN = digitalio.DigitalInOut(NEOPIXEL_PIN)
NEOPIXEL_PIN.direction = digitalio.Direction.OUTPUT
neopixel_write(NEOPIXEL_PIN, bytearray(NUM_PIXELS * 3))

def read_le(value):
"""Interpret multi-byte value from file as little-endian value"""
result = 0
shift = 0
for byte in value:
result += byte << shift
shift += 8
return result

class BMPError(Exception):
"""Error handler for BMP-loading function"""
pass

def load_bmp(filename):
"""Load BMP file, return as list of column buffers"""
# pylint: disable=too-many-locals, too-many-branches
try:
print("Loading", filename)
with open("/" + filename, "rb") as bmp:
print("File opened")
if bmp.read(2) != b'BM': # check signature
raise BMPError("Not BitMap file")

bmp.read(8) # Read & ignore file size and creator bytes

bmp_image_offset = read_le(bmp.read(4)) # Start of image data
bmp.read(4) # Read & ignore header size
bmp_width = read_le(bmp.read(4))
bmp_height = read_le(bmp.read(4))
# BMPs are traditionally stored bottom-to-top.
# If bmp_height is negative, image is in top-down order.
# This is not BMP canon but has been observed in the wild!
flip = True
if bmp_height < 0:
bmp_height = -bmp_height
flip = False

print("WxH: (%d,%d)" % (bmp_width, bmp_height))

if read_le(bmp.read(2)) != 1:
raise BMPError("Not single-plane")
if read_le(bmp.read(2)) != 24: # bits per pixel
raise BMPError("Not 24-bit")
if read_le(bmp.read(2)) != 0:
raise BMPError("Compressed file")

print("Image format OK, reading data...")

row_size = (bmp_width * 3 + 3) & ~3 # 32-bit line boundary

# Constrain rows loaded to pixel strip length
clipped_height = min(bmp_height, NUM_PIXELS)

# Allocate per-column pixel buffers, sized for NeoPixel strip:
columns = [bytearray(NUM_PIXELS * 3) for _ in range(bmp_width)]

# Image is displayed at END (not start) of NeoPixel strip,
# this index works incrementally backward in column buffers...
idx = (NUM_PIXELS - 1) * 3
for row in range(clipped_height): # For each scanline...
if flip: # Bitmap is stored bottom-to-top order (normal BMP)
pos = bmp_image_offset + (bmp_height - 1 - row) * row_size
else: # Bitmap is stored top-to-bottom
pos = bmp_image_offset + row * row_size
bmp.seek(pos) # Start of scanline
for column in columns: # For each pixel of scanline...
# BMP files use BGR color order
blue, green, red = bmp.read(3)
# Rearrange into NeoPixel strip's color order,
# while handling brightness & gamma correction:
column[idx] = int(pow(green / 255, GAMMA) * BRIGHTNESS * 255 + 0.5)
column[idx+1] = int(pow(red / 255, GAMMA) * BRIGHTNESS * 255 + 0.5)
column[idx+2] = int(pow(blue / 255, GAMMA) * BRIGHTNESS * 255 + 0.5)
idx -= 3 # Advance (back) one pixel

# Add one more column with no color data loaded. This is used
# to turn the strip off at the end of the painting operation.
if not LOOP:
columns.append(bytearray(NUM_PIXELS * 3))

print("Loaded OK!")
gc.collect() # Garbage-collect now so playback is smoother
return columns

except OSError as err:
if err.args[0] == 28:
raise OSError("OS Error 28 0.25")
else:
raise OSError("OS Error 0.5")
except BMPError as err:
print("Failed to parse BMP: " + err.args[0])


# Load BMP image, return 'columns' array:
COLUMNS = load_bmp(FILENAME)

print("Mem free:", gc.mem_free())

COLUMN_DELAY = SPEED / 65535.0 / 10.0 # 0.0 to 0.1 seconds
# print(COLUMN_DELAY)

led.value = True

while LOOP:
for COLUMN in COLUMNS:
neopixel_write(NEOPIXEL_PIN, COLUMN)
time.sleep(COLUMN_DELAY)

while True:

# Wait for touch pad input:
# buttons increase and decrease speed of playback
while not TOUCH.value:
if button_a.value:
if SPEED >= SPEED_ADJUST:
led.value = False
SPEED = SPEED - SPEED_ADJUST
COLUMN_DELAY = SPEED / 65535.0 / 10.0 # 0.0 to 0.1 seconds
time.sleep(0.25) #debounce
led.value = True
# print(SPEED)
if button_b.value:
if SPEED < 100000:
led.value = False
SPEED = SPEED + SPEED_ADJUST
COLUMN_DELAY = SPEED / 65535.0 / 10.0 # 0.0 to 0.1 seconds
time.sleep(0.25) #debounce
led.value = True
# print(SPEED)
continue

time.sleep(DELAY_TIME)

# Play back color data loaded into each column:
for COLUMN in COLUMNS:
neopixel_write(NEOPIXEL_PIN, COLUMN)
time.sleep(COLUMN_DELAY)
# Last column is all 0's, no need to explicitly clear strip

# Wait for touch pad release, just in case:
while TOUCH.value:
continue

 

For the CPX version, you can hit the on board buttons to increase or decrease the speed of playback. Additionally, flipping the switch to the left and restarting the board puts into looping animation mode, flip to the right and restart for one-shot mode where pressing A5 runs the image one time.

Image Selection

The are only two sections of code you'll need to adjust: FILENAME variable assignment and the image looping.

By default, it will play the bats.bmp file. If you create your own bitmap named unicorn.bmp and load it onto the HalloWing, you'll want to adjust the FILENAME to match, such as:

FILENAME = "unicorn.bmp"

If you want your image to loop automatically to create streaks or repeating stamps, change this line:

LOOP = False

to this:

LOOP = True

On the next page we'll add images to the board.

Make Pixel Art

Make Pixel Art

You'll need some art to display with your HalloWing Light Paintstick. You can get started by downloading this collection, and then unzipping it.

PixelArt.zip

Images

The Light Paintstick plays back images stored in flash memory on the HalloWing or Circuit Playground Express. To add images, plug in your board via USB to your computer. When the board mounts, you'll see a new drive named CIRCUITPY appear. Simply drag your image files onto the board with your computer file manager.

Art Specs

If you want to create your own artwork for display, these are the specifications to follow:

  • Images should be a maximum of 30 pixels high (the same as the number of NeoPixels on our strip)
  • Images can be up to 100 pixels wide
  • Colors are 24-bit (8-bits per channel) RGB
  • Save files as .bmp format

We've found that crisp images (not too much antialiasing) work best. The colors look best when using full primaries, such as 255 red, green, or blue, and mixes of those, but you can definitely try any color you like.

You can use nearly any paint program, including online pixel art generators that run in the browser!

Figure 36

Figure 37

Take Long Exposure Photos

Figure 38

Figure 39

Now we come to the fun part of making art that can hover in space! You'll freeze temporal, sequential art into what appears to be a single instant! How is this done? The trick is long exposure photography.

Typically, we take photographs that only expose the sensor (or film) for a very tiny fraction of a second. This is a simplification that ignores many factors, but you can think of it as: the shorter the exposure (also called shutter speed) the sharper the image. This is because any subjects that are moving will create a blur if the exposure is long, since they will occupy more than one point in space during the time that light is exposing onto the sensor.

Additionally, if we use typical aperture sizes, or f-stops, (think of it as the size of the hole letting light pass onto the shutter) we can let in a lot of light during that very quick exposure. If the shutter is open too long, then too much light will hit the sensor and the shot will be over exposed.

Long exposure photography flips these conventions on their head! We'll use very long exposures -- anywhere from 4 to 30 seconds for our Light Paintstick images -- so that our subject (the NeoPixels) will occupy many different points in space during the time that the shutter is open. But, to avoid over exposing the sensor and creating a blindingly bright image with no details, we'll use a very small aperture. This means that only very bright objects (such as our NeoPixel LEDs) will send enough light to the sensor to be exposed on the final image.

Figure 40

Figure 41

The size of the aperture is expressed in f-stop values. It can be confusing at first because the lower the f-stop number the larger the opening. This is because the f-number is a ratio of focal length to aperture diameter.

Typical settings for "normal" daylight photography are fast shutters -- 1/250 second for example, and wide open apertures -- f/5.6 for example. Long exposure photographs taken in dark settings will use slow shutter speeds such as 4" (seconds) to 30" and small apertures such as f/22.

Tools

Ideally, you will want to use a good camera with manual control over the settings, mounted on a tripod. Any mirrorless system, DSLR, or higher-end point-and-shoot should give you the control you need. The camera will need to allow you to shoot either long exposures or in "bulb" mode where the shutter stays open indefinitely until you release it.

Alternately, you can use a smart phone and dedicated apps. Search for the terms "long exposure" and "light trails" to find some options.

Don't forget that the HalloWing has an ON/OFF switch!

Action

Now, you get to start experimenting! Start off simply, with a rainbow pattern. In a dark environment, set up your camera, trigger the shutter, get in front of the lens, turn on your Light Paintstick, and sweep an arc shape over your head.

Release the shutter and check out your photo! You can now start to tune the settings to dial things in.

Next, try some longer exposures and run around with your Light Paintstick. Get creative! It's also fun to have some context in your photos, so try tuning the exposure settings on your camera so that some of your environment is visible, not just LED streaks against black.

Figure 42

This photo has been over exposed a bit and a flash triggered so you can see the scene and light paintstick path/action.

Figure 43

Figure 44

Figure 45

When no flash is used the subject doing the light painting is "invisible"!

Figure 46

Figure 47

Figure 48

Figure 49

Figure 50

Figure 51

Floating Images

Now, you can try stamping an image into midair! Switch the CircuitPython code LOOP = True to LOOP = False and re-save the code.py file onto the board so that you're displaying one of the individual bitmaps, such as the pumpkin.

You will want to tune the speed of the play back so that the image draws in about 3-4 seconds. Set your camera for a 5-6 second exposure. Trigger the shutter and then move the Light Paintstick in a straight line parallel to the camera.

 Figure 52

Figure 53

Figure 54

You can draw logos in midair, too!

Figure 55

Figure 56

Figure 57

Figure 58

Bats!

Figure 59

Figure 60

Multiple Stamps

You can trigger your image multiple times during a single exposure, just try to not overlap! Also notice that you can create a "backwards" image by moving the wand from left-to-right instead of right to left as with the bottom pumpkin shown here.

 

Figure 61

Figure 62

You can also switch the code back to looping LOOP = True so that as long as you move the wand the image will repeat.

Figure 63

Figure 64

Here's an example of this on a playground merry-go-round.

Figure 65

Figure 66

If your image is squashed, try turning the potentiometer to the right a bit so that it draws the raster a bit slower. If the image is too wide, turn the pot to the left to speed things up. You'll want to still move the wand at the same speed so that you only adjust a single variable at a time.

Here's an example of the same image being played back at different speeds by tuning the potentiometer between takes.

Figure 67

Rotate your arm in a big circle!

Figure 68

Have fun with your light painting! You can even start to get fancy and include yourself in the photos -- just draw your images as usual in the air, and then at the end, hold very still and point your Light Paintbrush at your own face for a few seconds to add it to the exposed portion of the frame!

Figure 70

Figure 71

Figure 72

So, have fun experimenting with different artwork and techniques as you explore the fascinating art of long exposure light painting with your HalloWing Light Paintstick!

Note, you can create some creepy outtakes while you're at it.

Figure 73

Here's the ghost of photographer Joel looking highly weird. This is what happens when you test the long exposure + flash theory.

NOTE: He is not wearing a mask.

Figure 74

Key Parts and Components

Add all Digi-Key Parts to Cart
  • 1528-2280-ND
  • 298-13123-ND
  • 1932-1019-ND
  • 1528-2594-ND
  • 1528-1997-ND
  • 1528-2697-ND
  • 1528-2710-ND