ImageMagick v6 Examples --
Channels, Masks, and Transparency

Index
ImageMagick Examples Preface and Index
Color Channels
Alpha or Matte Channel (How IM handles Transparency)
Using Masks with Images
Transparency Masking and Background Removal
In these examples we look at the special operators provided by IM for handling the individual red, green and blue channels of an image. More importantly we also look at the matte, mask, or alpha channel that controls an image's transparency within IM.


Color Channels

Due to the way our eyes interact with the world we, as human beings, see only three colors... red, green and blue. This is why TV and monitors, if you look at the screen closely with a magnifying glass, are made up of dots of red, green and blue colors.

Because of this, images in computers most commonly are also stored as three arrays of values, called channels. Each channel handles each of the red, green and blue colors. Thus the format is commonly called RGB. By combining these colors in various ways, just about (though not quite) every color we can see can be reproduced.

Before going any further I suggest you read about how IM actually saves images in memory by reading Image Storage Color System.

Extracting Channel Images

The easiest way separating out the individual color channels is to use the "-separate" operator to extract the current contents of each channel as a gray-scale image.

  convert rose: -channel R -separate separate_red.gif
  convert rose: -channel G -separate separate_green.gif
  convert rose: -channel B -separate separate_blue.gif
[IM Output] ==> [IM Output] [IM Output] [IM Output]

Notice how the red rose is prominent in the red channel image, while it is quite dark in the blue and green channels. On the other hand the green leaves are prominent in the green channel but not the others. The white near the bottom of the image is bright in all the channels.

In IM v5 and before "-channel" was not only a setting for later image operations but also on occasion an 'image operator' that converted the specified channel into a grey scale image. Very confusing!

The IM v6 the "-separate", was created to 'separate' these two very different different tasks. The "-channel" option is only a setting that is used by later image operations, while "-separate" will extract the specified channels into separate gray-scale and fully opaque images.

As of IM v6.2.9-3, the "-separate" operator will let you separate multiple color channels according to the "-channel" setting. The number of items in the "-channel" setting will determine the number of images created (in RGBA order).

For example as the default "-channel" setting is 'RGB' the default action is to create three images, which I output below.

  convert rose: -separate separate_RGB_%d.gif
[IM Output] ==> [IM Output] [IM Output] [IM Output]

And here we convert the image to the 'CMYK' Color Space (using "-colorspace") before extracting the four color channels involved.

  convert rose: -colorspace CMYK -separate separate_CMYK_%d.gif
[IM Output] ==> [IM Output] [IM Output] [IM Output] [IM Output]

The last image (the 'Black' or 'K' channel) is especially interesting as it appears to be a negated greyscale image of the original image. However it represents the amount of 'ink' a CMYK printer should deposit on the paper, reducing the amount of color needed by the other color channels.

Note that by default the "-channel" setting does not include the special 'matte' channel (or negated alpha channel). If you want to include all channels including the 'matte' channel you will need to use "-channel ALL" channel setting, or use 'RGBA' or 'CMYKA' "-channel" settings.

You can also extract the channels of other colorspaces, for example 'HSB' (Hue, Saturation, Brilliance), which is also commonly known as HSV (Hue, Saturation, Value)...

  convert rose: -colorspace HSB -separate separate_HSB_%d.gif
[IM Output] ==> [IM Output] [IM Output] [IM Output]

Or a similar but not quite the same 'HSL' (Hue, Saturation, Lightness) representation.

  convert rose: -colorspace HSL -separate separate_HSL_%d.gif
[IM Output] ==> [IM Output] [IM Output] [IM Output]

Note that the 'Hue' channel image is a mottle of almost pure black and white colors. That is because a Hue is actually circular. That is both black and white in the above channel image are actually very close representations of a 'Red' Hue, thus producing the effect shown.

However to extract a specific channel from some of these other color systems, you will need to 'fudge' it by requesting the equivalent RGB or CMYK channel name that represents that images Color Space in memory.

For example here we convert the rose image into many different Color Spaces, and extract the 'greyscale' representation of the image in those color spaces.

  convert rose: -colorspace Gray                      channel_gray.gif
  convert rose: -colorspace CMYK -channel K -negate -separate channel_black.gif
  convert rose: -colorspace HSB  -channel B -separate channel_brilliance.gif
  convert rose: -colorspace HSL  -channel B -separate channel_lightness.gif
  convert rose: -colorspace YUV  -channel R -separate channel_luma.gif
[IM Output]
Gray
Gray
[IM Output]
Neg Black
CMYK
[IM Output]
Brilliance
HSB
[IM Output]
Lightness
HSL
[IM Output]
Luma (Y)
YUV

The names and shorthand letters used by the "-channel" setting only define the channels in terms of the three channel 'RGB' and the four channel 'CMYK'. The actual channel represented however is a merger, as such the RGB 'Green' ('G') channel and the CMYK 'Magenta' ('M') channel are actually the same thing. It is the contents of the channel that can differ, depending on the image and its Color Space representation.

As such to extract the 'Lightness' channel from the 'HSL' color space (see above), we extract either the RGB 'B' (blue) channel or the CMYK 'Y' (yellow) channel. The result however is the representation of the images Lightness channel.

Note that the channel names 'Alpha' ('A'), 'Opacity' ('O'), and 'Matte', are also aliases for the "-channel" setting referring to the images transparency information. It does not matter that an 'alpha' channel is the inverse of a 'matte' channel, it still refers to the same channel, and produces the same result.

