対象実装: arm/v1/src/os_lib/web_engine/alpha_blend.rs
単体試験: arm/v1/tests/src/test_alpha_blend.rs
典拠(2026-07-28、ユーザ許可のうえ参照): - CSS Color Module Level 3 — https://www.w3.org/TR/css-color-3/#alphavaluedt - 合成アルゴリズムは SVG 1.1 §14.2 “Simple alpha compositing” に委譲されている
www.sugi-lab.net 再現中、ヒーロー中央に不透明な黒帯が出た。実データ:
[HERO][OVERPAINT] id="_ta_49" z=5 rect=(0,149,924,231) bg=0x33000000
0x33000000 は
.hero-overlay { background: rgba(0,0,0,.2) } に対応し、
アルファ 0x33
は正しくパースされている。にもかかわらず描画側が
アルファを無視して不透明色として塗っていた。
背景(backdrop)が不透明な画面バッファである本実装では、 結果アルファは常に 1 となり、各チャンネルは次で求まる:
result = src * alpha + dst * (1 - alpha)
src … 描画しようとしている色のチャンネル値dst … その位置に既にあるピクセルのチャンネル値alpha … 0.0〜1.0 にクランプ済みのアルファ8bit 整数で扱う場合、alpha8 = 0..=255 として
result = (src * alpha8 + dst * (255 - alpha8) + 127) / 255
(+127
は四捨五入。切り捨てのみだと合成を繰り返すたびに暗くなる。)
alpha = 0 なら結果は
dst と完全に一致する(何も描かない)。alpha = 255 なら結果は
src と完全に一致する(不透明描画と同じ)。src と
dst の間に必ず収まる(オーバーシュートしない)。Err
を返す(呼び出し側は不透明扱いへフォールバック)。mix-blend-mode / background-blend-mode
は未対応(source-over のみ)。Screen::draw_pixel では アルファ 0x00
を「完全透明」ではなく「不透明」として扱う。
既存コードの大半はアルファを設定せず 0x00RRGGBB
のまま色を渡しており、 仕様どおり 0x00
を完全透明にすると画面全体が消えてしまうため。 実際に合成するのは
0x01〜0xFE のみ。
これは仕様違反であり、恒久対応としては全描画呼び出しが明示的に
0xFF
を設定するよう是正したうえで、この妥協を撤廃すべき。spec/TODO.md
参照。
導入直後、ナビゲーションバーの右半分が黒くなる不具合が出た。 重なる要素を全列挙して実測した結果:
[NAVBG] id="_ta_19" z=1000 d=6 rect=(462,9,924,25) bg=0x00000000 ← .nav-list
.nav-list の背景は
transparent(=rgba(0,0,0,0))で、
parse_color はこれを 0x00000000
として正しく返していた。 ところが上記の妥協によりアルファ 0x00
が不透明扱いとなり、
不透明な黒として右半分を塗り潰していた。
対処:
extract_color(layout.rs)で、アルファ 0
の色は None(=背景なし)を返す。
背景色として完全透明は「背景を塗らない」と等価であり、意味的にも正しい。
parse_color は不透明色に必ず 0xFF を立てるため、アルファ 0
は transparent / rgba(...,0)
の明示指定だけを意味し、この判定は安全。
教訓:
「既存コードが壊れないように」と入れた互換妥協は、
別の正しい入力を壊す。妥協を入れるときは、それが誤って拾う入力
(ここでは
transparent)を洗い出して先に手当てすべきだった。
border-color: transparent(0x00000000)を持つ要素が、
黒い枠として描かれていた。
セクション見出しの周りの「角丸の輪郭」と、 #apps
の一覧にある 20×20 の ☐
は、いずれも実サイトに存在しない。 要素ダンプで
bd=Some(0) = 完全透明のボーダーだった。
boxfill はアルファで経路を分けていた:
let a = (color.0 >> 24) & 0xFF;
if a != 0 && a != 0xFF { /* 1 ピクセルずつ合成 */ }
// a == 0 は一括書き込みへ落ちる → 0x00000000 がそのまま入るフレームバッファにアルファは無いので、 書き込まれた
0x00000000 は不透明な黒になる。
draw_pixel の方は意図的にアルファ 0
を不透明として扱っている
(コメントに明記。下位のほぼ全ての呼び出しがアルファを設定せず 0
のまま色を渡すため、仕様どおり透明にすると画面が消える)。
boxfill: アルファ 0
なら何も描かずに返すdraw_individual_borders:
ボーダー色が全て透明なら描画しない (幅はレイアウトに残す。CSS
では透明ボーダーも場所を占める)結果: 見出しを囲っていた黒い枠が消えた。
角の跡と一覧の ☐ が残っている。 これらは draw_pixel
を直接使う角丸ボーダーの毎ピクセル経路で、
そこにはまだ透明判定が入っていない。
draw_pixel 自体は互換上変えられないので、
呼び出し側(角丸ボーダー描画)で透明を弾く必要がある。