Quick answer: JPEG is lossy — pixels near a colorkey background drift slightly off the key color and survive set_colorkey, leaving a halo. Use PNG with a real alpha channel for sprites.
A character sprite loaded from .jpg has a magenta backdrop; set_colorkey((255, 0, 255)) hides most of it but leaves a pink halo around the silhouette.
JPEG Smears Colors
JPEG compression averages neighboring pixels — pure magenta next to skin becomes “mostly magenta with hints of pink”. The exact key color isn’t a hard edge anymore, and set_colorkey is binary.
Use PNG
Re-export the source as PNG with a transparent alpha channel. image.convert_alpha() on load — no colorkey needed, no halo possible.
sprite = pygame.image.load("hero.png").convert_alpha()
If You Must Keep JPEG
Use set_colorkey with a tolerance via per-pixel processing — iterate with surfarray and set alpha based on distance to the key color. Slower at load and still imperfect; PNG is the right answer.
Premultiplied Alpha
For sprites with anti-aliased edges, premultiplied alpha (also a PNG technique) gives the cleanest blending in Pygame. Worth the workflow change for hero art.
Verifying
The sprite renders with crisp silhouette — no pink halo, no fringe color. The art pipeline is now PNG → load → convert_alpha.
“JPEG and colorkey transparency don’t mix. Re-export sprites as PNG with alpha.”
Reserve JPEG for big lossy backdrops where nothing is keyed — characters, UI, and effects always go PNG.