Whether the data in that channel is acted upon a 'alpha' channel data or as a 'matte' channel data, depend of the operator. Low level channel operators like "-threshold" work on the raw 'matte' data of the channel in memory. However most higher level operators like "-fx" and "-composite" treat that data as representing 'alpha' data, for operation purposes.

Other Channel Separation Methods

There are quite a number of other methods you can use to extract a specific color channel from an image. Typically they involve setting all the other color channels to zero. Here is a quick reference...

You can just copy one channel to all the other channels, to generate the same gray-scale type of image that "-separate" generates.

  convert  rose: -fx R greyscale_red.gif
  convert  rose: -fx G greyscale_green.gif
  convert  rose: -fx B greyscale_blue.gif
[IM Output] ==> [IM Output] [IM Output] [IM Output]

This uses the slow "-fx" operator, but not need a "-channel" setting. But if you are using "-fx" for some other image processing task, you may as well use this technique at the same time.

This last method is often regarded as the 'simplest' solution to understand, and is often used in basic IM tutorials.

Other methods involve using a multitude of techniques to 'zero' out the unwanted channels. These are listed in Zeroing Color Channels below, and are usally a lot fater that the "-fx".

Combining RGB Channel Images

Once you have separated out all the image color channels, and processed them, you will also need to be able to rejoin the images back together again.

This can be done using the special list operator "-combine", which is basically exactly the reverse of "-separate".

  convert  separate_red.gif separate_green.gif separate_blue.gif \
           -combine  rose_combined.gif
[IM Output] [IM Output] [IM Output] ==> [IM Output]
A user on the ImageMagick Mailing List wanted to be able to swap the red and blue channels of an image, this makes it easy, separate the channels, swap, and re-combine.

  convert rose: -separate -swap 0,2 -combine rose_rb_swap.gif
[IM Output]

Remember the default "-channel" setting is 'RGB', and can be used to define what images channel images are being joined together. If not all the channels being combined together are defined, the other channels are set using the color values from the current "-background" setting.

You should however note that both "-combine" and "-separate" will ignore the order in which channels are defined by the "-channel". Channels will always be processed and generated in the standard 'Red,Green,Blue,Matte' channel order, for each channel set in the "-channel" setting.

As such, even if you use a "-channel BR" setting, "-combine" will expect two images, first the red then the blue. The green and alpha values will be set from the current "-background" setting values.

Combining non-RGB Channel Images

As of IM v6.4.3-7, you can also "-combine" channel images that represent other colorspaces, but you need to tell IM what colorspace the resulting image should be.

This is done by using the special "-set colorspace" operator. This basically changes the colorspace of an image in memory but without mapping the images pixel data, leaving it as is.

Once the image has been combined in the right colorspace you can use a normal "-colorspace" operator to map the pixel data back to normal RGB data.

  convert separate_HSB_?.gif  -set colorspace HSB  -combine  \
          -colorspace RGB  rose_HSB_combined.gif
[IM Output]

This method also works for CMYK images, which is often difficult to handle due to the need for a fourth color channel.

  convert separate_CMYK_?.gif  -set colorspace CMYK  -combine  \
          -colorspace RGB  rose_CMYK_combined.gif
[IM Output]

An alternative workaround (for earlier versions of IM) is to load one image and changing it so that it is in the right colorspace, After that the individual channel images can be loaded and Channel Copied into that pre-prepared image.

  convert separate_HSB_0.gif -colorspace HSB \
          separate_HSB_0.gif -compose CopyRed   -composite \
          separate_HSB_1.gif -compose CopyGreen -composite \
          separate_HSB_2.gif -compose CopyBlue  -composite \
          -colorspace RGB   rose_HSB_combined_alt.gif
[IM Output]

Of course if you used "-set colorspace" operation, the data for the first channel will already be in place, as this does not change the actual pixel data, only the way the data is interpreted.

The last example will not work for 'CMYK' images, as the 'Black' channel image does not actually contain a black channel! As such "-compose CopyBlack" will fail to find valid data to copy. I regarded this as a bug, but is currently unlikely to be fixed.


Using other colorspaces can be useful. For example here I take the built-in rose image and want to negate the lightness channel (the last channel of HSL). When finished I re-combine to build a RGB image again.


  convert rose: -colorspace HSL -separate \
          \( +clone -negate \) +swap +delete \
          -set colorspace HSL -combine \
          -colorspace RGB   rose_light_neg.gif
[IM Output]

Note that the image still has the same colors, but the brightness (lightness) of the colors were reversed, producing a weird effect. You can replace the "-negate" with your own set of operations to adjust an images brightness levels.

However as "-negate" is itself a channel controlled operator we did not have to "-separate" out the lightness channel to negate it.

  convert rose: -colorspace HSL \
          -channel B   -negate   +channel \
          -colorspace RGB rose_light_neg2.gif
[IM Output]

As you can see this simplifies things, but it may not always be practical, for the effect you want to achieve.

Generating a ColorWheel

You can also use this technique of combining images to generate specific types of images which are hard to generate in other ways. For example here we generate perfect 'HSL' colorwheel.

  convert -size 100x300 gradient: -rotate 90 \
          -distort Arc '360 -90.1 50' +repage \
          -gravity center -crop 100x100+0+0 +repage  hue_angular.png
  convert -size 100x100 xc:white                     saturation_solid.png
  convert -size 300x100 gradient: -distort Arc '360 -90 50' +repage \
          -gravity center -crop 100x100+0+0 +repage  luminence_radial.png

  convert hue_angular.png saturation_solid.png luminence_radial.png \
          -set colorspace HSL -combine   -colorspace RGB colorwheel_HSL.png

