- Index
ImageMagick Examples Preface and Index
Color Channels
Alpha or Matte Channel (How IM handles Transparency)
Using Masks with Images
Transparency Masking an Images (Removing Backgrounds)
In these examples we look at the special operators provided by IM for handling
the individual red, green abd blue channels of an image. More importantally
we aso look at the matte, mask, or alpha channel that controls an images
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 commonly stored as
three arrays of values, called channels. Each channel handles each of the red,
green and blue colors, and 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
|
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 a '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 seperate 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
|
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
|
The last image (the '
Black' or '
K' channel) is
specifically 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
|
Or a simular but not quite the same '
HSL' (Hue, Saturation,
Luminance) representation.
convert rose: -colorspace HSL -separate separate_HSL_%d.gif
|
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, this 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 equivelent 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_luminance.gif
convert rose: -colorspace YUV -channel R -separate channel_luma.gif
|
Gray Gray
|
Neg Black CMYK
|
Brilliance HSB
|
Luminance HSL
|
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 'Luminance' 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 Luminance channel.
  |
Note that the channel names 'Alpha' ('A'),
'Opacity' ('O'), and 'Matte', are
also aliases for the "-channel" setting refering 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...
The "
-fx" command is probably
the most intuitive.
convert rose: -channel BG -fx 0 channel_red.gif
| |
|
But using "
-evaluate"
'
set' method is a lot faster.
convert rose: -channel RB -evaluate set 0 channel_green.gif
| |
|
Then again "
-gamma" can
zero out the unwanted channels without using a "
-channel" setting restriction.
That means you don't need to reset that setting again for later operations.
convert rose: -gamma 0,0,1 channel_blue.gif
| |
|
You can also 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
|
This uses the slow "
-fx"
operator, but also does 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.
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
|
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
Unfortunately at this time there is no simple way to 'combine' color channels
extracted from other (non-RGB)
Color
Spaces.
The problem is that the act of using "
-colorspace" on a set of channel image, modifies the way the
channel image is stored in memory, and as such any greyscale images will no
longer be the expected RGB greyscale image. This in turn causes "
-combine" to fail...
convert separate_HSL_[012].gif -colorspace HSL -combine rose_HSL_failed.gif
|
This could be regarded as a bug, which has been reported, but one that is not
likely to be fixed soon.
The current workaround is to generate a canvas image with the right colorspace
setting first, then individually copy the appropriate gray-scale channel
images into that pre-prepared image, one channel at a time.
convert separate_HSL_0.gif -colorspace HSL \
separate_HSL_0.gif -compose CopyRed -composite \
separate_HSL_1.gif -compose CopyGreen -composite \
separate_HSL_2.gif -compose CopyBlue -composite \
-colorspace RGB rose_HSL_combined.gif
|
This is actually what combine does internally for RGB images, though more
directly.
  |
The above work-around fails for the 'Black' channel for
'CMYK' images. This has also been reported.
|
For example here I take the builtin rose image and want to negate the
luminance channel. That is just the brightness, not its colors.
convert rose: -colorspace HSL -channel RGB -separate +channel \
\( +clone -negate \) +swap +delete \
\( -clone 0 -colorspace HSL \
-clone 0 -compose CopyRed -composite \
-clone 1 -compose CopyGreen -composite \
-clone 2 -compose CopyBlue -composite \
\) -delete 0-2 -colorspace RGB rose_lum_neg.gif
| |
|
You can replace the "
-negate" with your own set of operations to adjust an images
brightness levels.
Of course as "
-negate"
is a channel controled operator we did not have to "
-separate" out the
luminance channel to negate it.
convert rose: -colorspace HSL \
-channel B -negate +channel \
-colorspace RGB rose_lum_neg2.gif
| |
|
As you can see this simplifies the complexity caused by the "
-combine" operators
current short comming.
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 -size 100x100 xc: -colorspace HSL \
hue_angular.png -compose CopyRed -composite \
saturation_solid.png -compose CopyGreen -composite \
luminence_radial.png -compose CopyBlue -composite \
-colorspace RGB colorwheel_HSL.png
|
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 refered to at an images
'transparency' or 'opacity' channel, or even the images '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
| |
|
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
| |
|
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
| |
|
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
Controling 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
|
  |
