fix(indexed): resolve exact colors against the sprite's own palette#22
Open
Fuitad wants to merge 1 commit into
Open
fix(indexed): resolve exact colors against the sprite's own palette#22Fuitad wants to merge 1 commit into
Fuitad wants to merge 1 commit into
Conversation
Root cause: on an indexed sprite, img:putPixel and app.useTool both expect a palette index, not an arbitrary RGBA value. Passing a Color object directly resolves correctly when the sprite's palette was set earlier in the SAME Aseprite process, but pixel-mcp runs each tool call as its own `aseprite --batch` invocation: the palette is set in one process and drawing happens in the next. In that (normal, for every real tool call) cross-process scenario, Aseprite's own Color-to-index resolution for img:putPixel/app.useTool does not reliably match the sprite's actual, correctly-loaded palette (verified by reading spr.palettes[1] back at the point of failure) and silently resolves to an unrelated index instead, corrupting the drawn color with no error. Affects draw_pixels, draw_line, draw_contour, draw_rectangle, draw_circle, and fill_area whenever use_palette is false (the default) on an indexed sprite. Fix: resolveExactPaletteColor (pkg/aseprite/lua_core.go) resolves the color itself by reading the sprite's actual active palette at Lua runtime, bypassing Aseprite's unreliable resolution entirely. - Fully-transparent colors (alpha 0, e.g. a "paint transparent" fill_area call) resolve directly to spr.transparentColor, since transparency is a designated index, not a color expected to exist in the palette. - Any other color requires an exact match in the active palette and errors if none exists, so a caller drawing an approximate/non-locked color silently gets the wrong pixel instead of an error telling them to pass use_palette=true to snap to the nearest palette color. Wired into DrawPixels (FormatColorWithPalette) and DrawLine/ DrawContour/DrawRectangle/DrawCircle/FillArea (FormatColorWithPaletteForTool). Testing: - go test ./...: 628 passed - go test -tags=integration ./...: 805 passed - Added TestIntegration_IndexedMode_ExactColorResolution (pkg/tools/exact_palette_color_indexed_mode_bug_test.go): exact match via draw_rectangle, exact match via draw_pixels, and error on no match. Verified RED without the fix (all three fail/misbehave exactly as described above) and GREEN with it. - Updated two pre-existing tests that asserted the old, buggy behavior: TestFormatColorWithPalette_NoPalette and TestLuaGenerator_DrawPixels now expect resolveExactPaletteColor(...) instead of a raw Color(...) literal. - Fixed TestIntegration_SetPalette_ThenApplyShading, which drew an approximate "mid-tone" color with use_palette=false even though that color was never one of the locked palette's 16 exact entries; it now correctly passes use_palette=true to snap it.
Fuitad
added a commit
to Fuitad/pixel-plugin
that referenced
this pull request
Jul 2, 2026
Refreshes all 5 platform binaries from Fuitad/pixel-mcp @ 7bc0829, which adds a fix for indexed-mode shape/pixel tools silently mis-drawing colors when use_palette is false (the default): draw_pixels, draw_line, draw_contour, draw_rectangle, draw_circle, and fill_area now resolve the requested color against the sprite's actual active palette directly instead of trusting Aseprite's own raw-Color resolution, which is unreliable once the palette was set in a previous process (upstream willibrandon/pixel-mcp#22). Bumps plugin version to 0.5.2 and documents the change in CHANGELOG.md.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes a second, independent indexed-color-mode bug found while using the
pixel-pluginClaude Code plugin (which bundles this server) for a game project:img:putPixel/app.useToolcalls made withuse_palette=false(exact colors, the default) silently draw the wrong pixel on an indexed sprite, with no error.Root cause
On an indexed sprite, both
img:putPixelandapp.useToolexpect a palette index, not an arbitrary RGBA value. Passing aColorobject directly resolves correctly when the sprite's palette was set earlier in the SAME Aseprite process. But every pixel-mcp tool call is its ownaseprite --batchinvocation: the palette is set in one process (set_palette) and drawing happens in the next (draw_rectangle, etc).In that cross-process scenario (the normal case for every real tool call), Aseprite's own Color-to-index resolution for
img:putPixel/app.useTooldoes not reliably match the sprite's actual, correctly-loaded palette. I confirmed this by readingspr.palettes[1]back at the point of failure: the palette is exactly right, but the index Aseprite picks for the requested RGBA is unrelated to it. The result silently corrupts the drawn color (in one repro, a raw RGBA that exactly equals the palette's index 3 resolved to index 8 instead, which was out of a 4-color palette's range and therefore read back as fully transparent).Isolated with a minimal repro directly against the
asepriteCLI (bypassing this project's Go/MCP layer): the SAME create+set-palette+draw sequence resolves the color correctly when done as three statements in one process, or when reloaded viaapp.open()within that same process, but incorrectly whenever the draw happens in a freshly-startedasepriteprocess that only opens a previously-saved file (which is exactly how--batch <file> --script ...and pixel-mcp itself always operate).Affects
draw_pixels,draw_line,draw_contour,draw_rectangle,draw_circle, andfill_areawheneveruse_paletteis false (the default) on an indexed sprite.Fix
resolveExactPaletteColor(pkg/aseprite/lua_core.go) resolves the color itself by reading the sprite's actual active palette at Lua runtime, bypassing Aseprite's unreliable resolution entirely:fill_areacall establishing a background) resolves directly tospr.transparentColor, since transparency is a designated index, not a color expected to exist in the palette.use_palette=trueto snap to the nearest palette color, instead of a silently wrong pixel.Wired into
DrawPixels(FormatColorWithPalette) andDrawLine/DrawContour/DrawRectangle/DrawCircle/FillArea(FormatColorWithPaletteForTool).Testing
Added
TestIntegration_IndexedMode_ExactColorResolution(pkg/tools/exact_palette_color_indexed_mode_bug_test.go) with three subtests: exact match viadraw_rectangle, exact match viadraw_pixels, and an error on no match. Verified RED without the fix (all three fail/misbehave exactly as described above) and GREEN with it.Updated two pre-existing unit tests that asserted the old, buggy literal output (
TestFormatColorWithPalette_NoPalette,TestLuaGenerator_DrawPixels) to expectresolveExactPaletteColor(...)instead of a rawColor(...)call. Also fixedTestIntegration_SetPalette_ThenApplyShading, which drew an approximate "mid-tone" color withuse_palette=falseeven though that color was never one of the locked palette's exact entries; it now correctly passesuse_palette=trueto snap it, which is what the test's own comment ("mid-tone") already implied.Notes
This is a separate, independent bug from the one I opened in #21 (
transparentColorcolliding with a real palette color after a resize) - unrelated root cause, unrelated code paths, no overlap in the fix. This branch is based directly onmainso it applies cleanly regardless of #21's or #20's review status.