mirror of
https://github.com/tiennm99/claude-code-usage-bubble.git
synced 2026-08-05 18:23:55 +00:00
fix(bubble): restore alpha after GDI text so glyphs aren't transparent
User report on v0.1.14: text appears semi-transparent, desktop wallpaper
bleeds through glyph pixels.
Root cause: GDI's DrawTextW writes only RGB into 32bpp BI_RGB DIBs — the
"reserved" alpha byte (byte 3) is not preserved per the BITMAPINFOHEADER
contract. When UpdateLayeredWindow later composites with AC_SRC_ALPHA, it
reads alpha=0 at every glyph pixel and shows them as fully transparent.
The pre-v0.1.13 pipeline worked around this with an apply_alpha_mask
post-pass that OR'd 0xFF000000 into every pixel inside the rounded rect.
The stadium-shape rewrite (526786b) removed it on the false assumption
that tiny-skia's per-pixel alpha would "stick" through subsequent GDI
writes — but GDI runs *after* tiny-skia in the pipeline, so any pixel
GDI text writes to loses the alpha that tiny-skia set.
Fix: re-stamp the alpha channel from the original Pixmap after the GDI
text overlay. This restores tiny-skia's exact alpha values (255 in the
stadium interior, partial on the AA curved perimeter, 0 outside),
including the AA fade at the stadium's rounded ends.
Implementation:
- new helper `restore_alpha_from_pixmap(pixmap, dst)` next to the
existing `copy_pixmap_to_dib`
- hoist `pixmap` out of the if-let arm in render() so it survives until
after `paint_bubble_text`
- call `restore_alpha_from_pixmap` post-text
Two parallel reviewers (debugger + code-reviewer) converged on the same
diagnosis; the debugger preferred this approach for its simplicity and
because it's robust to any GDI behavior (whether alpha is zeroed,
untouched, or scribbled on, we overwrite with the known-good value).
Build clean.
This commit is contained in:
+28
-2
@@ -1226,6 +1226,19 @@ fn copy_pixmap_to_dib(pixmap: &Pixmap, dst: &mut [u32]) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Re-stamp the alpha byte of every DIB pixel from the source `Pixmap`. Used
|
||||
/// after GDI text rendering, which writes RGB but leaves the BI_RGB DIB's
|
||||
/// "reserved" alpha byte at zero — making glyph pixels appear transparent
|
||||
/// when `UpdateLayeredWindow` composites with `AC_SRC_ALPHA`.
|
||||
fn restore_alpha_from_pixmap(pixmap: &Pixmap, dst: &mut [u32]) {
|
||||
let src = pixmap.data();
|
||||
let pixel_count = (pixmap.width() * pixmap.height()) as usize;
|
||||
for i in 0..pixel_count {
|
||||
let a = src[i * 4 + 3] as u32;
|
||||
dst[i] = (dst[i] & 0x00FF_FFFF) | (a << 24);
|
||||
}
|
||||
}
|
||||
|
||||
fn measure_text_w(hdc: HDC, text: &str, font_height_px: i32) -> i32 {
|
||||
use windows::Win32::Foundation::SIZE;
|
||||
let font_name = wide_str("Segoe UI");
|
||||
@@ -1326,12 +1339,25 @@ fn render(hwnd: HWND) {
|
||||
|
||||
// Paint shape via tiny-skia (AA), then copy into the DIB. GDI text
|
||||
// overlays on top of the resulting bitmap.
|
||||
if let Some(pixmap) = paint_bubble_pixmap(&layout, &inputs) {
|
||||
copy_pixmap_to_dib(&pixmap, pixels);
|
||||
let pixmap_opt = paint_bubble_pixmap(&layout, &inputs);
|
||||
if let Some(ref pixmap) = pixmap_opt {
|
||||
copy_pixmap_to_dib(pixmap, pixels);
|
||||
} else {
|
||||
pixels.fill(0);
|
||||
}
|
||||
paint_bubble_text(mem_dc, &layout, &inputs);
|
||||
// GDI text writes RGB into the 32bpp BI_RGB DIB but does not preserve
|
||||
// the alpha byte (per the BITMAPINFOHEADER contract: byte 3 is
|
||||
// "reserved/0" for BI_RGB). UpdateLayeredWindow with AC_SRC_ALPHA then
|
||||
// reads those zeroed alpha bytes and paints the glyph pixels as fully
|
||||
// transparent — desktop bleeds through. Fix: re-stamp the alpha
|
||||
// channel from the tiny-skia Pixmap we still have in scope. This
|
||||
// preserves the AA alpha on the stadium's curved perimeter and forces
|
||||
// glyph pixels back to the opacity tiny-skia computed for that
|
||||
// location (255 in the interior, 0 outside).
|
||||
if let Some(ref pixmap) = pixmap_opt {
|
||||
restore_alpha_from_pixmap(pixmap, pixels);
|
||||
}
|
||||
|
||||
let mut wr = RECT::default();
|
||||
let _ = GetWindowRect(hwnd, &mut wr);
|
||||
|
||||
Reference in New Issue
Block a user