Before IM v6.3.0, the last set of hex digits contained a 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 otherwords '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
|
  |
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 complient 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 reconise 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
|
|
|
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 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
simplifes the mathemathics 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 Mask of the Image
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 because
of this 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 apposed 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.
Before IM version 6, the only method of obtaining the transparency mask of an
image, was to extract a matte and then "-negate" it, using seperate IM
commands such as a command pipeline like this...
convert drawn.png matte:- | convert - -negate mask.png
| |
|
With IM version 6 a whole array of methods are now available.
Here we use "-separate"
to extract the matte, then negate it. Note that we have to turn off the
channel setting before using "-negate" otherwise the operator will negate the now non-existent
'matte' channel of the separated image.
convert drawn.png -channel matte -separate +channel -negate mask_2.png
| |
|
Alternatively you can "-negate" the matte channel, before we "-separate" it from the image.
convert drawn.png -channel matte -negate -separate mask_3.png
| |
|
The new IM version 6 "-fx"
operator provides a even simpler method. As most mathematical operations
involve an alpha channel, that is what it will use when you request the
'alpha' of an image pixel value. As such we can use it to directly extract a
negative of the stored 'matte' channel.
convert drawn.png -fx 'A' +matte mask_4.png
| |
|
However "
-fx" is rather 'slow'
in comparision to using "
-separate".
The "+matte" operation in
the above is needed once the 'mask' has been extracted, to remove the still
existing transparency of the image.
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_5.png
| |
|
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
|
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
|
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
|
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, as that is the color under the images
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
| |
|
Note that I negated the label result to make it proper mask image, consisting
of a white foreground (opaque) on black background (transparent).
The simplist way to convert this mask into shaped image is to tell IM to
Combine it as a matte
Channel
Image. Remember however that a 'matte' image is a negated mask, so we
need to negate the above image to black on white.
convert heart_mask.gif -negate \
-background Red -channel A -combine heart_red.png
| |
|
Note that as we only created an image from one channel image, the other
channel images was set to the current background color, making this method
the easiest way to create a colored shape from a mask.
The other way of creating a shaped image, is to set the 'alpha' channel of
some image using a '
CopyOpacity' composition method. The shape can then be colored
using a "
-colorize"
operator.
convert heart_mask.gif \
\( +clone \) +matte -compose CopyOpacity -composite \
-fill Gold -colorize 100% heart_gold.png
| |
|
You can also use "
-flatten" with the same composition method.
convert heart_mask.gif \
+matte -background HotPink -compose CopyOpacity -flatten \
heart_hotpink.png
| |
|
This shaped image can now be simply overlaid on any background we want, such
as the built-in rose image.
convert rose: -page +2+2 heart_gold.png \
\( +clone -repage +7+29 \) \
\( +clone -repage +52+14 \) \
-flatten rose_with_love.gif
| |
|
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 as
as appropriate. For more examples of re-coloring a base image, see the whole
section on
Color Modifications.
For example we could add one shaped image, then recolor it for any others
before overlaying all the shapes onto the background image.
convert rose: \( heart_hotpink.png -repage +2+2 \) \
\( +clone -fill purple -colorize 100% -repage +7+29 \) \
\( +clone -fill red -colorize 100% -repage +52+14 \) \
-flatten rose_colored_love.gif
| |
|
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
|
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
| |
|
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.
Double masking
Under Construction
Sometimes you want to overlay new colors over an existing image but also
remove some parts of the original image at the same time. This type of
operation requires to sets of transpareny masks. Specifically you have
a masked overlay, and a second mask to erase some pixels.
FUTURE Simple example without needing semi-transparency...
The only example of this is given for fancy borders' in the thumbnails page.
That is two images are needed. One image to overlay new parts to an image, and a
second to erase old parts of the image.
Both images have transparency masks one to limit the valid parts of the
overlay, and a second to just erase unwanted pixels from the original. The
original image generally does not have any transparency, though that may not
always be the case.
You can apply the two aspects (overlay and erase) in either order, but the
order you decide on is very important as it greatly effects the design of the
two images to produce the desired result.
More importantally is that the two images share the same set of
semi-transparent pixels! This is true whether those pixels effect the
existing colored pixels of the original image, or the images final
semi-transparency.
And just make it more clear.
Double Masking Images, should not share the same set of semi-transparent pixels
EXAMPLE: overlay thick borders to top and bottom of rose image, then fade out
the edges.
1/ Both addition and mask fade out (WRONG)
2/ add opaque border, then fade out the result (RIGHT)
3/ Remove image parts, then add a faded border (RIGHT)
Either method will work as long as the overlay and and erasing work together
correctly.
Transparency Masking an Image
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.
Under Construction
One technique for solid color on solid color is convert the text into a mask.
turning the outside color balck 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
Masking with Floodfill
Images with a existing border has a distinct advantage over just relying on
the background color. It gives the image a definite edge, allowing better
separation between the inside and outside of the image. This separation allows
you to use '
floodfill technique to generate
masking images for background removal.
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
| |
|
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
| |
|
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 smooths 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
| |
|
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 horible 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
| |
|
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 single solid color.
Removing a Known Background
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.png bgnd.png \
-compose ChangeMask -composite bgnd_removed.png
|
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...
composite bgnd_overlaid.png bgnd.png \
-compose Difference bgnd_difference.png
|
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
| |
|
Using this mask we can set anything that has not changed to transparency.
convert bgnd_overlaid.png bgnd_mask.png \
+matte -compose CopyOpacity -composite bgnd_diff_removed.png
| |
|
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.
Image to Background Difference
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
| |
|
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 1 boolean_mask.png
convert cyclops.png boolean_mask.png \
+matte -compose CopyOpacity -composite \
cyclops_boolean.png
| |
|
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
| |
|
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 diminised 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
| |
|
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 simplier 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 above 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
| |
|
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
| |
|
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, seperate to the difference image itself.
Now we need to generate two masks: an outside mask of all areas that will
definatally be transparent; and a mask of the definate 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 40 48 49; do \
convert diff_mask.png -fill blue -fuzz $fuzz% \
-bordercolor black -border 1x1 -floodfill +0+0 black \
-shave 1x1 diff_mask_$fuzz.png; \
done
|
-fuzz 1%
|
-fuzz 3%
|
-fuzz 6%
|
|
-fuzz 40%
|
-fuzz 48%
|
-fuzz 49%
|
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 '
3%' which still contained a large area surrounding the image.
In a more typical non-shadowed case this area can be rather small, with only a
one or two pixels between the blue mask and the object edge, especially in the
corners of the image.
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 asctually 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
'
48%' was able to be chosen without any major problems. In a
more typical case 20% to 30% will probably work well. 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. It may even require a little hand editing
to get the mask just right when leaking is a problem.
In this particular case, I failed, as some background 'blue' will still be
present in the center area of the image. That is this image should have a
'hole' in the middle of the 'A' but that can't be helped. A little editing by
hand can fix this situation, but the hole was small enough with enough shadow,
not to make a very big difference. As such I left it as is.
We can now use this mask to core' or inside of our final background removed
image.
convert diff_mask_48.png -channel blue -separate +channel -negate \
letter.png +swap +matte -compose CopyOpacity -composite \
letter_inside.png
| |
|
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 (multipying) 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_03.png -negate diff_mask_48.png \
-channel blue -separate +channel -compose multiply -composite \
mask_aliasing_area.png
| |
|
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 transsition from opaque to transparency.
convert diff_mask.png -channel red -separate +channel \
mask_aliasing_area.png +matte -compose CopyOpacity -composite \
-background gray30 +compose -flatten -normalize \
mask_antialiased_pixels.png
| |
|
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 so as to try to not interfere with the
Image Normalization. This is needed to
ensure that the transparency values are correct even when the background/edge
change is not just purely black and white. The flat gray areas are not
important as they are outside the mask area, so will be ignored.
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
| |
|
All that is needed is to now layer the inside and edging image together
convert letter_inside.png letter_edging.png \
-background none -flatten letter_recovered.png
| |
|
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
| |
|
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: Do a Cyclops image with thresholds of 3% and 27% and overlay it on
black to prove the proper replacement of all anti-aliasing pixels.
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, colors with either another gradient, or a mix of color and
texture image. It is well worth a look.