Alpha blending
RGBA
RGB
graphics programming
image processing

Manually alpha blending an RGBA pixel with an RGB pixel

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Manual alpha blending means compositing a foreground RGBA pixel over an opaque RGB background pixel. This shows up in sprite rendering, custom image pipelines, and debugging cases where you want to verify exactly what a graphics library is doing. The math is simple once you keep three things straight: alpha range, rounding, and whether the source uses straight alpha or premultiplied alpha.

Use the Straight-Alpha Formula Per Channel

For straight alpha, each output color channel is the weighted mix of source and destination.

result = source * alpha + destination * (1 - alpha)

With 8-bit channels, alpha is usually stored from 0 to 255, so it must be normalized before blending.

python
1from typing import Tuple
2
3RGB = Tuple[int, int, int]
4RGBA = Tuple[int, int, int, int]
5
6def blend_rgba_over_rgb(src: RGBA, dst: RGB) -> RGB:
7    sr, sg, sb, sa = src
8    dr, dg, db = dst
9
10    a = sa / 255.0
11    inv = 1.0 - a
12
13    r = round(sr * a + dr * inv)
14    g = round(sg * a + dg * inv)
15    b = round(sb * a + db * inv)
16
17    return (
18        max(0, min(255, r)),
19        max(0, min(255, g)),
20        max(0, min(255, b)),
21    )
22
23print(blend_rgba_over_rgb((100, 150, 200, 128), (50, 100, 150)))

This is the core calculation you would port to C, C++, Rust, Swift, or shader code.

Integer Arithmetic Is Often Better for Tight Loops

If you are blending millions of pixels, integer math is often preferable because it avoids repeated floating-point conversion and mirrors how many image pipelines already store data.

python
1from typing import Tuple
2
3def blend_int(src: Tuple[int, int, int, int], dst: Tuple[int, int, int]) -> Tuple[int, int, int]:
4    sr, sg, sb, sa = src
5    dr, dg, db = dst
6
7    inv = 255 - sa
8
9    r = (sr * sa + dr * inv + 127) // 255
10    g = (sg * sa + dg * inv + 127) // 255
11    b = (sb * sa + db * inv + 127) // 255
12
13    return r, g, b
14
15print(blend_int((100, 150, 200, 128), (50, 100, 150)))

The extra 127 before division provides rounding instead of always truncating downward.

Straight Alpha and Premultiplied Alpha Are Different Pipelines

A large share of alpha-blending bugs come from confusing the two common representations.

  • Straight alpha stores color channels independently from alpha.
  • Premultiplied alpha stores color channels already multiplied by alpha.

If a premultiplied source is blended with the straight-alpha formula, the result often looks too dark. If a straight-alpha image is treated as premultiplied, edges may look too bright or haloed.

That is why the first debugging question should often be “what alpha representation does this asset or API use”.

Color Space Affects the Visual Result

Most quick examples assume the channels are already linear. Many real images, however, are encoded in sRGB. Blending directly in sRGB is common and often acceptable for UI or games, but it is not mathematically ideal.

For higher fidelity, the better pipeline is:

  1. convert color channels to linear space,
  2. blend there,
  3. convert back to display space.

That extra step matters most in gradients, anti-aliased edges, and high-quality compositing.

Good Test Cases Catch Most Mistakes

Small deterministic tests are enough to verify the core math.

python
1def test_cases():
2    assert blend_int((10, 20, 30, 0), (100, 110, 120)) == (100, 110, 120)
3    assert blend_int((10, 20, 30, 255), (100, 110, 120)) == (10, 20, 30)
4    assert blend_int((255, 255, 255, 128), (0, 0, 0))[0] in (128, 127)
5
6test_cases()

These checks validate the critical edge conditions:

  • fully transparent source,
  • fully opaque source,
  • midpoint alpha with expected rounding.

Common Pitfalls

  • Dividing alpha by 256 instead of 255.
  • Forgetting to clamp the result back into the 0 to 255 channel range.
  • Mixing straight-alpha and premultiplied-alpha assets in one pipeline.
  • Ignoring rounding and getting systematic darkening or brightening.
  • Assuming gamma-encoded blending and linear blending will look identical.

Summary

  • RGBA-over-RGB blending is a per-channel weighted mix based on source alpha.
  • For straight alpha, normalize the alpha channel correctly before blending.
  • Integer arithmetic is often a good choice in performance-sensitive pixel loops.
  • Always confirm whether the source uses straight or premultiplied alpha.
  • Small edge-case tests catch most compositing math bugs early.

Course illustration
Course illustration

All Rights Reserved.