Search code examples
imagemagickimagemagick-convert

Replace only background color of PNG


Here is my code:

#! /usr/bin/env sh
# Generate test image.
convert -size 100x60 xc:blue -fill blue -stroke black -draw "circle 50,30 55,55" in.png

# Make background transparent.
convert in.png -fill none -draw 'matte 0,0 floodfill' -flop  -draw 'matte 0,0 floodfill' -flop out.png
# Replace transparent background with green.
mogrify -background green -flatten out.png

# The wrong way.
convert in.png -transparent blue oops.png
mogrify -background green -flatten oops.png

It is based on this snippet: https://snippets.aktagon.com/snippets/558-how-to-remove-a-background-with-imagemagick

Starting with this:

Black circle filled with blue, blue background

I want to get this:

Black circle filled with blue, green background

Not this:

Black circle filled with green, green background

Can I achieve this with a single convert command instead of a convert followed by a mogrify?

I am using ImageMagick 6.8.9-9.


Solution

  • Essentially, you are seeking a "floodfill", like this:

    convert in.png -fill green  -draw 'color 0,0 floodfill' result.png
    

    enter image description here

    That will look at the top-left pixel (0,0) and fill all similarly coloured pixels which are connected to it with green. If your background has slight variations in it, e.g. it's a JPEG, add some fuzz factor

    convert in.jpg -fuzz 25% ...
    

    Note that if your circle had touched the top and bottom edges, it would prevent the fill from flooding around to the right side of the diagram. So, let's say you had created your circle like this:

    convert -size 100x60 xc:blue -fill blue -stroke black -draw "circle 50,30 50,0" in.png
    

    enter image description here

    And then you run the above command, you will get:

    enter image description here

    If that happens, you can add a single pixel wide border all the way around for the colour to "flow" through first, then flood-fill, and finally remove it later:

    convert in.png -bordercolor blue -border 1 -fill green  -draw 'color 0,0 floodfill' -shave 1x1 result.png