Problem
utilities_get_rgba_for_xy() in src/utilities/utilities_paths.py calls Gdk.pixbuf_get_from_surface(surface, x, y, 1, 1) to read a single pixel. This allocates a full GdkPixbuf GObject on the heap for every call.
utilities_get_magic_path() (used by the paint bucket / magic wand) calls this function up to 50,000 times per operation, making GObject allocation the dominant cost.
Proposed Fix
Replace the GdkPixbuf allocation with a direct read from the Cairo surface pixel buffer via surface.get_data(), handling the BGRA→RGBA conversion and alpha un-premultiplication in Python.
Cache the buffer once in utilities_get_magic_path() instead of re-acquiring it per pixel.
Measured Impact
Benchmarked on Python 3.12.3 / Cairo 1.18.0 / GTK 3 / Linux:
| Scenario |
Before |
After |
Speedup |
| Single pixel read (median) |
3.5 μs |
0.78 μs |
4.5× |
| 10,000 consecutive reads |
36.4 ms |
7.1 ms |
5.1× |
| Paint bucket worst case (50K reads, projected) |
~180 ms |
~36 ms |
~5× |
Also fixes an off-by-one in the bounds check (x > width → x >= width).
Affected Tools
- Paint bucket / magic wand (heaviest user — 50K calls per fill)
- Color picker (per mouse-move)
- Eraser color comparison
Problem
utilities_get_rgba_for_xy()insrc/utilities/utilities_paths.pycallsGdk.pixbuf_get_from_surface(surface, x, y, 1, 1)to read a single pixel. This allocates a full GdkPixbuf GObject on the heap for every call.utilities_get_magic_path()(used by the paint bucket / magic wand) calls this function up to 50,000 times per operation, making GObject allocation the dominant cost.Proposed Fix
Replace the GdkPixbuf allocation with a direct read from the Cairo surface pixel buffer via
surface.get_data(), handling the BGRA→RGBA conversion and alpha un-premultiplication in Python.Cache the buffer once in
utilities_get_magic_path()instead of re-acquiring it per pixel.Measured Impact
Benchmarked on Python 3.12.3 / Cairo 1.18.0 / GTK 3 / Linux:
Also fixes an off-by-one in the bounds check (
x > width→x >= width).Affected Tools