Button State Generator

Turn one button image into normal, hover, pressed, disabled and focus variants, as single PNGs and one packed strip.

A game button usually needs five images: normal, hover, pressed, disabled and focus. The generator derives them from one PNG with pixel operations you can read and change. Defaults: hover +10 % brightness and +8 % saturation, pressed −10 % brightness and 1 px down, disabled fully desaturated at 50 % alpha, focus a 2 px #3182f6 ring. It exports states/*.png, one packed strip and states.json with every value used. It only changes pixels that are already there; it draws no new artwork.

Drop image files hereProcess many files at once
No upload · processed on this device

How a state is computed

Each state is an ordered list of operations on the RGBA pixels. First the canvas grows if the state has an outline, then brightness adds the value × 255 to R, G and B, contrast scales around mid grey (128), saturation moves each channel away from or towards the pixel's luma (0.2126 R + 0.7152 G + 0.0722 B), an optional colour overlay is mixed in, the pixels are offset, the outline is drawn as a square dilation of the shape, and finally alpha is multiplied. Fully transparent pixels are never recoloured.

Engines expect these images in fixed slots. Godot 4's TextureButton has texture_normal, texture_hover, texture_pressed, texture_disabled and texture_focused; a disabled button without its own texture shows the normal one, and the focused texture is drawn on top of the base texture. Unity's UI Button with Transition = Sprite Swap takes a normal sprite on its Target Graphic plus Highlighted, Pressed and Disabled sprites.

Two WCAG 2.2 rules are worth knowing even though games are not web pages. Success Criterion 1.4.3 exempts text that is part of an inactive (disabled) component from its contrast minimum, which is why a faded disabled state is acceptable. 2.4.13 Focus Appearance (level AAA) asks for a focus indicator at least as large as a 2 CSS px perimeter of the component with a 3:1 contrast between focused and unfocused pixels; the default ring is 2 px, and #3182f6 against white measures 3.71:1.

Brightness
Added to every channel: +0.1 means +25.5 levels.
Saturation
+0.08 pushes each channel 8 % further from the luma; −1 turns the pixel grey.
Offset
Moves the pixels inside the same canvas; what leaves is dropped, what enters is transparent.
Outline
A ring of the chosen colour and width around the opaque shape; the canvas grows by that width.

Example: one pixel through the five default states

A 40 × 16 button filled with the colour (200, 120, 40). The numbers are what the exported PNGs contain:

state      pixel (R, G, B, A)     size     operations
normal     (200, 120, 40, 255)    40 × 16  none
hover      (231, 145, 58, 255)    40 × 16  brightness +0.1 (+25.5), saturation +0.08
pressed    (174, 94, 14, 255)     40 × 16  brightness −0.1, offset 0,1 → row 0 becomes transparent
disabled   (131, 131, 131, 128)   40 × 16  saturation −1, alpha × 0.5
focus      (200, 120, 40, 255)    44 × 20  canvas +2 px per side, 2 px ring (49, 130, 246)

disabled grey  0.2126 × 200 + 0.7152 × 120 + 0.0722 × 40 = 131.2 → 131;  alpha 255 × 0.5 = 127.5 → 128
hover green    luma 156.7 + (145.5 − 156.7) × 1.08 = 144.6 → 145

The focus image is 4 px wider and taller than the others, and its ring covers the corner pixels too, because the outline is a square dilation. states.json records each state's size and padding so the difference is never a surprise.

Use the states in Godot or Unity

  1. Godot 4: add a TextureButton and assign normal.png, hover.png, pressed.png and disabled.png to texture_normal, texture_hover, texture_pressed and texture_disabled.
  2. Godot 4 draws texture_focused over the base texture and its documentation recommends a partly transparent image such as an outline. focus.png contains the whole button, so for that slot erase the button inside the ring in an image editor and keep the 44 × 20 canvas.
  3. Unity UI: on the Button set Transition to Sprite Swap, put normal.png on the Target Graphic's Image and hover.png, pressed.png, disabled.png into Highlighted Sprite, Pressed Sprite and Disabled Sprite.
  4. If the button must resize, give each state PNG the same nine-slice borders in the 9-slice editor before assigning them, because the states carry no borders of their own.