[IM Output] [IM Output] [IM Output] ==> [IM Output]

Zeroing Color Channels

Sometimes you have an image (RGB or some other colorspace) where you just want to clear or 'zero' one or two of the color channels but leave all the other channels as is.

For example, to make a greyscale image without using a RGB Gray-Scaling Techniques, you could 'zero' the Saturation channel ('G') in a HSL colorspace so as to make a gray scale image. The 'Hue' value has no meaning when saturation is zero, so you are left with a greyscale image.

The most direct technique, is often to use the Evaluate Operator to zero all the values in the unwanted channel...

  convert rose: -colorspace HSL \
          -channel G  -evaluate set 0  +channel \
          -colorspace RGB rose_grey.gif
[IM Output]

However there are many ways not so obvious ways you can do this...


  # Evaluate (fast and direct)
    -channel G -evaluate set 0 +channel

  # FX zeroing (direct simple, but slow)
    -channel G -fx 0 +channel

  # Gamma which is a miss-use of the operator, but works VERY well!
  # ( 1 = leave alone;  0 = zero channel;  -1 = maximize channel )
  # This is short, simple , needs no channel setting, but very obtuse!
    -gamma 1,0,1

  # Threshold channels to zero
    -channel G -threshold 101% +channel

  # Threshold to maximum value then negate to zero
    -channel G -threshold -1 -negate +channel

  # Multiply with an appropriate primary/secondary color
  # The color specifies the channels to preserve!  'magenta' = 'red'+'blue'
    \( +clone +level-colors magenta \) -compose multiply -composite 

  # Colorize specific channels to black
  # (0 = leave alone;   100% set from fill (black) )
    -fill black -colorize 0,100%,0

Can you think of another ways or zeroing (or maximizing) a color channel which I have not listed above? -- mail me


Matte or Alpha Channel or How IM handles Transparency

If an image has transparent or semi-transparent features, ImageMagick accommodates this by adding a fourth channel to the image. This is commonly referred to as a 'alpha' or 'matte' channel. However these terms do not mean exactly the same thing, graphically.

To make matters worse, this channel is also sometimes referred to at an image's 'transparency' or 'opacity' channel, or even the image's 'mask'. All however refer to the same, special, fourth channel of the image.

For more information see Controlling Image Transparency.

To explain the difference we need a working example image...

   convert -size 100x100 xc:none   -stroke black  -fill steelblue \
           -strokewidth 1   -draw "circle 60,60 35,35" \
           -strokewidth 2   -draw "line 10,55 85,10"      drawn.png
[IM Output]

Now as you can see this image has a lot of areas which are fully transparent. Not only that I needed to save the image using the 'PNG' image format which is one of the small number of image formats that properly understands and handles transparent and semi-transparent colors.

I can demonstrate this transparency by overlaying the image onto the IM built-in checkerboard pattern, just as I described on the Introduction Page of these examples.

    composite -compose Dst_Over -tile pattern:checkerboard \
              drawn.png  drawn_overlay.jpg
[IM Output]

Internal Matte Channel

Now internally IM saves the transparency information in a 'matte' channel, which just like the color channel is just a plain grey scale image of values which range from white, for fully-transparent (or clear), to black for fully-opaque. It is sort of like what you would get if you look at a silhouette of the original image.

Here is the normal way to 'separate' the matte layer from the image.

  convert  drawn.png   -channel matte -separate  +matte matte.png
[IM Output]

There are other ways of extracting the internal matte channel too, and some of these methods can be very complex.

  # Here is a IM version 5 way of extracting the matte (or other) channel
  # Note how this required two separate steps, and commands.
  convert drawn.png drawn.matte
  convert drawn.matte matte2.png

  # You can join those two steps in a pipeline as well...
  convert drawn.png matte:- | convert - matte3.png

For more information about handling the matte, or alpha channel see Controlling Image Transparency

Semi-Transparent Colors

You can specify semi-transparent colors directly in only two different ways.

The most common method is to use a hex value. For example here are some color specifications showing various levels of color transparency. I have displayed the generated color images on a background pattern so that you can see that pattern though the image transparency.

  convert -size 50x50    xc:'#00FF00FF'   color_hex_1.png
  convert -size 50x50    xc:'#00FF00C0'   color_hex_2.png
  convert -size 50x50    xc:'#00FF0090'   color_hex_3.png
  convert -size 50x50    xc:'#00FF0060'   color_hex_4.png
  convert -size 50x50    xc:'#00FF0030'   color_hex_5.png
  convert -size 50x50    xc:'#00FF0000'   color_hex_6.png
[IM Output] [IM Output] [IM Output] [IM Output] [IM Output] [IM Output]

Before IM v6.3.0, the last set of hex digits contained the colors transparency in the form of a 'matte' value. That is a hexadecimal '00' represented 'opaque' and 'FF' was transparent.

However after IM v6.3.0, to bring IM in line with SVG standards and other graphics packages, this value was inverted so as to represent an 'alpha' transparency value. In other words 'FF' now represented fully-opaque and '00' is fully transparent.

