Context
cleopatra is the ecosystem's render engine, but there is currently no shared primitive for alpha compositing
— stacking a semi-transparent image over another (the Porter-Duff "over" operator). As a result the same small
formula is being written independently in higher layers:
pyramids-eo implements it privately in composites/overlay.py (alpha_overlay) for its day/night composite.
digital-earth assembles RGB/HSV composites (scene/maps/raster.py) and will need the same "over" math.
These copies will drift over time (edge cases: RGBA-over-RGBA premultiplication, divide-by-zero on zero output
alpha). Since "over" is a pure drawing operation with no GIS/EO knowledge, cleopatra — the leaf render package
everything else depends on — is its natural home.
Problem / Current Behaviour
cleopatra has no array-level compositing helper. glyphs/base/ holds glyph, animation, and hillshade, but
nothing for combining two image arrays by alpha. Downstream packages therefore reinvent it.
Affected locations
| File |
Symbol |
Notes |
src/cleopatra/glyphs/base/ |
— |
new compositing.py module goes here |
src/cleopatra/glyphs/base/__init__.py |
module docstring |
list the new submodule |
Steps to Reproduce / Motivation Example
# Desired: one shared primitive any glyph or downstream compositor can call.
import numpy as np
from cleopatra.glyphs.base.compositing import alpha_over
fg = np.array([[[1.0]], [[0.0]], [[0.0]], [[0.5]]]) # red, alpha 0.5 -> (4, H, W)
bg = np.array([[[0.0]], [[0.0]], [[1.0]]]) # blue -> (3, H, W)
alpha_over(fg, bg).round(2).ravel().tolist()
# [0.5, 0.0, 0.5] # 50/50 red-over-blue
Proposed Solution
Add src/cleopatra/glyphs/base/compositing.py with a NumPy-only alpha_over:
def alpha_over(foreground: np.ndarray, background: np.ndarray) -> np.ndarray:
"""Composite a (4, H, W) RGBA foreground over a (3|4, H, W) RGB(A) background (Porter-Duff "over").
- RGB background -> 3-band result: fg_rgb * fg_a + bg_rgb * (1 - fg_a)
- RGBA background -> 4-band result with out_a = fg_a + bg_a * (1 - fg_a),
premultiplied blend, divide-by-zero guarded where out_a == 0.
"""
Import convention follows the package (no re-export at package roots):
from cleopatra.glyphs.base.compositing import alpha_over.
Once this lands, pyramids-eo and digital-earth can delegate their array-level "over" to it (pyramids-eo keeps
only the thin Dataset-metadata wrapper, which cleopatra cannot host since it has no pyramids dependency).
Out of Scope
- Any pyramids
Dataset / geotransform-aware wrapper (stays in pyramids-eo).
- Other blend modes (multiply, screen, etc.) — this issue is only the "over" operator.
- Refactoring
digital-earth / pyramids-eo to consume it (follow-up in those repos).
Effort Estimate
Size: XS
Rationale: one small pure-NumPy function plus a focused unit test; no dependencies or API-surface changes.
Definition of Done
Context
cleopatra is the ecosystem's render engine, but there is currently no shared primitive for alpha compositing
— stacking a semi-transparent image over another (the Porter-Duff "over" operator). As a result the same small
formula is being written independently in higher layers:
pyramids-eoimplements it privately incomposites/overlay.py(alpha_overlay) for its day/night composite.digital-earthassembles RGB/HSV composites (scene/maps/raster.py) and will need the same "over" math.These copies will drift over time (edge cases: RGBA-over-RGBA premultiplication, divide-by-zero on zero output
alpha). Since "over" is a pure drawing operation with no GIS/EO knowledge, cleopatra — the leaf render package
everything else depends on — is its natural home.
Problem / Current Behaviour
cleopatra has no array-level compositing helper.
glyphs/base/holdsglyph,animation, andhillshade, butnothing for combining two image arrays by alpha. Downstream packages therefore reinvent it.
Affected locations
src/cleopatra/glyphs/base/compositing.pymodule goes heresrc/cleopatra/glyphs/base/__init__.pySteps to Reproduce / Motivation Example
Proposed Solution
Add
src/cleopatra/glyphs/base/compositing.pywith a NumPy-onlyalpha_over:Import convention follows the package (no re-export at package roots):
from cleopatra.glyphs.base.compositing import alpha_over.Once this lands,
pyramids-eoanddigital-earthcan delegate their array-level "over" to it (pyramids-eo keepsonly the thin
Dataset-metadata wrapper, which cleopatra cannot host since it has no pyramids dependency).Out of Scope
Dataset/ geotransform-aware wrapper (stays inpyramids-eo).digital-earth/pyramids-eoto consume it (follow-up in those repos).Effort Estimate
Size:
XSRationale: one small pure-NumPy function plus a focused unit test; no dependencies or API-surface changes.
Definition of Done
cleopatra.glyphs.base.compositing.alpha_overimplemented (RGB and RGBA background paths)glyphs/base/__init__.pydocstring lists the new submodule