Check the result

  • Every rect in states.json cuts exactly its state out of button-states.png, and states.hover.ops lists the values used.
  • normal.png is the source byte for byte (in Firefox semi-transparent pixels can differ by one alpha-rounding step).
  • Run the game and hover, press and disable the button: nothing jumps, and the pressed state moves by exactly the offset.

Troubleshooting

SymptomLikely causeHow to checkFix
The pressed image loses its bottom rowThe 1 px offset moves the pixels inside the same canvas, so the last row falls off and row 0 becomes transparentCompare the bottom edge of pressed.png with the sourceLeave one transparent row under the art, or set the offset to 0 and move the label in the engine instead
The focused button jumps or looks doubledfocus.png is larger (44 × 20 for a 40 × 16 button) and Godot draws the focus texture over the basestates.json → states.focus.sizeCentre it by −outline px, or use a ring-only overlay as described above
The disabled button almost vanishes on a dark backgroundAlpha 0.5 lets the background show throughLook at it over your real background, not on whiteRaise the disabled alpha (0.7) or keep alpha 1 and only desaturate
Hover is barely visible on a light buttonBrightness is additive and channels already near 255 clipPixel values above about 230 in the sourceUse an overlay colour or contrast for hover instead of brightness

Other ways to do it

OptionWhen it is the better choice
Unity Transition = Color TintThe states only differ in colour: no extra images, but a tint multiplies colours and cannot move pixels (pressed offset) or add a ring.
Draw each state by hand in an art programThe states differ in shape: a bevel that flips, an icon that changes, a glow. Pixel arithmetic cannot draw what is not in the source.

What this page does not cover

  • No per-state nine-slice borders: set them on the state PNGs in the 9-slice editor.
  • Engine use was not tested; the slot names above come from the Godot and Unity documentation.

Versions and sources

Measured in Chromium for docs/UI-LAB.md on a 40 × 16 button: normal is byte-identical to the source, hover is brighter, pressed is offset with the vacated rows cleared, disabled is grey at alpha 128, focus is 44 × 20 with a #3182f6 ring and untouched artwork, and all five rects in states.json cut exactly their state out of the strip. The example values above were recomputed with the same module. Godot and Unity import were not run.

Official documentation used

Button State Generator

Turn one button image into normal, hover, pressed, disabled and focus variants, as single PNGs and one packed strip.

How it works

  1. Add images
  2. Adjust settings and review the result
  3. Download a new file

Supported formats

PNG / JPG / WebP / AVIF / HEIC → PNG + JSON (ZIP) — Input decoding depends on browser support and optional engines.

What this tool does

Turn one button image into normal, hover, pressed, disabled and focus variants, as single PNGs and one packed strip.

Implementation and verified quality

advanced · brightness, contrast, saturation, overlay, offset and square outline ops / MaxRects strip. Quality qualification is in progress. Pro/Flagship quality has not been established.

Before you export

Fixed pixel operations: brightness, contrast, saturation, colour overlay, offset and outline. It does not draw artwork that is not there.

Frequently asked questions

What are this tool’s limitations?

Fixed pixel operations: brightness, contrast, saturation, colour overlay, offset and outline. It does not draw artwork that is not there.

Are my files uploaded?

This tool processes the files you open on your device without uploading them. Images you explicitly attach to community posts are uploaded separately. Optional engines and models are downloaded from third parties; those requests expose connection information such as your IP address.

Will my work be saved automatically?

No. Images and editing state stay in this tab; media output can use local temporary storage. Changing language preserves your work; reloading, closing the tab, or following a link to another page can clear it.