You can also specify colors using the special 'rgba()' color function. Where RGB values goes from 0 to 255, and the alpha channel is specified as a decimal fraction between 0.0 (transparent) to 1.0 (opaque).

  convert -size 50x50   xc:'rgba(255,0,0, 1.0)'   color_rgba_1.png
  convert -size 50x50   xc:'rgba(255,0,0, 0.8)'   color_rgba_2.png
  convert -size 50x50   xc:'rgba(255,0,0, 0.6)'   color_rgba_3.png
  convert -size 50x50   xc:'rgba(255,0,0, 0.4)'   color_rgba_4.png
  convert -size 50x50   xc:'rgba(255,0,0, 0.2)'   color_rgba_5.png
  convert -size 50x50   xc:'rgba(255,0,0, 0.0)'   color_rgba_6.png
[IM Output] [IM Output] [IM Output] [IM Output] [IM Output] [IM Output]

Before IM version 6.2.7, the 'rgba()' also used a matte value for the alpha channel value. That is a value of 0 for fully opaque and 255 for fully-transparent. This was changed as defined by the "W3C CSS3 Color Module recommendation for specifying colors", as part of IM becoming more compliant with other image standards, particularly for WWW and SVG use.

Note that a fully-transparent color while completely invisible, still has a color. However most IM operators recognise that any color that is fully-transparent, is the same and any other fully-transparent color. Because of this and the way the internal mathematics works, many operators will often replace a fully-transparent color with fully-transparent black, (also known as the special color 'none').

As a matte channel is what is used internally by IM, some operators such as "-threshold" works on those values directly. As such if you use the "-channel" setting to include the 'matte' channel in its operation (the letter 'A' in its argument), the "-threshold" operator will be applied to just the internal 'matte' values of the image.

As such one way of generating a transparent canvas from an image is...

  convert  drawn.png  -channel RGBA -threshold -1  trans_threshold.png
[IM Output]

