8000 Return sRGB from get_pixel_color instead of raw linear values by floraschouw · Pull Request #55 · gimp-mcp/gimp-mcp · GitHub
[go: up one dir, main page]

Skip to content

Return sRGB from get_pixel_color instead of raw linear values - #55

Open
floraschouw wants to merge 1 commit into
gimp-mcp:mainfrom
floraschouw:fix/pixel-color-srgb
Open

floraschouw wants to merge 1 commit into
gimp-mcp:mainfrom
floraschouw:fix/pixel-color-srgb

Conversation

@floraschouw
Copy link
Copy Markdown

Fixes #43.

Cause

Gegl stores colour in linear light, so Gegl.Color.get_rgba() returns linear floats. _get_pixel_color scaled those straight by 255 and labelled the result sRGB. Paint #ff5733, read the same pixel back, get #ff1808.

Checked against babl on GIMP 3.2.4, which converts the same colours correctly:

colour painted get_rgba()*255 (old) R'G'B'A u8 (babl)
#ff5733 255, 24, 8 255, 87, 51
#6495ed 32, 76, 215 100, 149, 237
#28b45a 5, 116, 26 40, 180, 90
#808080 55, 55, 55 128, 128, 128

Why this does not call babl

My first attempt resolved babl's R'G'B'A u8 format at module level. That breaks the plug-in completely, and the failure is worth recording because it is not a normal exception:

babl_format("R'G'B'A u8"): you must call babl_init first

babl_format() calls babl_fatal() when babl is not yet initialised, which aborts the process at the C level. It cannot be caught from Python -- except Exception does not see it. GIMP imports plug-in files during its plug-in scan, before that initialisation, so the plug-in never registers and the whole Tools > MCP menu disappears with it.

So the conversion uses the sRGB transfer function directly. For 8-bit output it is exact: it round-trips all 256 levels and matches babl on every colour in the table above.

The trade-off is that it assumes an sRGB working space, where babl would honour the image's actual colour space. The previous code assumed sRGB too and got it wrong even for that, so this is strictly better; doing it properly would mean resolving babl lazily from inside a handler rather than at import.

Two smaller defects in the same two lines

  • int(rf*255) truncated instead of rounding, losing a level independently of the gamma bug: #6495ed came back as (32, 76, 215) where correct rounding gives (32, 77, 216) before conversion.
  • Nothing clamped, so an out-of-gamut or HDR channel above 1.0 could report a value above 255.

Alpha is deliberately excluded from the transfer function; it is stored linearly and takes no gamma encoding.

Verification

Against a running GIMP 3.2.4 on Windows 10 -- paint, then read the same pixel back:

painted read back before read back after
#ff5733 #ff1808 #ff5733
cornflowerblue #204cd7 #6495ed
rgb(40,180,90) #05741a #28b45a
#808080 #373737 #808080
#000000 #000000 #000000

Every colour now round-trips exactly, including the mid-grey that is most sensitive to the transfer function.

python -m py_compile passes and ruff check is unchanged at 183 findings. The module also imports cleanly under GIMP's own Python 3.14, which is the check that catches the babl abort described above.

Gegl stores colour in linear light, so Gegl.Color.get_rgba() returns linear
floats. _get_pixel_color scaled those straight by 255, producing linear
values labelled as sRGB. Painting #ff5733 and reading the same pixel back
returned #ff1808.

Checked against babl on GIMP 3.2.4, which converts the same colours right:

    colour     get_rgba()*255 (old)   R'G'B'A u8 (babl)
    #ff5733    255,  24,   8          255,  87,  51
    #6495ed     32,  76, 215          100, 149, 237
    #28b45a      5, 116,  26           40, 180,  90
    #808080     55,  55,  55          128, 128, 128

The conversion is done with the sRGB transfer function rather than by
calling babl. babl cannot be reached safely from this module: if it is not
yet initialised, babl_format() calls babl_fatal() -- "you must call
babl_init first" -- which aborts the process at the C level and cannot be
caught from Python. GIMP imports this file during its plug-in scan, before
that initialisation, so a module-level Babl.format() makes the entire
plug-in fail to register and disappear from the Tools menu.

The transfer function is exact for 8-bit output: it round-trips all 256
levels and matches babl on every colour above. It assumes sRGB, so a non-
sRGB working space would need babl (resolved lazily, from inside a handler);
the previous code assumed sRGB too, and got it wrong even for that.

Two smaller defects went with it:

- int(rf*255) truncated instead of rounding, losing a level on its own:
  #6495ed came back as (32, 76, 215) where the correct answer is
  (32, 77, 216) before conversion.
- Nothing clamped, so an out-of-gamut or HDR channel above 1.0 could report
  a value greater than 255.

Alpha is deliberately left out of the transfer function; it is stored
linearly and takes no gamma encoding.

Fixes gimp-mcp#43

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai
coderabbitai Bot commented Sep 1, 2026
Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 38 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 2 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: 432eace2-e5a3-44b7-b1a9-f412f2753767

📥 Commits

Reviewing files that changed from the base of the PR and between 09bfb2d and 7a91b65.

📒 Files selected for processing (1)
  • gimp-mcp-plugin.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

get_pixel_color returns severely darkened RGB values vs. the color actually painted (gamma/color-space bug)

2 participants

0