Image Processing
Alpha Transparency
Outline Generation
Stroke Algorithm
Graphic Design

Algorithm for drawing an outline or stroke around any alpha transparent image

ML System Design practice on Codemia

Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.

Practice ML system design

Introduction

To draw an outline around a transparent image, you should treat the alpha channel as the true shape of the object. The most reliable algorithm is to expand that alpha mask outward, subtract the original mask, and then composite the colored ring underneath the source image.

The Core Idea: Outline the Alpha Mask

An image with transparency already tells you which pixels belong to the visible object. Fully transparent pixels are background, opaque pixels are part of the object, and partially transparent pixels describe soft edges.

Instead of trying to detect visual edges from RGB values, work from the alpha channel directly:

  1. Extract the alpha channel.
  2. Grow the alpha mask outward by the stroke width.
  3. Subtract the original mask from the grown mask.
  4. Fill the remaining ring with the outline color.
  5. Composite the original image over that outline.

This works on arbitrary shapes because it follows the actual silhouette, not a rectangle around the image.

Why Dilation Works

The “grow the mask” step is a standard image-processing operation called dilation. If a pixel is near an opaque pixel, dilation turns it on. Repeating that process or using a larger structuring element creates a thicker border.

If expanded_mask is the dilated alpha and original_mask is the original alpha, then the outline area is approximately:

text
outline = expanded_mask - original_mask

That subtraction leaves only the outside ring. You can then tint that ring with any color and place the original image on top.

A Runnable Pillow Example

Python and Pillow make this straightforward. The example below loads a PNG, generates a black outline, and saves the result.

python
1from PIL import Image, ImageChops, ImageFilter
2
3def add_outline(input_path, output_path, outline_color=(0, 0, 0, 255), width=6):
4    image = Image.open(input_path).convert("RGBA")
5
6    alpha = image.getchannel("A")
7
8    # Expand the alpha mask by using a max filter.
9    expanded = alpha.filter(ImageFilter.MaxFilter(size=width * 2 + 1))
10
11    # Keep only the ring outside the original shape.
12    outline_mask = ImageChops.subtract(expanded, alpha)
13
14    outline_layer = Image.new("RGBA", image.size, outline_color)
15    outline_layer.putalpha(outline_mask)
16
17    result = Image.alpha_composite(outline_layer, image)
18    result.save(output_path)
19
20add_outline("sprite.png", "sprite_outlined.png", outline_color=(255, 255, 255, 255), width=4)

ImageFilter.MaxFilter behaves like a simple dilation for this use case. The alpha values spread outward, and the subtraction step isolates the stroke region.

Handling Soft Edges and Anti-Aliasing

Alpha-transparent artwork often contains partially transparent edge pixels for anti-aliasing. That is a good thing. If you operate on the alpha mask instead of a hard binary edge map, the final outline usually looks smoother.

For icons or game sprites, you may want a crisp outline. In that case, threshold the alpha channel first:

python
1from PIL import Image
2
3def threshold_alpha(alpha_image, cutoff=1):
4    return alpha_image.point(lambda p: 255 if p >= cutoff else 0)

You can threshold before dilation if you want a harder silhouette. For painted artwork, keeping the original soft alpha often produces a more natural result.

Controlling Stroke Placement

The algorithm above creates an outer stroke. That is what most users expect, because it preserves the visible content of the original image.

If you want a centered stroke, things get trickier because part of the outline must overlap the original object. A common approximation is:

  1. Dilate the mask outward.
  2. Erode the mask inward.
  3. Use the difference between them as the stroke band.

For many UI and sprite workflows, an outer stroke is simpler and avoids eating into the original art.

A Simple Numpy Version

If you are working in a custom rendering pipeline, the same idea applies outside Pillow. The following sketch shows the logic using arrays:

python
1import numpy as np
2
3alpha = np.array(
4    [
5        [0, 0, 0, 0, 0],
6        [0, 1, 1, 0, 0],
7        [0, 1, 1, 0, 0],
8        [0, 0, 0, 0, 0],
9    ],
10    dtype=np.uint8,
11)
12
13# expanded would usually come from a dilation step
14expanded = np.array(
15    [
16        [0, 1, 1, 1, 0],
17        [1, 1, 1, 1, 0],
18        [1, 1, 1, 1, 0],
19        [0, 1, 1, 1, 0],
20    ],
21    dtype=np.uint8,
22)
23
24outline = np.clip(expanded - alpha, 0, 1)
25print(outline)

The important part is not the exact library. The important part is that the outline comes from geometric operations on the alpha mask.

Common Pitfalls

One common mistake is detecting edges from RGB colors while ignoring transparency. That fails on sprites with transparent padding or internal color changes. Start from alpha instead.

Another issue is forgetting to add padding around the image before expanding the mask. If the source object touches the canvas edge, the stroke can get clipped. Allocate a slightly larger canvas when necessary.

A third problem is using a hard threshold on artwork that depends on soft transparency. That can make the outline jagged. Choose binary masks for crisp assets and grayscale alpha for smoother assets.

Summary

  • The cleanest outline algorithm works from the alpha channel, not from RGB edges.
  • Create the stroke by dilating the alpha mask and subtracting the original mask.
  • Composite the colored outline underneath the original RGBA image.
  • Threshold the alpha only when you want a harder, more pixelated edge.
  • Add canvas padding if the object touches the border and the stroke gets clipped.

Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
ML System Design practice on Codemia

Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.

Practice ML system design