Everything is Dots

Dots, dots, everything is dots...

I have spent a lot of time over the past few months in writing programs to emulate the visual effect of a dot matrix printout. For those who didn't live through the era, there was a time in computer history when most everything printed looked all faded and banded and grainy. And even so, it was probably more presentable than the alternative-- your handwriting/drawing skills. That was all made possible by a dot matrix printer. It creates an image by barbarically hammering an ink impregnated ribbon with a pin onto a piece of paper.

I won't document my complete journey, but I will share one of many scripts that I've written. It's actually not the best approximation of a dot matrix printout that I've produced, but it is the result of a difficult personal challenge. I wanted to approximate the effect using only bash and ImageMagick. ImageMagick is a software manipulation suite of tools that is by its name aptly powerful and arcane. I'm pleased with the result, but I did go close to losing myself to dot mania.

The Script


#!/bin/bash
# ./dotmagick.sh [input file]
# A tmp directory will be generated with buildfiles
# remove build files when modifying variables e.g.: rm -rf tmp && ./dotmagick.sh [input file]

## Build Variables
# Size of dot in pixels
DOT_SIZE=4
# Width of image in dots
WIDTH=320
# Height of image in dots
HEIGHT=320

WORKING_SCALE=4
INPUT_FILE=$1
BUILD_DIR="./tmp"
DISPLACEMENT_VALUE=$((DOT_SIZE / 10))

IMAGE_WIDTH=$((WIDTH * DOT_SIZE))
IMAGE_HEIGHT=$((HEIGHT * DOT_SIZE))

# Generate Build Files
if [ ! -f $BUILD_DIR/masking_grid.png ]
then
  echo "Generating masking grid."

  # Make Build Directory
  mkdir -p $BUILD_DIR

  echo "Creating Dot Grid"
  convert -size $((IMAGE_WIDTH * WORKING_SCALE))x$((IMAGE_HEIGHT * WORKING_SCALE)) xc:white \
    $BUILD_DIR/masking_grid.png
  convert -size $((DOT_SIZE * WORKING_SCALE))x$((DOT_SIZE * WORKING_SCALE)) xc:white -strokewidth 1 -fill black \
    -draw "circle $((DOT_SIZE * WORKING_SCALE / 2)),$((DOT_SIZE * WORKING_SCALE / 2)) $((DOT_SIZE * WORKING_SCALE / 2)),$((DOT_SIZE * WORKING_SCALE - DISPLACEMENT_VALUE))" -write mpr:dot +delete \
    -size $((IMAGE_WIDTH * WORKING_SCALE))x$((IMAGE_HEIGHT * WORKING_SCALE)) tile:mpr:dot \
    $BUILD_DIR/masking_grid.png

  echo "Creating X Axis Displacement Map"
  convert -size ${WIDTH}x${HEIGHT} xc:"gray(50%)" \
    +noise Random -channel green -separate -posterize 3 -scale $((DOT_SIZE * 100 * WORKING_SCALE))% \
    $BUILD_DIR/displacement_map_x.png

  echo "Creating Y Axis Displacement Map"
  convert -size ${WIDTH}x${HEIGHT} xc:"gray(50%)" \
    +noise Random -channel red -separate -posterize 3 -scale $((DOT_SIZE * 100 * WORKING_SCALE))% \
    $BUILD_DIR/displacement_map_y.png

  echo "Displacing Dot Grid on X Axis"
  composite $BUILD_DIR/displacement_map_x.png $BUILD_DIR/masking_grid.png \
    -displace $((DISPLACEMENT_VALUE * WORKING_SCALE))x0 \
    $BUILD_DIR/masking_grid.png

  echo "Displacing Dot Grid on Y Axis"
  composite $BUILD_DIR/displacement_map_y.png $BUILD_DIR/masking_grid.png \
    -displace 0x$((DISPLACEMENT_VALUE * WORKING_SCALE)) \
    $BUILD_DIR/masking_grid.png

  echo "Creating Dot Mask"
  convert $BUILD_DIR/masking_grid.png \
    -fuzz 90% \
    -transparent black \
    $BUILD_DIR/masking_grid.png

  echo "Creating Pin Banding Overlay"
  PIN_COLORS="#282828 #252525 #292929 #101010 #656565 #181818 #191919 #404040"
  STROKE_ARRAY=()
  i=0
  for PIN_COLOR in $PIN_COLORS;
  do
    STROKE_ARRAY[$i]="stroke '$PIN_COLOR' line 0,$(((DOT_SIZE * WORKING_SCALE * i) + (DOT_SIZE / 2 * WORKING_SCALE))) $((DOT_SIZE * WORKING_SCALE * 8 - 1)),$(((DOT_SIZE * WORKING_SCALE * i) + (DOT_SIZE / 2 * WORKING_SCALE)))"
    i=$((i + 1))
  done

  convert -size $((DOT_SIZE * WORKING_SCALE * 8))x$((DOT_SIZE * WORKING_SCALE * 8)) xc:none -background none -fill none -strokewidth $((DOT_SIZE * $WORKING_SCALE)) \
    -draw "${STROKE_ARRAY[*]}" -write mpr:grid +delete \
    -size $((IMAGE_WIDTH * WORKING_SCALE))x$((IMAGE_HEIGHT * WORKING_SCALE)) tile:mpr:grid \
    $BUILD_DIR/pin-banding-overlay.png
fi

convert $INPUT_FILE \
  -resize $((WIDTH))x$((HEIGHT)) \
  -normalize \
  -monochrome \
  -scale $((IMAGE_WIDTH * WORKING_SCALE))x$((IMAGE_HEIGHT * WORKING_SCALE)) \
  out.png;

composite $BUILD_DIR/masking_grid.png \
  out.png \
  out.png

convert out.png \
  -compose Lighten $BUILD_DIR/pin-banding-overlay.png \
  -composite -resize $((IMAGE_WIDTH))x$((IMAGE_HEIGHT)) \
  out.png

The Output


i_dont_always_print-1

How it Works


Create a grid of dots.

masking_grid_bk

Create X and Y displacement maps.

displacement_map_x

displacement_map_y

Apply displacement maps to dot grid to make it jangly and change black areas to transparent to create a mask.

masking_grid

Create a pin banding overlay to create faded ink effect.

pin-banding-overlay

Finally, apply pin banding and mask to image to make it dot matrixy.

test
out

There Are Only Dots


out-1