feat: true no-X headless --render (CPU spectrogram bitmap + overlay)

Rewrite --render to compute the spectrogram and write a PNG entirely on the
CPU, with no window, no GL context, and no X server. Previously it opened a
hidden GL window and grabbed LoadImageFromScreen(), which still required an X
server (Xvfb); the output was a UI screenshot rather than the spectrogram data.

The new path (RunHeadlessRender) loads the WAV, computes the STFT, colorizes
the bitmap at native STFT resolution, bakes the mLnL annotation overlay onto
it, and exports — all CPU-only. render.c gains a GL-free colorize
(BuildSpectrogramImageCPU), a CPU font loader (LoadFontCPU), and a CPU overlay
drawer (DrawAnnotationsToImage).

Annotations draw outline + label only: mLnL captures contain many overlapping
full-band boxes whose translucent fills alpha-stack to opaque and bury the
signal. The outline marks each region while the spectrogram reads through; a
dark backing strip keeps labels legible. Note: MeasureTextEx/ImageText* bail
when font.texture.id == 0, so the CPU font sets a sentinel non-zero id (the
draw path reads glyph images, never the texture).

Render options: --annotation-opacity (overlay strength), --annotation-kinds
(comma-separated kind filter), --width (resize; default native). Removed the
obsolete --pane/--height window options and the screenshot workaround.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-03 22:37:38 -07:00
parent 95be6f6c22
commit fb7bc5486e
5 changed files with 378 additions and 120 deletions
+10
View File
@@ -20,6 +20,16 @@ const char* ColormapName(ColormapType type);
void GenerateSpectrogramTexture(StftResult* stft, Image* image, Texture2D* texture);
void ColorizeSpectrogram(Image* image, Texture2D* texture);
// --- Headless (no-GL) spectrogram + annotation rendering ---
// BuildSpectrogramImageCPU fills `image` with the colorized spectrogram with no
// texture upload, so it runs with no window / no X server. LoadFontCPU returns
// a Font usable by ImageDrawTextEx without a GL context (glyphCount==0 on
// failure). DrawAnnotationsToImage paints the mLnL overlay onto a full-
// resolution spectrogram Image, honoring the kind filter and resting opacity.
void BuildSpectrogramImageCPU(StftResult* stft, Image* image);
Font LoadFontCPU(const char* path, int baseSize);
void DrawAnnotationsToImage(Image* img, Font font);
// --- On-screen drawing (operate on the global app state) ---
void DrawSpectrogramGrid(Rectangle bounds, int numCellsX, int numCellsY, Color color);
void DrawLabels(Rectangle bounds);