This caused all the values, in all the channels of the image, to be set to their maximum RGBA value (as they are all larger than the threshold value of '-1'). That means that the RGB color is set to pure-white, but it has also been made fully-transparent (due to IM's use of an internal matte).

For a list of ways in which you can convert a image into a fully transparent canvas, see Transparent Canvas.

Most other operators, like "-fx" and Alpha Composition, take the stored 'matte' value and negate it before applying any operations on that value. This way it becomes a proper 'alpha' channel value, which simplifies the mathematics for that operator.

This is one reason why the "-channel" uses an 'A' letter to mean the images opacity or transparency channel, rather than a letter 'M' for matte, or 'O' or opacity (the later has the same meaning as 'A', the former is used to represent the magenta channel, for image saved in memory in CYMKA format

Extracting the Transparency Mask

The 'alpha' channel is a negative of the a 'matte' channel. In other words a value of 0 (or black) represents is full transparency or "visibly zero". While the maximum value is fully-opaque or totally visible.

If you think about this, it is more logical in a mathematical way, and therefore many IM image operations use an 'alpha' channel for their operational requirements. These include "-composite" operations, and the "-fx" operator.

When you extract a gray-scale image of an images alpha channel, (as opposed to a 'matte' channel as we did above) you will get black for fully-transparent and white for fully-opaque. Again this is basically the opposite or 'negative' to the 'matte' that is stored internally by IM.

Such an image is also known as a 'mask' of the image, as it is commonly used for this purpose.

The best way to get the transparency mask of an image is to use the Alpha Channel Control Operator.

  convert drawn.png -alpha extract mask.png
[IM Output]

There are also other ways. For example before IM version 6, the only well known method of obtaining the transparency mask of an image, was to extract a matte and then "-negate" it, using separate IM commands such as a command pipeline like this...

  convert drawn.png matte:- | convert - -negate mask_matte.png
[IM Output]

You can also use "-separate" to extract the matte, then negate it.

  convert drawn.png  -channel matte -separate +channel -negate mask_separate.png
[IM Output]

One of the more novel ways of extracting the mask from an image, is to "-colorize" the opaque parts of the image to a specific color, then "-flatten" the image.

  convert drawn.png -fill skyblue -colorize 100% \
                    -background navy -flatten     mask_colorize.png
[IM Output]

This has the advantage that you can generate a mask using any colors you like! Not just black and white. This is typically used when it is the shape of the image that is important rather than the mask itself.

Editing a Image Mask

The mask of an image is a really useful thing to have.

For example by modifying the mask, we can take a bite out of the circle! Remember "black" in a mask is transparent, while white is opaque, so all we need to do is draw black over anything we don't want visible.

  convert mask.png -fill black -draw "circle 40,80 60,60" \
          +matte  mask_bite.png
[IM Output] ==> [IM Output]

Don't forget the "+matte" operation in the above as it is vital.

A mask image must NOT have a matte or alpha channel added to it, otherwise, that channel will be used instead of the actual grey-scale color in image when using it as a mask in "composite".

As such the "+matte" operation, is required to ensure IM does not accidentally add one! The best idea is to always specify that option at the last operation on any modification you perform on the 'mask' image.

Now lets return the mask back into the original image, by replacing the original images alpha channel (or internal 'matte'). Note that the mask filename is given before the image it is being added to in the "composite" command.

  composite -compose CopyOpacity mask_bite.png drawn.png drawn_bite.png
[IM Output]  + [IM Output] ==> [IM Output]

And Presto we took a bite out of the original image.

We can also re-add a part of the image we removed, by adding white to the mask image for the parts we want to appear.

For example here I re-add part of the 'bite' I removed from the original drawing, then return the mask back into the original image.

  convert mask_bite.png -fill white \
          -draw "circle 50,70 60,60" \
          -draw "roundRectangle  78,5 98,25 5,5" \
          +matte  mask_bite2.png
  composite -compose CopyOpacity mask_bite2.png drawn.png drawn_bite2.png
[IM Output] ==> [IM Output]  + [IM Output] ==> [IM Output]

Just a word of warning about re-adding parts. In the original image the 'transparent' colored background is defined by IM as being 'fully transparent black'. That means that if we make a part of the image we haven't drawn before opaque, then it will be black, since that is the color under the image's transparency.

I did that in the above as an example.


Using Masks with Images

Masks as Colored Shapes

An alternative to just using the mask to add or re-add transparency to an image is to actually combine the mask directly with images in various ways.

For example suppose we just want to use a mask as a symbol or shape we want to overlay onto an image in various colors. For this we need a mask, which I'll extract from a special 'symbol' font.

  convert -font WebDings -pointsize 24 label:Y \
          +trim +repage  -negate   heart_mask.gif
[IM Output]

Note that I negated the label image to make it proper mask image, consisting of a white foreground (opaque) on black background (transparent).

As of IM v6.4.3-7 the simplest way to convert a grayscale mask, into a colored shape is to use the Alpha Shape operator.

  convert heart_mask.gif  -background Red -alpha Shape  heart_red.png
[IM Output]

Note the use of 'PNG' image format for generated shaped image rather than GIF so as to avoid problems with GIF Boolean Transparency.

Before this the simplist solution was to negate the alpha mask into a matte Channel Image then use Combine to generate the shaped image.

  convert heart_mask.gif -negate  \
          -background Gold  -channel A  -combine   heart_gold.png
[IM Output]

The color of the shaped mask in this case is defined by the "-background" color which Combine used to fill in the undefined channels of the new image.

An older but more complicated way is to use 'CopyOpacity' composition method to set an image's transparency to the given mask, then use Uniformly Color Tinting to color the resulting shape. This works and for a long time was the best technique to use, but is no longer recommended.

  convert heart_mask.gif \
          \( +clone \) +matte -compose CopyOpacity  -composite \
          -fill Violet  -colorize 100%    heart_violet.png
[IM Output]

Now that you have a 'shaped' image, you can just simply overlay the image on any background we want, such as the built-in rose image, using one of the many Image Layering Techniques and Alpha Composition Methods.

  convert rose: -page +2+2  heart_gold.png \
                 \( +clone -repage +7+29 \)  \
                 \( +clone -repage +52+14 \)  \
          -flatten       rose_with_love.gif
[IM Output]

This is fine if we want all our symbols the same color, but would require multiple intermediate images if we want to use multiple colors, making it impractical for overlaying lots of symbols with lots of different colors.

One way you can make multi-colored overlays is to re-color the shaped image immediately after reading in the image.

  convert rose: \(  heart_gold.png           -repage +2+2   \) \
          \( +clone -fill red    -colorize 100% -repage +7+29 \) \
          \( +clone -fill violet -colorize 100% -repage +52+14 \) \
          -flatten      rose_colored_love.gif
[IM Output]

Note we only read in one shaped image, then recolored a Clone of that image for each new 'layer' to be overlaid. For more examples of re-coloring a base image, see the whole section on Color Modifications.

See also Drawing Symbols for an alturnative method of marking specific locations in an image.

Mathematical Composition

Rather than overlaying the mask onto some background, you may only be interested in coloring the white or black parts of the mask itself. This is relatively straight forward, simply by using some Mathematical Alpha Composition Methods to change the color of the mask to match a color, tile or other image.

For example the 'Multiply' compose method will replace the white areas (multiply value of 1) with the overlay image, while leaving the black areas (multiply value of 0), black.

The 'Screen' operator is exactly the same as 'Multiply' but with the images negated so it effectively replaces the black areas of the image.

For example, lets use the larger mask image from above, to overlay a larger image generated with a tile pattern.

  convert mask_bite.png -size 100x100   tile:tile_disks.jpg \
                        -compose Multiply  -composite   compose_multiply.png
  convert mask_bite.png -size 100x100   tile:tile_water.jpg  \
                        -compose Screen    -composite   compose_screen.png
[IM Output] ==> [IM Output] [IM Output]

The 'Multiply' alpha composition method is especially useful for replacing the background of text images (black text on white background), such as generated from Postscript Documents.

Masked Alpha Composition

This is great but what if you want to replace both the white and black areas of the mask (with appropriate merger of images for anti-aliasing pixels).

This is where a special three image form of Masked Alpha Composition becomes useful. It lets you use the mask, to select and mix two different images together to generate the final result.


  convert -size 100x100   tile:tile_water.jpg  tile:tile_disks.jpg \
           mask_bite.png    -composite   compose_masked.png
[IM Output]

The first image will replace the black background parts of the mask, while the second image replaces the white foreground parts of the mask. The mask itself is given as the third image.

Remember the final size of the resulting image will come from the first, 'background' image of the above operation (black parts), so swap images and negate the mask if you want it the other way around.

And finally remember that if you use the "composite" command instead of "convert", the 'overlay' image (white parts) is given first with the 'background' image (black parts) second. In other words the first two images need to be swapped for that command.


Transparency Masking and Background Removal

One of the most common problems in image processing is mask generating from a existing fully-opaque image. Such images are commonly downloaded from the World Wide Web, or generated by programs, or in image formats that don't provide any form of transparency.

Unfortunately their is no general solution to this problem, especially when you also want to retain any semi-transparent edging to the image. Consequentially their are hundreds of ways and variations on doing this task, all dependant on the exact situation.

Closely related to image masking is transparency adjustments to match a background that an image is going to be overlaid on. This is talked about in detail as part of saving to the GIF Image File Format which only allows Boolean transparency.

Mask from a two color image.

The simplest images are naturally the simplest to generate a mask. A typical example of this image which involves just a simple color gradient, and a typical example is text images and shapes.

Under Construction

One technique for solid color on solid color is convert the text into a mask.
turning the outside color black and the inside white.

text ->  greyscale -> normalize.
Now negate if needed to make the inside white and outside black.

One automatic way is grab pixel 0,0, expand to a overlay canvas and overlay
using 'difference' .  A white pixel at 0,0 will negate the image, a black will
leave it alone!  See Compose 'Difference'...
http://www.imagemagick.org/Usage/compose/#difference

You now have a mask, which you can use to select color and or image overlays.
http://www.imagemagick.org/Usage/channels/#masks
This technique is very similar to another technique published by Dr Rick Mabry on his web site, Gradient Color Replacement. In this case he replaces colors that fall along some linear gradient, with either another gradient, or a mix of color and texture image. It is well worth a look.

Masking Simple Backgrounds (floodfill)

When an images background is a simple single solid color, you can often generate simple masks (and background removal) by just doing Replacing Colors in Images.

FUTURE: change example using a image with a simple color pattern.

For example here is a direct floodfill masking of a image with a solid color background.

  convert cyclops.png -matte -fill none -draw 'matte 0,0 floodfill' \
          cyclops_flood_1.png
[IM Output]

Well that did not work, as the floodfill 'seed' point in the top-right corner does not actually reach all parts of the image!!!

The solution to this is to enlarge the image slightly, so as to provide a path for the floodfill to reach all the outside edges of the image. However for this you need to know the color of the background.

  convert cyclops.png -bordercolor white -border 1x1 -matte \
          -fill none  -fuzz 1% -draw 'matte 1,1 floodfill' \
          -shave 1x1    cyclops_flood_2.png
[IM Output]

Of course we did not specify a very good Fuzz Factor. The problem with this is that you get a halo around the object within the image. This is because most images contain special pixels along the edges which smoothes the look of the image.

However as this image has a good black border to it, relative to the background, using a nice large fuzz setting can be used to nicely separate the image from the background.

  convert cyclops.png -bordercolor white -border 1x1 -matte \
          -fill none -fuzz 20%  -draw 'matte 0,0 floodfill' \
          -shave 1x1    cyclops_flood_3.png
[IM Output]

This technique has some problems with it. First it is an all or nothing masking of the image, producing edges that are aliased, staircase-like and often horrible looking. This is fine for the limited GIF image file format, but not very good if you plan to overlay that image onto another background.

It is also very very difficult to get every anti-aliasing edge pixel. As such if I overlay the above image on a black background, you may see some pixels that are much whiter that normal.

  convert cyclops_flood_3.png -background black -flatten \
          cyclops_flood_3_over.png
[IM Output]

Also if you do manage to use a high enough fuzz factor, you are likely to have the problem of having very little edging pixels left, or 'leaking' into the center of the image.

Finally a direct flood fill like this does not work for a background that isn't a simple single solid color.

Backgrounds with Bordered Objects

Images with a existing single color border has a distinct advantage for these methods of background removal, as the border provides a definite boundary between what is 'inside' and what is 'outside' the image, and that in turn allows use a better method of specifying the boundary of the backgound image.

That is rather than specifying what colors should be regarded as background, we can instead specify what colors mark the border of the object being masked.

Example: Floodfill using the negative of the border color and large fuzz factor... However as the border color is known, the Draw Color primitives do provide a even better way. That is by using the 'filltoborder' color replacement drawing method, we can specify the limit of the flood fill instead.
    convert color_test.png   -fill white  -bordercolor royalblue \
            -draw 'color 30,20 filltoborder'   color_filltoborder.png
You can also specify a Fuzz Factor so that colors similar to the one you specified will be thought of as a border color. Remember too small a fuzz factor and the border will leak, too large and you will start matching background colors as well.

Removing a Known Background

While removal of a simple background to a 'Boolean' mask, is relatively straight forward, things get more complicated when the background is not so simple. However if the background itself is known. you can use that to help in its removal from other images.

As of IM v6.3.4 a special Alpha Composition method was added called 'ChangeMask' which allows for the direct removal of a known background from an image.

For example here we have a unaltered background image, and one that has been overlaid by a GIF image with a simple Boolean (straight on/off) transparency. By using 'ChangeMask' we can recover that original overlaid image (if it is very different to the background).

  convert bgnd_overlaid.gif   bgnd.gif  \
            -compose ChangeMask  -composite  bgnd_removed.png
[IM Output] [IM Output] ==> [IM Output]

Basically what this does is determine how 'different' the pixels are from one image to the other, and is the difference is less than the current Fuzz Factor, then make that pixel transparent.

Only fully transparent pixels are added to the image, otherwise the original image is left as is, transparency and all.

We can simulate the operator by using the older 'Difference' composition method to generate a Comparison Difference Image...

  composite bgnd_overlaid.gif   bgnd.gif  \
            -compose Difference     bgnd_difference.png
[IM Output] ==> [IM Output] ==> [IM Output]

As you can see the difference image is black for all the unchanged parts and a mix of colors for the parts which has changed.

By separating and adding the individual color channels together and thresholding we get a mask of any difference in any channel between the two images.

  convert bgnd_difference.png  -channel RGBA  -separate +channel \
          -compose plus -background black -flatten \
          -threshold 0   bgnd_mask.png
[IM Output]

Using this mask we can set anything that has not changed to transparency.

  convert bgnd_overlaid.gif bgnd_mask.png \
          +matte -compose CopyOpacity -composite  bgnd_diff_removed.png
[IM Output]

As you can see the 'ChangeMask' composition method makes this process a lot easier.

However this only presents a 'on/off' style of background masking. It does not allow for fuzzy or anti-aliased edges, or transparent feathering of the result.

Difference Image Masking and Feathering

The above can be taken further to images that have aliased edges. as well as non-simple backgrounds.

For example, Here we have a 'Cyclops' on a white background, which we want to extract. We then generate gray-scale image of the differences between this image and the background color (as defined by top-left most pixel).

  convert cyclops.png \( +clone -fx 'p{0,0}' \) \
          -compose Difference  -composite  \
          -modulate 100,0  +matte  difference.png
[IM Output] [IM Output]

Of course this difference image is no good as a mask directly. If you did use it you will effectively make most of your image semi-transparent, instead of just the surrounding background.

However from this difference image, huge number of different transparency masks can be created, depending on exactly what you are trying to achieve.

We can adjust the above difference image to produce a mask of all pixels that are even the smallest amount different from the background color.

  convert difference.png  -threshold 0  boolean_mask.png
  convert cyclops.png  boolean_mask.png \
          +matte -compose CopyOpacity -composite \
          cyclops_boolean.png
[IM Output] [IM Output]

As you can see a boolean 'any difference' resulted in a good amount of the original background being included. This is because the original image is either 'anti-aliased' or blurred slightly with the background (in this case it was caused by the original image being resized from a JPEG format image).

This would not be a problem if the original image was itself a Boolean overlay (EG a GIF format image, overlaid on a background). In that case your result will be perfect (see the 'ChangeMask' example above).

By varying the "-threshold" you can add a 'fuzz factor' to the boolean (on/off only) mask, so as to get the mask closer to the image proper.

  convert difference.png  -threshold 15%  threshold_mask.png
  convert cyclops.png  threshold_mask.png \
          +matte -compose CopyOpacity -composite \
          cyclops_threshold.png
[IM Output] [IM Output]

Notice that the eye of the cyclops image is now also regarded as being a transparent hole!

This 'hole' highlights the biggest drawback with this whole technique. Parts of the image object which are close to the background color, or worse still, exactly matches the background, will be thought of as being the same as the background.

Of course this may be desirable in images of 'holey' object, such as a donut, but for our cyclops, a 'holey eye' is definitely a mistake.

The original 'halo' effect can also be desirable for some things like text to make it more readable when you want to overlay it again on some other 'noisy' background. You can enhance the halo effect by blurring the mask a little before applying it, so that the resulting 'halo' becomes diminished by distance.

  convert difference.png -bordercolor black -border 5 \
          -threshold 10%  -blur 0x3  halo_mask.png
  convert cyclops.png -bordercolor white -border 5   halo_mask.png \
          +matte -compose CopyOpacity -composite  cyclops_halo.png
[IM Output] [IM Output]

This 'halo' effect can be further modified by using more Histogram Adjustments on the mask image, giving you very precise control of the results for specific images.

A small amount of blurring (say "-blur 0x0.5") is actually recommended in the threshold masking, just to smooth out the edging of the mask. Of course the result will not be boolean, so don't try to save it to a GIF format image file.

Masking with Anti-Aliased Edges

The Difference Masking technique that we used above can be used with the previous FloodFill Masking technique to solve most of the problems we have seen with simpler masking techniques.

This is a complex multi-layered masking technique, but one that should produce near ideal removal of the images background. However for it to work you will need to know either the color of the background, or be able to generate that background from a known pattern, or other image.

For this example I decided to use something that was very hard to separate. A patterned letter on a textured background. On top of this I also added a shadow that I want to also be extracted.

  convert -size 70x60 xc:none -font Candice -pointsize 50 -stroke black \
          -fill black          -annotate +12+42 'A' -channel RGBA  -blur 0x3 \
          -fill tile_disks.jpg -annotate +10+40 'A' \
          tile_water.jpg  -compose DstOver -composite letter.png
[IM Output]

First we will need to generate a difference image, and lucky for us we do have a background image. Of course it will work just as well for a plain colored image as well, such as we used above in our previous difference image.

Basically by using a difference image we can remove any influence of the backgound image, as we generate the various masks needed.

  convert letter.png  tile_water.jpg -compose Difference -composite \
          -modulate 100,0  +matte -channel B -evaluate set 0 diff_mask.png
[IM Output]

Note that this time I processed the difference image slightly, by clearing out the blue channel, and producing a black-yellow difference image. This is tricky as it frees 'blue' channel to allow the generation of a clean floodfill mask, separate to the difference image itself.

Now we need to generate two masks: an outside mask of all areas that will definitely be transparent; and a mask of the definite inside of the object in the image, such that it does not contain any 'holes'. So lets flood fill the image from the outside inward, using a number of different fuzz factors.

  for fuzz in 01 03 06   28 32 34; do \
    convert diff_mask.png -fill blue -fuzz $fuzz% \
            -bordercolor black -border 1x1 -floodfill +0+0 black \
            -shave 1x1 diff_mask_$fuzz.png; \
  done
[IM Output]
-fuzz 1%
[IM Output]
-fuzz 3%
[IM Output]
-fuzz 6%
== [IM Output]
-fuzz 28%
[IM Output]
-fuzz 32%
[IM Output]
-fuzz 34%

The blue areas in the above images is the area being masked.

Now we need to select two of the floodfill masks, to define the area in which the semi-transparent pixels will lie.

The first mask should mask the areas of the image we definitely want to make full-transparent. That is the parts we definitely expect to be fully transparent on the final image. The area inside the mask should still contain most of the black halo shadow of the image.

In this case we have a lot of interaction between the image proper and the rest of the background so I chose a Fuzz Factor of '1%' which still contained a large area surrounding the image. In a more typical non-shadowed case this area can be even smaller, down to a non-percentile value such as 5 or 10.

The second mask should have large enough 'fuzz' so as to eat up all the semi-transparent pixels that is present. That is right up to and preferably actually into border of the image without completely removing the border, or 'leaking' into the image proper (see last image above). The negative of this mask will actually represent all the pixels that will be definitely fully-opaque in the final image.

This selection can be difficult and may require a lot of trial and error to figure out the best value to use. For this image a very high fuzz '32%' was able to be chosen without any major problems. Basically you want to try and get it high enough that the final image will not contain any of the original 'background' pixels in it, but without the mask eating away the insides of the image. It may even require a little hand editing to get the mask just right when it leaks.

We can now use this mask to extract the 'core' or inside of our image. That is the parts we are sure does not contain any semi-transparency through to the background pattern we are removing.

  convert diff_mask_32.png -channel blue -separate +channel -negate \
          letter.png +swap +matte -compose CopyOpacity -composite \
          letter_inside.png
[IM Output]

Note how I extracted the blue mask from the flood-filled masked images. Also due to the all-or-nothing nature of flood-filling, the mask will show heavy staircasing or alias effects around the edges. This is the problem the second mask will allow us to fix.

Remember this image is only of the pixels that we know does not interact with the original background, and will be left as is, in the final image. It does not include any of the shadow effects, and anti-aliasing pixels that I am specifically attempting to recover. Recovering those pixels is where the real work lies.

By negating and subtracting (multiplying) the masks we can generate a new mask which defines the area where we want to extract semi-transparent edging or shadowing pixels...

  convert diff_mask_01.png -negate diff_mask_32.png \
          -channel blue -separate +channel -compose multiply -composite \
          mask_aliasing_area.png
[IM Output]

This area is then used to extract the anti-aliasing pixels from the difference mask, which defines how transparent the pixels should be. We normalize those pixels to get a smooth transition from opaque to transparency.

  convert diff_mask.png -channel red -separate +channel \
          mask_aliasing_area.png +matte -compose CopyOpacity -composite \
          -background gray30 -compose Over -flatten -normalize \
          mask_antialiased_pixels.png
[IM Output]

The lighter the color in the above mask, the more opaque the pixel will be. Similarly the darker the color, the more transparency it will be.

Note that I used a gray background here to ensure that the transparent colors that is present in the image will not interfere with the Normalization of the image. Without this this normalization will fail. The flat gray color itself is not important as they are outside the mask area, so will be ignored later.

Now that we have the right transparency level, we need to know what color should be used for these semi-transparent pixels. This color will usually be the same as the edge color of the image, in this case simply, black. However because of the interaction of the original background I decided to go for a dark grey color for the shadow

For an image, such as from a photo, you may need to somehow replicate the varying colors of the edge of inside area to set the correct color for the semi-transparent anti-aliasing pixels. This is a process which I have not figured out (yet).

While we are at it lets also re-mask the image to leave just these special edging pixels.

  convert mask_antialiased_pixels.png mask_aliasing_area.png \
          -compose multiply -composite -negate \
          -background '#444' -channel A  -combine letter_edging.png
[IM Output]

All that is needed is to now layer the inside 'core' of the image with the semi-transparent edging pixels.

  convert letter_inside.png letter_edging.png \
          -background none  -flatten    letter_recovered.png
[IM Output]

And hey presto, we have a image with the background removed to produce a perfect anti-aliased image, with correctly recovered semi-transparent edging and shadowing

You can even overlay it onto a completely different background.

  convert letter_recovered.png tile_aqua.jpg \
          -background none -compose DstOver -flatten    letter_on_aqua.png
[IM Output]

The image I used for this example is very difficult with a large 'edge' region. Most images are not nearly so bad, but this method is probably the best and most universal background removal technique.

FUTURE: generate a script that will do the above, given the original image, the background image, and the range for the 'edging mask' to use. Use the using the cyclops image from above. to create a smoothly anti-aliased cyclops image. It should be possible to do this ALL IN ONE COMMAND!

This technique is very similar to another technique published by Dr Rick Mabry on his web site, Gradient Color Replacement. In this case he replaces colors that fall along some linear gradient, with either another gradient, or a mix of color and texture image. It is well worth a look.


Created: 10 December 2003
Updated: 18 September 2008
Author: Anthony Thyssen, <A.Thyssen@griffith.edu.au>
Examples Generated with: [version image]
URL: http://www.imagemagick.org/Usage/channels/

a