ImageMagick v6 Examples --
Image Transformations

Index
ImageMagick Examples Preface and Index
Art-like Transformations
Computer Vision Transformations
Shade 3D Highlighting
Using FX, the DIY Image Operator
Evaluate and Function, Fast FX Operators

These operations produce major changes to the overall appearance of the image either for visual, or art-like effects. However while the overall look of the image has changed, often dramatically, the original image itself is still generally visible in the result.


Art-Like Transformations

Raise or Sunk Borders

The "-raise" operator is such a simple image transformation, that it almost isn't. All it does is as a rectangular bevel highlight to an existing image.

  convert rose:  -raise 5  rose_raise.gif
[IM Output]

A inverted sunken effect can be generated using the 'plus' form of the operator...

  convert rose:  +raise 5  rose_sunken.gif
[IM Output]

This operator is a bit like Framing an image, but instead of adding extra pixels as a border, the "-raise" operator re-colors the edge pixels of the image. This makes it an image transform.

In actual fact Image Framing is achieved by adding a Border, then raising it!

The operator only works on rectangular images, and will fail for images with a transparent background, as the color modifications will also be transparent. Basically is it a rather dumb operator!

Adding an Inside Border

Rather than adding a border around the outside of an image a user wanted to add one to overlay the edges of an image. The solution was to draw a rectangle around the image. As the built in rose in is 70x46 pixels, this is the result.

  convert rose: -fill none -stroke navy -strokewidth 11  \
          -draw 'rectangle 0,0 69,45'   inside_border.jpg
[IM Output]

The width of the border added is controlled by the "-strokewidth" of the rectangle. That is
{stroke width}  =  {border width} * 2 - 1

As such the above 6 pixel border needed a "-strokewidth" of 11.

If you don't know the size of the image, then you can do a two sets of "-chop" and "-splice" to overlay inside borders.

  convert rose: -background green -chop 6x6+0+0 -splice 6x6+0+0  \
                -gravity SouthEast -chop 6x6+0+0 -splice 6x6+0+0 \
                +gravity     inside_border2.jpg
[IM Output]

Vignette Photo Transform

A special operator to make a image circular with a soft blurry outline was a fairly recent addition to IM.

  convert rose: -background black -vignette 0x5  rose_vignette.gif
[IM Output]

By using a very small sigma you can remove the blur (0 produces an error).

  convert rose: -background black -vignette 0x.001  rose_vignette_0.gif
[IM Output]

OR with transparency (and PNG format)...

  convert rose: -matte -background none -vignette 0x3  rose_vignette.png
[IM Output]

A better technique for a more rectangular image with soft edges is demonstrated in Thumbnails with Soft Edges.

Complex Polaroid Transformation

Thanks to the work done by Timothy Hunter, (of RMagick fame), a "-polaroid" transformation operator, was added to IM v6.3.2.
Polaroid® is a registered trademark of the Polaroid Corporation.

For example, here I give a polaroid look to a photo thumbnail. The image is looking up the spiral staircase (downward), inside the Arc de Triumph, Paris. It is a very long staircase!.

  convert spiral_stairs_sm.jpg -thumbnail 120x120 \
          -bordercolor white -background black  +polaroid  poloroid.png
[IM Output]
Note the resulting image has a semi-transparent shadow, so you either have to use a PNG format image, or "-flatten" the result onto a fixed background color for GIF or JPG formats.

This operator is very complex, as it adds border (as per the "-bordercolor" setting), 'curls' the paper, and adds a inverse curl to the shadow. The shadow color can be controlled by the "-background" color setting.

As you saw above the plus form of the operator will rotate the result by a random amount. This operator makes a Index of Photos much more interesting and less static than you would otherwise get.

The minus form of the operator lets you control the angle of rotation of the image.

  convert spiral_stairs_sm.jpg -thumbnail 120x120 \
          -bordercolor AliceBlue -background SteelBlue4 -polaroid 5 \
          poloroid_5.png
[IM Output]

If the image has "-caption" meta-data, that text will also be added into the lower border of the polaroid frame, via the "caption:" image creation operator. That is it will be word wrapped to the width of the photo.

  convert -caption '%c %f\n%wx%h' spiral_stairs_sm.jpg -thumbnail 120x120 \
          -bordercolor Lavender  -background gray40  +polaroid \
          poloroid_captioned.png
[IM Output]

The other standard text settings (as per "caption:"), allows you to control the look of the added caption.

  convert spiral_stairs_sm.jpg -thumbnail 120x120 -font Candice -pointsize 18 \
          -bordercolor Snow -background black -fill dodgerblue -stroke navy \
          -gravity center  -set caption "Spiral Stairs\!"  -polaroid 10 \
          poloroid_controls.png
[IM Output]

The image meta-data attribute "-caption" was used due to the internal use of "caption:" text to image generator.

On the other hand the IM command "montage" uses "-label" as it uses the non-word wrapping "label:" text to image generator.

The transforms use of Rotate and Wave shearing distortions to add a little 'curl' to the photo image has a tendency to produce horizontal lines of fuzziness in text of the image generated. This is a well known Image Distortion problem (see Rotating a Thin Line), and one that can be solved by using a super sampling technique.

Basically we generate the polaroid twice as large as what we really want, then we just resize the image to its final normal size. The reduction in the image size effectively sharpens the resulting image, and more importantly the caption text.

However to make this work we not only need a image at least twice the final size, but also we may need to a larger border to the image, and draw the text at twice its normal "-density". Do not increase the fonts "-pointsize" as that does not enlarge the text in quite the same way.

  convert -caption 'Spiral Staircase, Arc de Triumph, Paris, April 2006' \
          spiral_stairs_sm.jpg  -thumbnail 240x240 \
          -bordercolor Lavender -border 5x5   -density 144  \
          -gravity center  -pointsize 8   -background black \
          -polaroid -15     -resize 50%     poloroid_modified.png
[IM Output]

As you can see, even though we used a much smaller font pointsize, the caption text is very sharp, clear and readable. The same for any other fine detail that may have been present in the original image. The only disadvantage of this is that the shadow of the resulting image will be smaller, and less fuzzy.

For total control of the polaroid transformation, you can do all the steps involved yourself. The original technique documented on Tim Hunter's page, RMagick Polaroid Effect. The steps are: create and append caption, add borders, curl photo with wave, add a reversed curled shadow, and finally rotate image.

For more examples, and other DIY methods, see Polaroid Thumbnail Examples, and A Montage of Polaroid Photos. You may also be interested in some of the polaroid examples in RubbleWeb IM Examples, Other.

Oil Painting, blobs of color

The "-paint" operator is designed to convert pictures into paintings made by applying thick 'blobs' of paint to a canvas. The result is a merging of neighbourhood colors into larger single color areas.


  convert rose:  -paint 1   rose_paint_1.gif
  convert rose:  -paint 3   rose_paint_3.gif
  convert rose:  -paint 5   rose_paint_5.gif
  convert rose:  -paint 10  rose_paint_10.gif
  convert rose:  -blur 0x3 -paint 10  rose_blur_paint_10.gif
[IM Output] ==> [IM Output] [IM Output] [IM Output] [IM Output] [IM Output]

Notice that at a high radius for the paint blobs, the blobs start to get a squarish look to them. This effect can be smoothed somewhat by blurring the image slightly before hand, as shown in the last image above.

It is an interesting effect and could be used to make some weird and wonderful background images. For example see its use in Background Examples.

On final warning. While "-paint" is supposed to produce areas of a single solid color, at large radius values, it has a tendency to produce a vertical gradient in some areas. This is most annoying, and may be a bug. Does anyone know?

Charcoal, artists sketch of a scene

The charcoal effect is meant to simulate artist's charcoal sketch of the given image.

The "-charcoal" operator is in some respects similar to edge detection transforms used by Computer Vision. Basically it tries to convert the major borders and edges of object in the image into pencil and charcoal shades.

The one argument is supposed to represent the thickness of the edge lines.

  convert rose:  -charcoal 1   rose_charcoal_1.gif
  convert rose:  -charcoal 3   rose_charcoal_3.gif
  convert rose:  -charcoal 5   rose_charcoal_5.gif
[IM Output] ==> [IM Output] [IM Output] [IM Output]

For a better example of using a charcoal transformation on a real image see Charcoal Sketch of a Photo.

.
Technically the "-charcoal" operator is a "-edge" operator with some thresholding applied to a grey-scale conversion of the original image.

Pencil Sketch Transform

The "-sketch" operator basically applies a pattern of line strokes to an image to generate what looks like an artistic pencil sketch. Arguments control the length and angle of the strokes.

However it is best applied to a larger image with distinct and shadings.

See Pencil Sketch for a full example of this operator and how it works internally.

Emboss, creating a metallic impression

The "-emboss" operator tries to generate the effect of a acid impression of a grey-scale image on a sheet of metal. It is in many respects very similar to the "-shade" operator we will look at below, but without the 3D looking edges.

Its argument is a radius/sigma, with only the sigma being important. I not found the argument very useful, and may in fact be buggy. The argument has also changed in a recent version of IM. I just don't know what is going on. Help me understand if you can.

  convert rose:  -emboss 0x.5  rose_emboss_0x05.gif
  convert rose:  -emboss 0x.9  rose_emboss_0x09.gif
  convert rose:  -emboss 0x1   rose_emboss_0x10.gif
  convert rose:  -emboss 0x1.1 rose_emboss_0x11.gif
  convert rose:  -emboss 0x1.2 rose_emboss_0x12.gif
  convert rose:  -emboss 0x2   rose_emboss_0x20.gif
[IM Output] ==> [IM Output] [IM Output] [IM Output] [IM Output] [IM Output] [IM Output]

The operator is a grey-scale operator, meaning it will be applied to the three color channels, separately. As such should only be applied to grey-scale images. As you saw above, color images can produce some weird effects.

  convert rose: -colorspace Gray  -emboss 0x.5  rose_g_emboss_0x05.gif
  convert rose: -colorspace Gray  -emboss 0x.9  rose_g_emboss_0x09.gif
  convert rose: -colorspace Gray  -emboss 0x1   rose_g_emboss_0x10.gif
  convert rose: -colorspace Gray  -emboss 0x1.1 rose_g_emboss_0x11.gif
  convert rose: -colorspace Gray  -emboss 0x1.2 rose_g_emboss_0x12.gif
  convert rose: -colorspace Gray  -emboss 0x2   rose_g_emboss_0x20.gif
[IM Output] ==> [IM Output] [IM Output] [IM Output] [IM Output] [IM Output] [IM Output]

 If anyone knows exactly what the emboss algorithm is supposed to do,
please let me know.

Stegano, hiding a secret image within an image

The "-stegano" operator is really more of a 'fun' operator. For example it could be use by a spy to hide info in the 'chaos' of a random image.

For example, lets generate a cryptic message (image) that you want to send to your fellow spy...

  convert -gravity center -size 50x40 label:"Watch\nthe\nPidgeon" message.gif
  identify message.gif
[IM Output]
[IM Text]

Note that we will also need the size of the message image (36x43 pixels), thus the identify in the above.

Next the put it into some image with some offset. The offset (and message size) used is the cryptographic 'key' for the hidden message.

  composite message.gif rose: -stegano +15+2  rose_message.png
[IM Output]

Now you can send that image to your compatriot, who presumably already knows the messages size and offset.

He can the recover the message hidden in the image...

  convert -size 50x40+15+2 stegano:rose_message.png message_recovered.gif
[IM Output]

The larger the containing image the better the recovered image will be.

Just to show you how the hidden message was distributed throughout the container image, lets do a comparison of the combined against the original.

  compare -metric PAE rose:  rose_message.png   rose_difference.png
[IM Output]
[IM Text]

Which shows the message image was encrypted and distributed all over the image to hide it.

Also the 'PAE' metric returned by the above shows that the largest difference was only a single color value out of the 8 bit color values used for this image.

That is tiny. So tiny that a small change or modification to the image will destroy the message hidden within. It is such a small difference, you can't even use JPEG with its lossy compression as the image format, or any other lossy image format (including GIF).

Also if you had the wrong 'offset key' you will not get the message...

  convert -size 50x40+14+2 stegano:rose_message.png message_bad.gif
[IM Output]

Also you can limit what area of the image the message is to be hidden by using a Region setting. This would probably make finding and decoding the hidden message in a large image, especially if restricted to a 'busy' area, a order of magnatitude harder to determine. But how cryptographically secure it is, is not known.

As a method of image copyright protection, it is useless, as the smallest change to the image will destroy the hidden message, and thus its effectiveness.

As a spy tool it is also not very good, with such a small number of 'combinations' for a reasonable sized image. Anyone who knows roughly what you are doing could probably crack it quickly. Better to stick to well known and tested cryptographic methods.

It's only real practical use is as a fun tool, or as a way to add very small amounts of noise to an existing image.

Encrypting Image Data

While "-stegano" attempts (badly) to hide a second image inside another image, without making it look like the image is hiding such information, the operators "-encipher" and "-decipher" actually encrypt the image data into a garbled mess.

That is the image content itself is no longer recognisable until the image is later decrypted. This can be used for example to protect sensitive images on public services, so that only others with the secret pass-phrase can later view it.

For example lets encrypt that secret message image we created above, using a pass-phrase I have saved in the file "pass_phrase.txt".

  convert message.gif  -encipher pass_phrase.txt  -depth 8  message_hidden.png
[IM Output] ==> [IM Output]

The encrypted image assumes it is saved using a 8 bit image file format. As such it is recommended to enforce that limitation by setting "-depth 8" before the final save to the output file.

As you can see the resulting image looks like complete garbage, with no indication of the images real content.

Now you can publish that image on the web, and only someone who knows the exact original pass-phrase can restore the image data...

  convert message_hidden.png -decipher pass_phrase.txt message_restored.gif
[IM Output]

However be warned that if the image data is corrupted in some way, you will not be able to restore it. That includes if the PNG saved using a gray-scale image format type. As such, only a non-lossy image format can be used, such as PNG, MIFF, TIFF, or even Pixel Enumeration Text. However using a lossy image format, such as JPEG, PNG8, and GIF, will corrupt the image data, thus destroy the resulting encryption.

Note that any meta-data that may be describing the image, will still be in the clear. That means, you could encrypt images using the images own 'comment' string as the pass-phrase or use that comment encrypted using some smaller password. Its a simple idea that could make the pass-phrase more variable.

Encrypting an image can be just one step. Taking the result just that little further can produce a image that will not simply decrypt, without some extra processing. For example here I use some Simple Non-Destructive Distorts to confuse anyone trying to decrypt the image in the normal way.

  echo "password" | convert message.gif -encipher - \
                      -transpose  -depth 8  message_obfuscate.png
  echo "password" | convert message_obfuscate.png -transpose \
                      -decipher -  message_restored_2.png
[IM Output] ==> [IM Output] ==> [IM Output]

If you did not include the "-transpose" in the decryption command above, the image will not have deciphered correctly. Also note that due to the streaming cipher used (see the expert note below) using a "-roll" will not prevent the image from being decrypting, at least partially.

Note that in the above I did not use a file to hold the 'pass-phrase' but fed the phrase into the "convert" command using standard input, which allows you use some other program or command to get the phrase from the user, generate it, or some other method, instead of using text file with the pass-phrase in the clear.

The pass-phrase could also be generated from other freely downloadable files and images. For example you could decrypt your image using the signature, or comment string of a well known, freely downloadable image reference image. For instance here I use the signature of the "rose.gif" image to encrypt and later decrypt the "message.gif" image.

  identify -format %# rose.gif |\
    convert message.gif  -encipher - -depth 8  message_signed.png
  identify -format %# rose.gif |\
    convert message_signed.png   -decipher -   message_restored_3.png
[IM Output]  + [IM Output] ==> [IM Output]  + [IM Output] ==> [IM Output]

As of IM v6.4.8-0 the file used by "-encipher" and "-decipher" can be a binary file, such as the direct use of an image for the passphrase.

  convert message.gif   -encipher rose.gif  -depth 8  message_binary.png
  convert message_binary.png   -decipher rose.gif   message_restored_4.png
[IM Output]  + [IM Output] ==> [IM Output]  + [IM Output] ==> [IM Output]

Before IM v6.4.8-0 a binary file would stop at the first 'NULL' character it finds. Something that would happen rather early if a PNG image was used.

This technique is exact (unless some of the data was destroyed in transmission). And as such you can use it to encrypt an image containing other hidden information such as a Stegano Image. This means even if authorities do decrypt the image, or force you to reveal the password, the image is still not the final hidden one.

The "-encipher" and "-decipher" operators was added to IM v6.3.8-6, but required you to include a "--enable-cipher" option in the build configuration. However by IM v6.4.6 (when did it change?) this configuration item was no longer needed and it became a standard configuration setting. As such you can probably use it immedaitally.

The cipher was implemented using a self-synchronizing stream cipher implemented from a block cipher.

This means that you can still decipher even a partial download of the image, which was destroyed by transmission error, even though some part of the image may have been destroyed. You also do not need to downloaded the whole image to decrypt and examine the parts that was successfully downloaded.

But you do need the pass-phase to have any chance at all of successfully decrypting the image, as it is a very very strong encryption.


Computer Vision Transformations

Edge Detection

The "-edge" operator highlights areas of color gradients within an image. It is a grey-scale operator, so is applied to each of the three color channels separately.

  convert mask.gif -edge 1   mask_edge_1.gif
  convert mask.gif -edge 2   mask_edge_2.gif
  convert mask.gif -edge 3   mask_edge_3.gif
  convert mask.gif -edge 10  mask_edge_10.gif
[IM Output] ==> [IM Output] [IM Output] [IM Output] [IM Output]

As you can see, the edge is added only to areas with a color gradient that is more than 50% white! I don't know if this is a bug or intentional, but it means that the edge in the above is located almost completely in the white parts of the original mask image. This fact can be extremely important when making use of the results of the "-edge" operator.

For example if you are edge detecting an image containing an black outline, the "-edge" operator will 'twin' the black lines, producing a weird result.

  convert piglet.gif  -colorspace Gray  -edge 1 -negate  piglet_edge.gif
[IM Output] ==> [IM Output]

However by negating the image before doing the edge detecting, the twined lines go inward and join together, removing the 'twin line' effect.

  convert piglet.gif -colorspace Gray \
                 -negate -edge 1 -negate    piglet_edge_neg.gif
[IM Output]

I have found that the edges tend to be too sharp, generating a non-smooth edge to the resulting images. As such I find a very very slight blur to the result improves the look quite a bit.

  convert piglet_edge_neg.gif  -blur 0x.5  piglet_edge_blur.gif
[IM Output]

Here I have applied edge detection to a color image, and a grey-scale version to show you its effects on photo-like images.

  convert rose:                   -edge 1  rose_edge.gif
  convert rose: -colorspace Gray  -edge 1  rose_edge_grey.gif
[IM Output] [IM Output]

As you can see without converting the image to grey-scale the edges for the different color channels are generated completely independent of each other.

Edge Outlines from Anti-Aliased Shapes

The biggest problem with normal edge detection methods is that the result is highly aliased. That is it generates a very staircase like pixel effects, regardless of if the shape is smooth (anti-aliased) or aliased.

For example here is a smooth anti-aliased voice balloon ("WebDings" font character '(' ).

  convert -size 80x80 -gravity center -font WebDings label:')' voice.gif
[IM Output]

And here is its edge detected image...

  convert voice.gif -edge 1 -negate   voice_edge.gif
[IM Output]

As you can see it looks horrible, with some minor anti-aliasing on the outside of the edge, and a total aliased (staircase) look on the inside of the line.

The negating the image generated a similar outline around the outside of the image, but also has strong aliasing outside of the line.

  convert voice.gif -negate -edge 1 -negate   voice_edge_negate.gif
[IM Output]

An alternative when you already have an image with an anti-aliased edge, is to generate the difference image of a 'jittered' clone of the original shape. For example here we find the difference image between the original, image and one that has been offset (or jittered) to the right by 1 pixel.

  convert voice.gif \( +clone -roll +1+0 \) -compose difference -composite \
          -negate   voice_jitter_horiz.gif
[IM Output]

Note that the this does not produce a good edge for horizontal sloped edges. However by combining both a horizontal and a vertical jittered difference image, we can get a very good anti-aliased outline of the shape.

  convert voice.gif \
          \( -clone 0 -roll +1+0 -clone 0 -compose difference -composite \) \
          \( -clone 0 -roll +0+1 -clone 0 -compose difference -composite \) \
          -delete 0  -compose screen -composite -negate  voice_jitter_edge.gif
[IM Output]

This technique also has the advantage of working regardless of if the mask is negated or not.

Note however that the result has a 1/2 pixel offset relative to the original image, so it may require some further 'distortion' processing to re-align either the original shape, or the outline if the two needs to be combined to get the result you want.

Edge Outlines from Bitmap Shapes

Bitmap images are much harder, as they don't have any anti-aliased pixels that can be used to produce a smooth outline.

For example here is a fancy 'Heart' shape that was extracted from the "WebDings" font (character 'Y'). However I purposefully generated it as a aliased bitmap, to simulate a horrible bitmap image downloaded from the network. Such as the outline of a GIF image containing transparency.

  convert +antialias -size 80x80 -gravity center \
          -font WebDings label:Y   heart.gif
[IM Output]

So we have this horrible image, but we want to find the images outline rather than its shape. Direct use of edge detection will only generate a pure bitmap edge around the outside of the bitmap shape.

  convert heart.gif -edge 1 -negate   heart_edge.gif
[IM Output]

A negated edge generates a edge image but for the inside of the black area.

  convert heart.gif -negate -edge 1 -negate   heart_edge_negate.gif
[IM Output]

By adding both of the above you get a 2 pixel edge centered on the bitmap shapes edge.

  convert heart.gif \( +clone -negate \) -edge 1 \
          -compose add -composite  -negate  heart_edge_double.gif
[IM Output]

As you can see the resulting image is highly aliased with 'staircase' like effects in the outline, even though the original image is itself not too bad in this regard. This is not a good solution.

A slightly better edge can be created by using a special single pixel 'erode' on the shape, then subtract that from the original image, to get the shapes outline pixels (technique by Fred Weinhaus).

  convert heart.gif \( +clone -blur 0x0.2 -threshold 0 \) \
          -compose difference -composite  -negate  heart_erode.gif
[IM Output]

And a similar effect can be achieved by just using resizes to blur the edge in the right way, before using a Solarize to extract the mid-gray pixels that form the edge. A thicker edge can be generated by adding a "-filter Cubic" setting, or other Resize Filter.

  convert heart.gif -resize 400% -resize 25% \
          -solarize 50% -evaluate multiply 2 -negate heart_resize.gif
[IM Output]

Or for more controlled fuzzier effect you can just blur the shape and extract the edge in a similar way. I find a blur of '0.7' about the best, with a 3 pixel limit to speed things up.

  convert heart.gif -blur 3x.7 -solarize 50% -level 50%,0 heart_blur.gif
[IM Output]

Note the use of Level Operator, which is the equivalent of the "-evaluate multiply 2 -negate" used in the previous example.

With an anti-aliased border you can now re-add the original shape if you just want to smooth the original shape rather than get its outline. Just remember that the outline is positioned exactly along the edge of the original image, so will be half a pixel larger in size that the previous examples.

Do you know of any other ways of generating a anti-aliased outline from a shape (anti-aliased, or bitmap). If so please mail it to me, or the IM forum. You will be credited, like Fred was above.

Edging using a Raster to Vector Converter

One of the most ideal solutions is to use a non-IM 'raster to vector' conversion program to convert this bitmap shape into a vector outline. Programs that can do this include: "ScanFont", "CorelTrace" and "Streamline" by Abobe. Most of these however cost a lot of money. But a free solution is "AutoTrace" or "PoTrace". Other suggestions are welcome.

Both trace programs are simple to use, but requires some pre and post image setup. They have a limited number of input formats, and outputs a vector image which will create a 'smoothed' form of the input image. I prefer the "AutoTrace" as it does not scale the resulting SVG, producing a standard line thickness, however you can not use it in a 'pipeline'.

For best results it is a good idea to ensure we only feed it a basic bitmap image, which we can ensure by thresholding the input image, while we convert it to a image format autotrace understands. I can then convert that image into a SVG vector image.

  convert heart.gif -colorspace gray -threshold 50% heart_tmp.pbm
  autotrace -output-format svg -output-file heart.svg heart_tmp.pbm
  convert heart.svg heart_svg.gif
  rm -f heart_tmp.pbm
[IM Output] ==>
[IM Text]
==> [IM Output]

As of IM v6.4.2-6 you can generate the SVG form of the image, directly with a IM built with the "AutoTrace" delegate library. See SVG Output Handling for details.


  convert heart.gif  heart_2.svg
[IM Text]

Now the SVG output will of course represent a smoothed version of the original image, which is not what we actually want. But as we now have a vector form of the image we can adjust the SVG 'style' attributes so as to 'stroke' the outline, rather than 'fill' the shape. The modified SVG can then be fed back into ImageMagick again to recreate the clean outline raster image.

For example...

  cat heart.svg |
    sed 's/"fill:#000000[^"]*"/"fill:none; stroke:black;"/' |
      convert svg:- heart_outline.gif
[IM Output]

Yes it is a little awkward, but the smooth anti-aliased result is well worth the effort. It would be nice if outline or some other modifications could be specified as options to the "autotrace" command itself, but that is currently not a feature.

You can also further modify the SVG output to thickening the edge, or specify some other stroke or background color, change the fill color of the vector edge shape. For example here we generate a smooth properly anti-aliased a thicker outline of the shape with a red fill.

  cat heart.svg |
    sed 's/"fill:#000000;[^"]*"/"fill:red; stroke:black; stroke-width:5;"/' |
        convert svg:- heart_outline_thick.gif
[IM Output]

Such a perfect looking heart could not have been generated from a bitmap shape in any other way.

We could also just extract the 'd="..."' path element to use directly as a SVG Path String in the Draw Command. This would then allow you to use any of the other IM draw settings with that vector outline, giving you complete control of the final result.


Local Adaptive Thresholding

Under Construction

The "-lat" operator, tries to adaptively threshold each pixel based on the value of pixels in a surrounding window. This is commonly used to threshold images with an uneven background (i.e., uneven illumination). It is based on the assumption that pixels in a small window will have roughly the same background color and roughly the same foreground color.

For example.
   convert input.png -lat 17 output.png
In the above a 17 pixel square 'window' is used to determine the average color of the image at each point, if the pixel is darker than this average it is made black, if lighter than this average it is made white.

A small window size will make the threshold more sensitive to small changes in illuminations, is faster to compute but is adversely affected by noise in the image.
Example
A larger window size will make the threshold less sensitive to small changes in illumination, is slower to compute and less affected by noise in the image. This has the effect of making the threshold value selection more or less sensitive to small changes in pixel values.
Example
The window does not need to be square.  for example...
  convert input.png -lat 15x25 output.png
You can also provide a offset which will be added to the calculated average color, making the local threshold value for each pixel either lighter or darker. This can be used for example to reduce the effect of noise or the effect of small changes in pixel values.

  convert input.png -lat 15x25+2%
These small changes normally occur when a scanner or digital camera is used to acquire the image. Use a positive offset value to make the adaptive thresholding less sensitive to small variations in pixel values. Use a negative threshold to make the adaptive threshold more sensitive to small variations in pixel values.

Alternatively, one could reduce the noise in the image before processing it with "-lat".

In summary, each pixel is thresholded using the following logic:
  AVG = average value of each pixel in the window
  IF (input pixel is > AVG + OFFSET)
     Output pixel is BLACK
  else
     Output pixel is WHITE

---

An alternative is to subtract a blurred copy of the original image
using (Modulus) Subtraction, then thresholding.

   convert rose: -colorspace gray -lat 10x10+0% x:

is roughly equivalent to...

   convert rose: -colorspace gray \( +clone -blur 10x65535 \) \
           -compose subtract -composite -threshold 50%  x:

The special "-blur 10x65535"  is a linear averaging blur limiting itself to a
10x10 window.

The 'Subtract' composition being a mathematical modulus type of operation will
wrap the values that goes negative back round to a value greater than 50%.

If you want to include an offset you can do so by also subtracting a solid
color background image by using a -flatten...  for example

   convert rose: -colorspace gray -lat 10x10+10% x:

is roughly equivalent to...

   convert rose: -colorspace gray \( +clone -blur 10x65535 \) \
           -compose subtract -background gray10 -flatten -threshold 50%  x:

The above was modified from initial notes provided by D Hobson <dhobson@yahoo.com>


   -adaptive-sharpen
        Sharpen images only around the edges of the images

   -segment cluster-threshold x smoothing-threshold
         Segmentation of the color space (not image objects)
         This can produce very verbose output.
         This applies the "fuzzy c-means algorithm" if you want to know more.

Also related is -despeckle. to remove single off color pixels.

Generate a 3d stereogram of two images (one for each eye)
This is also known as a anaglyph
  composite left.jpg right.jpg -stereo anaglyph.jpg


Shade 3D Highlighting

Shade Usage

The "-shade" operator I have always thought one of the most interesting operators provided by ImageMagick. The documentation of this operator only gave a rough hint as to its capabilities. It too me a lot of personal research to make sense of the operator, and even figure out how best to use the power that it can provide IM users.

Basically what this operator does is assume that the given image is something called a 'height field'. That is a grey-scale image representing the surface of some object, or terrain. The color 'white' represents the highest point in an image, while 'black' the lowest point.

This representation come out of the 1980 computer vision research, where a photo with a strong 'camera light' was used, making near points bright, and points far away dark.

As a grey-scale image is needed by "-shade", the operator will automatically remove any color from the input image. Similarly any transparency that may be present in the image is completely useless and ignored by the operator.

Now "-shade" takes this grey-scale height field and shines a light down onto it. The result is a representation of light shades that would thus be produced. Remember you must think of the input image as a 'surface' for the output to make any sense.

For our demonstrations we will need a 'height field' image so lets draw one.

  convert -font Candice -pointsize 64 -background black -fill white \
          label:A  -trim +repage -bordercolor black -border 10x5 \
          shade_a_mask.gif
[IM Output]

This image is also equivalent of a 'mask' of an shape, is often not only used as input to "-shade", but also a Masking Image to cut out the same shape from the shaded results. See Masking a Shade Image below.

To the "-shade" operator this image will look like a flat black plain, with a flat white plateau rising vertically upward. Only the edges of this image will thus produce interesting effects.

To this effect the two arguments defines the direction from which the light is shining.

The first argument is the direction from which the light comes. As such a '0' degree angle will be from the east (of left), '90' is anti-clockwise from the north (or top), and so on. For example...

  convert shade_a_mask.gif   -shade    0x45   shade_direction_0.gif
  convert shade_a_mask.gif   -shade   45x45   shade_direction_45.gif
  convert shade_a_mask.gif   -shade   90x45   shade_direction_90.gif
  convert shade_a_mask.gif   -shade  135x45   shade_direction_135.gif
  convert shade_a_mask.gif   -shade  180x45   shade_direction_180.gif
[IM Output] ==> [IM Output] [IM Output] [IM Output] [IM Output] [IM Output]

You get the idea. The light can come from any direction.

The other argument is the elevation, and represents angle the light source makes with the ground. You can think of it as how high the sun is during the day, so that '0' is dawn, and '90' is directly overhead.
[photo]


  convert shade_a_mask.gif   -shade  90x0    shade_elevation_0.gif
  convert shade_a_mask.gif   -shade  90x15   shade_elevation_15.gif
  convert shade_a_mask.gif   -shade  90x30   shade_elevation_30.gif
  convert shade_a_mask.gif   -shade  90x45   shade_elevation_45.gif
  convert shade_a_mask.gif   -shade  90x60   shade_elevation_60.gif
  convert shade_a_mask.gif   -shade  90x75   shade_elevation_75.gif
  convert shade_a_mask.gif   -shade  90x90   shade_elevation_90.gif
[IM Output] [IM Output] [IM Output] [IM Output] [IM Output] [IM Output] [IM Output]

As you can see with an elevation of '0' the shape is only highlighted on the side from which the light is coming. Everything else is black, as no light shines on any other surface. I call this a 'Dawn Highlight' and has its own special uses.

This brings use to the first item of note. A image that is "-shade" will often have more dark, or shadowed areas, than highlighted areas. The shading is not equal.

As the light gets higher over the 'height field' image. The overall brightness of the image will become whiter, until at 'high-noon' or an elevation of '90' any flat areas are brilliantly white, and only slopes and edges are shaded to a grey color, with a mid grey as a maximum, or 'cliff-like' slope change.

This 'noon' image is another special case that is a bit like a edge detection system, though it is between 2 and 4 pixels wide for sharp edges. I have used this image in the past for generating a mask for the the beveled edge of "-shade" images, so as to make flat areas transparent.

If the elevation angle goes beyond '90' degrees, you will get the same result as if the light was from the other direction. As such the argument '0x135' will produce exactly the same result as '180x45'. A negative elevation angle will also produce the same results, as if the light is coming up from below, onto a 'translucent' like surface. As such '0x-45' will be the same as '0x45'. In other words for a particular shade there are usually 4 other arguments that will also produce the same result.

From the above I would consider an argument of '120x45' to be about the best for direct use of the shade output. For example here it creates some beveled text...

  convert -size 320x100 xc:black \
          -font Candice -pointsize 72 -fill white \
          -draw "text 25,65 'Anthony'" \
          -shade 120x45  shade_anthony.jpg
[IM Output]

One of the major problems with "-shade" is the thickness of the bevel that is actually produced. A sharp edge such as I used above will always produce a bevel of about 4 pixels wide, both into and out of the masked area. There is no way to adjust this thickness, short of resizing images before and after using the "-shade" operator..

If you would like to find out just how bright 'flat areas' will be from a specific elevation lighting angle, then you can use the following command, to shade a flat solid color surface.

  convert -size 50x50 xc:white -draw 'circle 25,25 20,10' \
          -blur 0x2  -shade 0x45   -gravity center -crop 1x1+0+0 txt:-
[IM Text]

As you can see a elevation of '45' degrees produces a quite bright flat color of about 70% grey, which is a reasonable grey level for general viewing. However if you plan to use shade for generating 3-D highlights of various shapes, then the actual grey level becomes very important. This will be looking at later in Creating Overlay Highlights.

That is basically it, for the "-shade" operator. However using it effectively presents a whole range of techniques and possibilities, which we will look at next.

Masking Shaded Shapes

As mentioned above, a simple 'mask' shape is often used with "-shade" to generate complex 3-D effects from a simple shape. For example lets do this to a directly shaded mask image.


  convert shade_direction_135.gif  shade_a_mask.gif \
          +matte -compose CopyOpacity -composite   shade_beveled.png
[IM Output]  + [IM Output] ==> [IM Output]

Notice that about half the bevel generated by the "-shade" operator, actually falls outside the masked area. In other words, a straight bevel is halved when masked.

On the other hand the vertical or 'midday' shade image (using '90' degree elevation angle) can be used to just extract the beveled edge, leaving the center of the image hollow.

  convert shade_direction_135.gif \
          \( shade_elevation_90.gif -normalize -negate \) \
          +matte -compose CopyOpacity -composite   shade_beveled_edge.png
[IM Output]  + [IM Output] ==> [IM Output]

Note however that the 'midday' shade image, while providing a way to mask the location (and intensity) of the effects of the "-shade" operator does not actually cover those effects completely.

By combining the 'midday' shade image with the original mask you can increase the size of that mask slightly to produce a better masked beveled image.

  convert shade_direction_135.gif \
          \( shade_elevation_90.gif -normalize -negate \
             shade_a_mask.gif -compose screen -composite \) \
          +matte -compose CopyOpacity -composite   shade_beveled_plus.png
[IM Output]

Remember with IM v6 you can generate the 'shade' image I generated previously all in the same command. As such the above could have been completely generated from scratch. For example.

  convert -font Candice -pointsize 72 -background black -fill white \
          label:X  -trim +repage -bordercolor black -border 10x5 \
          \( -clone 0 -shade  135x45 \) \
          \( -clone 0 -shade  0x90  -normalize -negate \
             -clone 0 -compose screen -composite \) \
          -delete 0 +matte -compose CopyOpacity -composite \
          shade_beveled_X.png
[IM Output]

With the addition of the Alpha Extract Method you can not only extract the alpha channel from a shaped image. Not only that but the operator has the side effect of preserving the shape in the 'turned-off' alpha channel. As it is 'turned off' it will not be touched by many image processing operators, including "-shade".

What this means is that for shaped images, you can extract the shape, do the work, then simply recover the transparency AFTER you have finished all the image processing, simply by turning the Alpha On again!

For example here I draw a 'Heart' on a transparent background, do some blurring and shading of the image, then restore the original shaped outline of the image.

  convert -size 100x100 -gravity center -background None \
          -font WebDings label:Y \
          -alpha Extract -blur 0x6 -shade 120x21 -alpha On \
          -normalize +level 15%  -fill Red -tint 100%    shade_heart.png
[IM Output]

All I can say is WOW, what a time saver! A lot simplier than the previous command with all its extra processing and image cloning.

Rounding Shade Edges

As you saw in the last example, by blurring the image shape mask, the 'slope' of edge 'cliffs' will be smoothed out, as if worn down by time. This produces a nice rounded effect to the shade image.

  convert -size 50x50 xc:black -fill white -draw 'circle 25,25 20,10' \
          shade_circle_mask.gif
  convert shade_circle_mask.gif            -shade 120x45  shade_blur_0.gif
  convert shade_circle_mask.gif -blur 0x1  -shade 120x45  shade_blur_1.gif
  convert shade_circle_mask.gif -blur 0x2  -shade 120x45  shade_blur_2.gif
  convert shade_circle_mask.gif -blur 0x3  -shade 120x45  shade_blur_3.gif
  convert shade_circle_mask.gif -blur 0x4  -shade 120x45  shade_blur_4.gif
  convert shade_circle_mask.gif -blur 0x5  -shade 120x45  shade_blur_5.gif
[IM Output] ==> [IM Output] [IM Output] [IM Output] [IM Output] [IM Output] [IM Output]

As you can see blurring not only rounds-off the edges, but makes the lighting effects dimmer. You can maximize the contrast of the result by normalizing the it, so as to bring the brightest and darkest points back to pure white and black colors respectively.

  convert shade_blur_3.gif   -normalize  shade_blur_3n.gif
[IM Output] ==> [IM Output]

The only draw back with this is that this also generally darkens the shaded image. This is something which we'll need to take into account in Creating Overlay Highlights.

Lets finish off this shade image by directly masking it as well..

  convert shade_blur_3n.gif shade_circle_mask.gif \
          +matte -compose CopyOpacity -composite   shade_blur_3n_mask.png
[IM Output] ==> [IM Output]

As you can see blurring the mask image will round off the edges of the resulting shape very nicely.

Creating Overlay Highlighting

The output from the "-shade" operator is very nice, but it is rare that you actually want a plain grey scale image of your shape. What is needs is some color.

This however is not so easy as the two major ways of adding color, Color Tinting Mid-Tones to just recolor a grey-scale, or 'Overlay' alpha composition, to replace the grey areas with a image, both relay on a special form of grey-scale image. That is a perfect mid-tone grey ('grey50') is replaced by the color or image, while whiter or darker greys, whiten and darken the color or image as appropriate.

These special grey-scale 'overlay highlight' images with perfect mid-tone greys for un-modified areas are, however, not so straight forward to create using "-shade". However the following are some of the more simpler ways I have discovered.

Using a 30 degree elevation lighting angle with "-shade", is one way of producing a perfect mid-tone grey for flat areas of the shape being shaded.

  convert -size 50x50 xc:black -fill white -draw 'circle 25,25 20,10' \
          -blur 0x2  -shade 120x30           shade_30.png
  convert shade_30.png   -gravity center -crop 1x1+0+0 txt:-
[IM Image]
[IM Text]

Unfortunately changing the rounding effect of the "-blur" in the above command tends to also vary the result highlight intensity of the shade image. That is using a large blur not only produces a well rounded looking edge, but also made the highlight so dim as to be near invisible.

This means that you need to add lots more contrast to the output of the "-shade" image produced, to make the highlight effective as an overlay image. To fix this we need a way remove this contrast effect from the rounding adjustment. The typical way to do this is to just "-normalize" the image, but doing this to 30 degree shade image, results in the 'flat' areas will no longer being a perfect grey. For example...

  convert -size 50x50 xc:black -fill white -draw 'circle 25,25 20,10' \
          -blur 0x2  -shade 120x30 -normalize   shade_30_norm.png
  convert shade_30_norm.png   -gravity center -crop 1x1+0+0 txt:-
[IM Image]
[IM Text]

After some further experimentation however I found that using a 21.78 degree shade elevation angle, will after being normalized, produce the desired perfect mid-tone grey level as well as a good strong highlighting effect.

  convert -size 50x50 xc:black -fill white -draw 'circle 25,25 20,10' \
          -blur 0x2  -shade 120x21.78 -normalize   shade_21_norm.png
  convert shade_21_norm.png   -gravity center -crop 1x1+0+0 txt:-
[IM Image]
[IM Text]

As the shade image is now run though the "-normalize" operator, the "-blur" value used for 'rounding edges' will no longer effect final intensity of the result. A much better method.

Now we can adjust the output intensity of the highlights produces output completely independent to the other adjustments. Typically as the normalized result is extreme, we will need a controlled de-normalization, or anti-contrast control, to reduce the highlight to the desired level.

The simplest method for adjusting the resulting highlight, is to color tint the image with a perfect grey. This will shift all the color levels in the image toward the central pure mid-tone grey color.

For example...

  convert -size 50x50 xc:black -fill white -draw 'circle 25,25 20,10' \
          \( +clone -blur 0x2 -shade 120x21.78 -normalize  \) \
          +swap +matte -compose CopyOpacity -composite  shade_tint_0.png
  convert shade_tint_0.png -fill grey50  -colorize 10%  shade_tint_10.png
  convert shade_tint_0.png -fill grey50  -colorize 30%  shade_tint_30.png
  convert shade_tint_0.png -fill grey50  -colorize 50%  shade_tint_50.png
  convert shade_tint_0.png -fill grey50  -colorize 80%  shade_tint_80.png
[IM Output] ==> [IM Output] [IM Output] [IM Output] [IM Output]

An alternative to just linearly tinting the highlight, is to reduce its general effect while preserving the extreme bright/dark spots of the highlight by using Sigmoidal Non-liner Contrast instead. This should give a more 'natural' look to the highlight effect, and can make the highlight brighter, as if the surface was more reflective.

However to make this technique more effective, we need make sure we do not have pure white and black colors in the shade result. This can be achieved by first using a "-contrast-stretch" of '0%' rather than "-normalize", and also de-normalizing that result by a small amount, as we did above.

This may seem to be just adding complexity to the generation of the highlight overlay image, but emphasizing the bright spots in the highlight makes the extra processing worth the effort.

For example...

  convert -size 50x50 xc:black -fill white -draw 'circle 25,25 20,10' \
          \( +clone -blur 0x2 -shade 120x21.78 -contrast-stretch 0% \) \
          +swap +matte -compose CopyOpacity -composite shade_sig_0.png

  convert shade_sig_0.png  -sigmoidal-contrast 10x50%  shade_sig-10.png
  convert shade_sig_0.png  -sigmoidal-contrast  5x50%  shade_sig-5.png
  convert shade_sig_0.png  -sigmoidal-contrast  2x50%  shade_sig-2.png

  convert shade_sig_0.png  +sigmoidal-contrast  2x50%  shade_sig+2.png
  convert shade_sig_0.png  +sigmoidal-contrast  5x50%  shade_sig+5.png
  convert shade_sig_0.png  +sigmoidal-contrast 10x50%  shade_sig+10.png
[IM Output] [IM Output] [IM Output] <== [IM Output] ==> [IM Output] [IM Output] [IM Output]

As you can see that the overall highlighting is reduced in intensity, but the bright spot from reflected light remains as bright as ever, just reduced in size. The result is a much more natural 'shiny' look to the shape.

The only drawback with this technique is that a shadow 'spot' is also generated though this is often not as noticeable.

Finally we can combine the a 'highlight spot' with a general highlight reduction to produce a highly configurable set of highlight overlay generator controls...

  convert -size 50x50 xc:black -fill white -draw 'circle 25,25 20,10' \
          \( +clone -blur 0x4 -shade 120x21.78 -contrast-stretch 0% \
             +sigmoidal-contrast 7x50% -fill grey50 -colorize 10%  \) \
          +swap +matte -compose CopyOpacity -composite shade_overlay.png
[IM Output]

In summary, the above example has four separate controls...
"blur" : Rounding the shape edges (0.001=beveled 2=smoothed 10=rounded)
"shade" : The direction the light is coming from (120=top-left 60=top-right)
"sigmoidal" : surface reflective control highlight spots (1=flat 5=good 10=reflective )
"colorize" : Overall contrast of the highlight ( 0%=bright 10%=good 50%=dim )

Note while the above examples have been shaped to the original 'circle' shape, the transparency should only be restored AFTER 'Overlay' compositing has been applied, not before.

Also if you plan to use a highlight repeatedly on the same shape (after any rotation is performed), you can pre-generate the highlight overlay once for each shape you plan to use, saving the result for multiple re-use.

I also highly recommend you experiment with the above techniques, as they are key to making your flat shaped images, much more realistic looking. If you come up with other ideas for highlighting, please let me know.

FUTURE:
   Color Tinting the Overlay image
   Overlay Alpha Composition with an Image

Using a Dawn Shade Highlight

In Masking Shade Images above we showed how useful a 'mid-day' or 'high-noon' shade image (using an elevation of '90'), can be useful for masking and location and extent of the effects produced by "-shade. However the horizontal or 'dawn' shade images (using an elevation of '0')of a shape can also be quite useful as well.

It can for example be used as a mask for either white or black images to generate separate highlight and shading effects on shapes. Not only that by doing this, I could ensure a shape gets roughly equal amounts of light and dark areas (or even unequal amounts), as I could produce them in the same way.

FUTURE: more detail here
See the first Advanced 3D Logo for an example of using this technique.


Using FX, The DIY Image Operator

The new IM version 6 image list operator "-fx" is a general DIY operator that does not fit into any specific category of IM operators, as it can be used to create just about any image operation. Examples of its use are thought these pages, but here we will look specifically at its capabilities and how you can use them.

The command is so generic in its abilities, that it can,

Of course many of these techniques are already part of IM, producing a faster and more flexible result. But if it isn't built-in the "-fx" allows you to generate your own version of the desired operation. In fact I and others have often used it to prototype new operations that are larger built into IM's core library. (See DIY New Ordered Dither Replacement).

The operator is essentially allows you to perform free-form mathematical operations on one or more images. For the official summary of the command see FX, The Special Effects Image Operator on the ImageMagick Web Site.

FX Basic Usage

The command takes a image sequence of as many input images you like. Typically one or two images, and replaces ALL the input images with a copy of the first image, which has been modified by the results of the "-fx" function. That is any meta-data that is in the first image will be preserved in the result of the "-fx" operator.

For mathematical ease of use, all color values are in the range 0.0 to 1.0. This includes the transparency or alpha channel, which goes from 0.0 (fully transparent) to 1.0, fully opaque. Note that this is the negative of how IM normally stores the transparency information, but is more mathematically correct.

The "-channel" setting defines what channel(s) in the first (also called the 'zeroth' or "u") image, is replaced with the result of the "-fx" operator. This is limited, by default, to just the color channels of the original image. any existing transparency in that image will not be modified, unless the "-channel" setting is changed.

The expression is executed once for each pixel, as well a once for each color channel in that pixel that is being processed. Also as the expression is re-parsed each time, a complex expression could take some time to process on a large image.

For example, here we define a black image, but then set the blue channel to be half-bright to form a 'navy blue' color instead.

  convert  -size 64x64 xc:black -channel blue -fx '1/2' fx_navy.gif
[IM Output]

And here we we take a black-white gradient, and then set the blue and green channels to zero, so it becomes a black-red gradient.

  convert  -size 64x64 gradient:black-white \
           -channel blue,green    -fx '0'    fx_red.gif
[IM Output]

To make the "-channel" setting more like the "-fx" operator, it will accept any combinations of the letters 'RGBA' to specify the channels to which operators are to confine their actions.

This means that to limit the output of "-fx" to just the blue and green channels you can now say "-channel BG" instead of the longer "-channel blue,green".

We could have generated the above examples without using "-fx", but being able to do this to an existing image is what makes this a powerful image operator.

The function can in fact read and use ANY pixel, or specific color from ANY of the images already in the current image sequence in memory. The first 'zero' image, is given the special name of "u". The second image "v". Other images in memory can be referenced by an index. As such "u[3]" is the fourth image in the current image sequence, while "u[-1]" is the last image in the sequence. This is the same indexing scheme used by the new image sequence operators, so you should be right at home.

If no other qualifiers are given, the color value used is same color used in the image specified. That is unless you specifically say you want to use the red color, it will use the color value for the color channel the command is processing at that time. That is it will apply the expression for the blue color value when it is processing the blue channel.

Same is true for the position of color pixel being processed. Unless told otherwise it will process the RGB color values for pixel 4,6, of the specified image, when calculating the results for pixel 4,6. However you can specifically use a pixel located at a specific coordinates, or when relative to the current pixel being calculated.

For example here we take the IM built-in "rose:" image and multiply all pixel values by 50%.

  convert  rose:  -fx 'u*1.5'    fx_rose_brighten.gif
[IM Output]

In the above example, each of the individual red, green and blue values (but not the alpha value due to the default setting of channel) was multiplied by 1.5. If the resulting value is outside the 0 to 1 range it, will be limited to the appropriate bound (1.0 in this case).

Lots of other "-fx" formulas to recolor images are explored in Mathematical Color Adjustments and histogram Curves .

As we can reference any image in the current image sequence we can merge two, or even more images, just about any way we want.

Here we generate a black-red-blue color chart image, by copying the blue channel from a black-blue gradient (rotated), into the previous black-red gradient we generated above.

  convert -size 64x64 gradient:black-blue -rotate -90  fx_blue.gif
  convert fx_red.gif  fx_blue.gif \
          -channel B  -fx 'v'    fx_combine.gif
[IM Output]  + [IM Output] ==> [IM Output]

Of course we could have just used a Channel Coping Composition Method instead which would be a lot faster.

Now the second image in the above is only used as a source image. What really happens is that "-fx" first creates a copy of the first image. It this modifies that image according to the formula and all the other images given. And finally it junks all the input images replacing them with the modified copy of the first image.

Not only can you reference other images, but you also can select any pixel from those images. The values 'i,j' is the current position of the pixel being processed, while 'w,h' gives the size of the image. As you only reference an un-modified source image you will not read the modified values.

For example you can easily make your own 'mirror image' type function (like the "-flop" image operator).

  convert  rose: -fx  'p{w-i,j}'  fx_rose_mirror.gif
[IM Output]

This type of 'image distortion' was made more powerful by creating Distortion and Displacement Maps, or Color Lookup Tables.

You can also use the pixel position generate completely new colors in your final image, to generate your own images and gradients. For example here we generate a different type of gradient image.

  convert  rose: -channel G -fx 'sin(pi*i/w)' -separate   fx_sine_gradient.gif
[IM Output]
The above can no be generated faster using a 'Sin' Evaluate Mathematical Operator. (See below).

When generating gray-scale gradients, you can make the -fx operator work about 3 times faster, simply by asking it to only process one color channel, such as the 'G' or green channel in the above example. This channel can then be Separated to form the required gray-scale image. This can represent a very large speed boost, especially when using a very complex "-fx" formula.

For more FX generated gradients, see examples Roll your own Gradients.

Now the size of the final image is the same as the first image given, as such to generate a larger image, you will need to set the first image to the size you want. For example here we generate a larger copy of the rose image.

  convert  rose:   -size 120x80 xc: +swap  \
           -fx 'v.p{  (i+.5)*v.w/w-.5, (j+.5)*v.h/h-.5  }' \
           fx_scaled.png
[IM Output]

Note how the pixel lookup is performed, it may seem complex but it is the proper way to scale an image from on size to another. This was actually used by me as a check on the methodology needed when I was initially developing the General Image Distortion Operator

One of the more interesting uses of the "-fx" operator is copying one color channel to another channel from the same or different images.

For example a user on the ImageMagick Mailing List wanted to be able to swap the red and blue channels of an image. This was the answer I worked out. See if you can figure out how it works.

  convert rose: \( +clone -channel R -fx B \) \
          +swap -channel B -fx v.R     fx_rb_swap.gif
[IM Output]

Again a faster and better way was later developed using "-separate" and "-combine"). See Combining RGB Channel Images.

Do you see a trend here?

The ability of the "-fx" operator to copy one color channel to other color channels, also allows far easier extraction of image masks. See Extracting a Mask Image for examples of this type of image manipulation.

As the default "-channel" setting, limits the output of the "-fx" operator to just the three color channels. This means that if you want to effect the alpha or transparency channel, you must explicitly specify it, by changing the channel setting.

For example lets make a semi-transparent "rose:" image, by setting all the alpha channel values to half.

  convert  rose: -alpha set  -channel A  -fx '0.5'    fx_rose_trans.png
[IM Output]

Note the for the above to work properly I needed to ensure that the "rose:" actually had a alpha channel for the "-fx" to work with. I did this with the Alpha Channel Control Operator.

This ability of the "-fx" operator to manipulate the RGBA channels of an image makes this operator perfect for manipulating Channels and Masks.


As of IM 6.2.10 you can add variable assignments to "-fx" expressions, which allows you to reduce the complexity of some expressions, that would basically be impossible any other way.

For example, here I create a gradient based on the distant from a particular point (assigned to the variables 'xx' and 'yy'). Without variables this formula could have become very complex.

  convert -size 100x100 xc:  -channel G \
          -fx 'xx=i-w/2; yy=j-h/2; rr=hypot(xx,yy); (.5-rr/70)*1.2+.5' \
          -separate  fx_radial_gradient.png
[IM Output]

Due the simple tokenization handling used by "-fx", variable names can only consist of letters, and must not contain numbers. Also as a lot of single letters are used for internal variables accessing image information, it is recommended that variable names be at least two letters long. As such I use 'xx' and 'yy' rather than just 'x' or 'y'.

The "-fx" function 'rr=hypot(xx,yy)' was added to IM v6.3.6 to speed up the very commonly used expression 'rr=sqrt(xx*xx+yy*yy)'.

Of course if you need the distance squared, you should avoid the 'hypot()' function, and the sqrt() function it implies.

For more examples of some really complex expressions see More Complex DIY Gradients, which would be impossible with out multiple statement assignments. The same is true for DIY Perspective Transform.

As of IM version 6.3.0-1, the complexity of "-fx" expressions started to require external files, so the standard '@filename' can now be used to read the expression from a file.

    echo "u*2" | convert rose:  -fx "@-"  fx_file.png
[IM Output]

This also means you can use more complex scripts to generate the specific FX expressions for a particular job. Internally the file is simply read into a string and interpreted as usual.

Other settings that are important to "-fx" are "-virtual-pixel" and "-interpolate".

The Virtual Pixel Setting allows one to set what colors or image results should be returned when the lookup coordinates go outside the area covered by the input image. This allows one to set edge effects for things like blurs, as well as tile image over a larger area.

The Interpolate Setting allows one to specify how IM should mix colors of neighbouring pixels when the lookup coordinates (floating point values) fall between the integer coordinates of the pixels in the input image. For more information see Interpolated Pixel Lookup.

FX Debugging

The 'debug(expr)' is essentially a way of printing a floating point value, each time the FX expression is calculated. This in turn provides a method of debugging your expressions.

However you can limit the output from the "debug()" by using a tertiary if-else expression. For example this will print the floating point color values for pixel 10,10 from the built-in "rose:" image. The actual image result is ignored by using the 'NULL:' image handler.

  convert rose: -fx 'i==10&&j==10?debug(u):1; u' null:
[IM Output]

Remember the output is on standard error, not the normal standard output, that way you can use this in a command pipeline, without problems.

Note how the FX expression was executed three times, once for each channel for just that one pixel. Multiply that by the number of pixels, and you can imagine the length of the output if "debug()" was not limited to just one pixel, even for this small image.

FX-like Built-in Operations

Well "-fx" operations is slow, very slow when processing large images with very complex expressions. However it represents a way to develop new image processing functions that currently does not exist in ImageMagick. The result of such development by users has allows ImageMagick to expand, with new functions and methods, such as the Color Lookup Table ("-clut").

Generally however once a new method has stabilized using "-fx", the expression is converted into a faster built-in operation, usally into one of a grouped of 'FX-like' operators.

These include the follow general image operator and there methods...
-evaluate Direct pixel/color/channel modification functions.
(See Evaluate below).
-function More Complex pixel/color/channel modification functions.
(See Function below).
-sparse-color General Image Coloring Method
(See Sparse Color Gradients )
-compose General Multi-Image combining and overlaying.
(See Alpha Composition).
-distort General Image Distortion functions, based on moving lookup points.
(See the Distort Operator )
Future: Some type of general convolution/morphology operator

As people develope new types of image operations, they usally prototype it using a slow "-fx" operator first. When they have it worked out that 'method' is then converted into a new fast built-in operator in the ImageMagick Core library. One example of this was the Color Lookup Table or "-clut" operator.

Users are welcome to contribute their own "-fx" expressions (or other defined functions) that they feel would be a useful addition to IM, but which are not yet covered by other image operators, if they can be handled by one of the above generalized operators, it should be reasonably easy to add it.

For example I myself needed a 'mask if color similar' type operation for comparing two images. This has been added as a new "-compose" method "ChangeMask". This in turn allowed me to then add a more complex Transparency Optimization for GIF animations.

What is really needed at this time is a FX expression compiler, that will pre-interpret the expression into a tighter and faster executable form. Someone was going to look into this but has since disappeared.

However if speed and complexity is starting to become a problem then it is probably better to move on to a API scripting language such as PerlMagick. An example of this using PerlMagick ""pixel_fx.pl" is part of that API's distribution.


FX Expressions as Format and Annotate Escapes

As of IM version 6.2.10 you can now use FX Expressions within Image Property Escaped strings such as used by "-format" and "-annotate" arguments.

The escape sequence '%[fx:...]' is replaced by a number as a floating point value, calculated once for each image as they are processed.

  convert xc: -format '%[fx:atan(1)*4]' info:
[IM Output]

Will mathematically calculate and return the value of PI, though this value is available as the built-in variable 'pi'.

Also see Border with Rounded Corner which used a FX Expressions to generate a draw string based on image width and height information.

For each image in the current image sequence the given FX expression is expanded once and only once per image. However there are some slight modifications to built-in variables, as it is is not actually being used to generating a new image. Specifically...

For example here I "-annotate" each image with the color of the top left corner of each image.

  convert -size 100x25 xc:red xc:green xc:blue   -gravity center \
          -annotate 0 '%[fx:t] : %[fx:r],%[fx:g],%[fx:b]' \
          annotate_fx_%d.gif
[IM Output] [IM Output] [IM Output]

Notice how the text that is written is different for each image, as 'r' is actually equivalent to 's.p{0,0}.r'. The same goes for the 'g' and 'b' color channel values. Of course each one returns a normalized value in the range of 0.0 to 1.0.

To make the output of specific pixel color values easier, a '%[pixel:...]' escape was also added in IM v6.3.0. This operator calls the given FX expression once for each channel in each image, and formats the returned value into a color that IM can handle as a color argument.

  convert -size 300x60 gradient:yellow-limegreen \
          -gravity NorthWest  -annotate 0 '%[pixel:s.p{0,0}]' \
          -gravity SouthEast  -annotate 0 '%[pixel:s.p{99,99}]' \
          -gravity Center     -annotate 0 '%[pixel:s.p{50,50}]' \
          annotate_pixel.gif
[IM Output]

Fx can be applied to images in other colorspaces, so I can for example find out the 'Hue' value (in the 'red' channel) for three different colors.

  convert xc:red xc:green xc:blue -colorspace HSL \
          -format '%[fx: s.r ]' info:
[IM Output]

It means also you can use IM for some direct color maths, such as the average color of 'gold', 'yellow', and 'khaki'.

  convert xc: -format '%[pixel:(gold+yellow+khaki)/3]' info:
[IM Output]

Accessing data from other images

There is one serious problem with using FX escaped expressions however. IM does not have direct access to the other images in the current image sequence when you are creating images. This is just generally not needed, in typical image creation, as new images generally do not depend on previous in-memory images.

Basically if you want to gather the color of a specific pixel in a different image to the one you are drawing on (as above), or are creating a new image, then the IM core functions have no direct link to the desired info.

For example if you try to create a label with the color of the built-in "rose:" image pixel 12,26 (a bluish pixel), the direct approach will fail!

  convert rose: label:'%[pixel:u[0].p{12,26}]' -delete 0 label_fx_direct.gif
[IM Output]

Well obviously the rose image does not contain any black pixels! so the above result was wrong.

The way to fix this is to extract the wanted information and save it into the global IM meta-data. This is passed to all sub-routines in the library core, including those for image creation.

  convert rose:  -set option:mylabel '%[pixel:u[0].p{12,26}]' \
          label:'%[mylabel]'   -delete 0    label_fx_indirect.gif
[IM Output]

This is not intuitive but we now get the correct result.

The special 'option:' tag, tells IM to add the given setting to the global 'ImageInfo' structure, rather than the individual images meta-data. The percent escape searches both structures for matches.

It is a bit like 'exif:' tag to define the option as a special digital photo EXIF meta-data.

For API's this situation can be avoided as you can read the required data and then pass it directly to the required functions without needing to get IM to store that information between operations.


Evaluate and Function, Freeform Channel Modifiers

Because the FX Operator (see above) is so slow, the "-evaluate" operator was added to let you make simple image modifications more quickly.

Later a more complex "-function" operator was added in IM v6.4.8-8, to allow greater flexibility in complex image adjustments. These two operators, along with other Image Level Adjustment Operators such as "-negate", "-level", will probably be most useful for minor tweaks to grey-scale images, before you apply those images.

Especially in gray-scale images such as used for Image Masking and Background Removal, Highlight and Shadow Overlays, and the generation and fine-tuning of Displacement Maps.

Evaluate, Simple Math Operations

The "-evaluate" operator is basically a fast, but very simple version of "-fx" operator (actually pre-dates its addition to IM by just a cuple of months). However it is limited to just one simple operation, using one user provided constant argument.

You can find out what functions have been built into evaluate using

  convert -list evaluate

This includes the typical mathematical functions 'add', 'subtract', 'multiply', and 'divide'.

Unlike the -fx operator the values are not normalised to a 0 to 1 range, but remain the real color values of the image. As such subtracting a value of 50 in a Q8 IM (See Quality and Depth will result in a large subtraction, but for a Q16 version of IM, it will only be a small hardly noticeable change.

However if you add a '%' to the argument, that argument will represent a percentage of the maximum color value (known as 'QuantumRange' which is equal to ('2quality-1'). This means you can make your "-evaluate" arguments IM quality level independent, by the appropriate use of percentages for the appropriate evaluate methods.

For example, to half the contrast of the image, you can 'divide' it by '2' then 'add' '25% to re-center it around a the perfect grey.


  convert rose:  -evaluate divide 2 -evaluate add 25%  rose_de-constrast.gif
[IM Output]

This is a couple of orders of magnitude faster than directly using the "-fx" operator with 'u/2+.25'. As such you should use this operator in preference to "-fx" if at all possible.

The major problem with "-evaluate" is that all results are clipped to the 0 to 'QuantumRange' limits (unless you are using a HDRI version of ImageMagick), as each modified value is saved back into the image data.

As such this contrast enhancement function (equivalent to "-fx '2*u-.25' ") will fail, as the doubled value will be clipped, before the subtraction is made.

  convert rose:  -evaluate multiply 2  -evaluate subtract 25% \
          rose_contrast.gif
[IM Output]

First the 'multiply' will clip all the large color values to the maximum value, then the 'subtract' will clip the lower bound values again, producing the wrong result.

The direct solution is to subtract the appropriate constant first, before multiplying, effectively using the equivalent formula '(u-.125)*2'

  convert rose:  -evaluate subtract 12.5%  -evaluate multiply 2 \
          rose_contrast2.gif
[IM Output]

However there are lots of alternatives to this 'clipping' problem. The first logical one being the newer Polynomial Function Method (see below). Other less direct alternatives also include using Level Adjustment Operators or even a Level Adjustment by Color.

Basically be careful with regards to color value clipping when using "-evaluate".


The "-evaluate" operator, like "-fx" (and most other low level IM operators) is "-channel" effected. This allows you to control an images alpha transparency separately to the color channels.

And yes, like "-fx", transparency is treated as 'alpha values' and not a 'matte' value.

For example to make an image 50% transparent, as part of a Dissolve type operation.

  convert rose: -alpha set  -channel A -evaluate divide 2   rose_transparent.png
[IM Output]

Often I have also found that it is often easier to use "-evaluate" on the individual color channels before separating the various channels into separate images for specific purposes, (See Separating Channels).

For example here I use it to do a fast, but unusual form of gray-scaling. Basically I multiply each channel by the appropriate amount, then separate and add the channels together to produce a image that has been gray-scaled using a specific set of color ratios.

  convert rose: -channel R  -evaluate multiply .2 \
                -channel G  -evaluate multiply .5 \
                -channel B  -evaluate multiply .3 \
                +channel -separate \
                -background black -compose plus -flatten grey_253.png
[IM Output]

Evaluate Math Functions

Included in Evaluate beyond just the basic mathematical operators, there are also a set of special purpose mathematical functions.

These functions are implemented to generally use a normalized color value (0 to 1 range) with the output again normalized so as to fit the full color range of the image. The Sigmoidal Contrast function is also an example of this math function fitting.


The 'Pow' function (added IM v6.4.1-9) for example works with normalized color values, and allows users to do image brightness modifications. This is actually equivalent to the "-gamma" operator. For example " -gamma 2" would be equivalent to "-evaluate pow 0.5" or a 'square root' function (gamma inverts the value given).

It is equivalent to the pow() C function, (using normalize color values)
    value = pow(value, constant) 

The 'Log' function (added IM v6.4.2-1) also works with normalized values (with a 1.0 added to avoid infinities), with the given constant being used as the logarithmic base. The actual formula (with normalized values) is thus...
    value = log(value*constant+1.0)/log(constant+1.0) 

As of IM v6.4.8-8 the 'cos' and 'sin' methods were added. These methods take the value given in the image and normalize it into a angle so the full range will cover a full circle of angles. The result is given a 50% bias and scaled to again fit into the normal range of values. The constant is used as a multiplier for the value (and thus the angle) so that 'N' means the function will go around the circle 'N' times over the full value range.

Specifically it defines these function (using normalized values) as...
   value = 0.5 - 0.5 * cos( constant*value*2*PI )
   value = 0.5 + 0.5 * sin( constant*value*2*PI ) 
In essence what these functions do is re-map the image values (usually gray-scale values) into a sine/cosine curve.

For example here I take a gradient image and modify it using these evaluate methods.

  convert -size 20x640  gradient: -rotate 90  gradient.png
  convert gradient.png  -evaluate cos 1  eval_cos_1.gif
  convert gradient.png  -evaluate sin 1  eval_sin_1.gif
[IM Output] [IM Output] [IM Output]

Now as the constant parameter is a angle multiplier, the cosine value remapping essentially will create that many peaks' over a full gradient.

  convert gradient.png  -evaluate cos 5   eval_cos_5.gif
[IM Output]

This is perfect for many tasks, from generating ripple or dispersion effects to generate ripple looking displacement curves.

By using a multiplier constant of '0.5' you can simply convert a linear gradient into a sine curve gradient, which still has the same slope as the original. By negating the result you can ensure that the gradient also slopes correctly.

  convert gradient.png  -evaluate cos 0.5  -negate  eval_cos.5.gif
[IM Output]

Which is great generating smooth gradients for use in overlapping photos.

Function, Multi-Argument Evaluate

The above wave generators proved to be extremely useful, especially with Displacement Maps, that a much finer control of the functions was needed, using multiple user parameters. Also the the ability to use a polynomial expression to control how gray-scale images were modified.

These operators like the mathematical operators above work on the normalised channel values of the image.

Because of this the "-function" operator was added in IM v6.4.8-9.

Sinusoid Function

The 'Sinusoid' function method is a much more advanced version of the "-evaluate" methods 'sin' and 'cos', and can in fact replicate those functions, but you have much better controls over how it modifies the color values in an image.

-function   Sinusoid   frequency,phase,amplitude,bias
Only the first value 'frequency', which works exactly as per above, is required with all the other parameters being optional.

By default it will generate a Sine Curve.

  convert gradient.png -function Sinusoid 1    func_sine.gif
[IM Output]

By adding a 'phase' argument in degrees, you can specify the starting angle for the curve. For example here I make a smooth Sinusoidal gradient going from black to white (minimum to maximum along a Sine curve)

  convert gradient.png -function Sinusoid 0.5,-90    func_sine_grad.gif
[IM Output]

Or a simple set of 5 bands, using a negated 'cosine' curve.

  convert gradient.png -function Sinusoid 5,-90    func_bands.gif
[IM Output]

The next two optional values, 'amplitude' and 'bias' controls the scale and center-line of sinusoidal curve. For example, here I make a wave that oscillates Between white and gray, starting and finishing on white.

  convert gradient.png -function Sinusoid 5,90,.25,.75    func_bias.gif
[IM Output]

Becareful with these last parameters as they could easy cause the waveform to exceed the bounds of the color value range, and thus be clipped (unless you are using a HDRI version of ImageMagick).


Polynomial Function

The 'polynomial' method will take any number of values, and will modify the color values in an image according the exact expression given, much faster than the FX Operator can.

-function   Polynomial   a,b,c,...
Each value will be used as a coefficient from the highest order to the lowest, to produce a polynomial with the number of terms given.

For example a argument of '4,-4,1' will generate the polynomial expression equivalent to the "-fx" expression " 4*u^2 - 4*u + 1 ".

If you know your high school maths you should know then that produces a parabolic curve going from 1.0 to 0.0 then back to 1.0, over the color range 0.0 to 1.0. That is it will make, black and white colors 'white', and make perfect grays, 'black'.

  convert gradient.png -function Polynomial 4,-4,1    func_parabola.gif
[IM Output]

Of course simple linear modification is also possible, exactly as you get if you used a Level Operator...

  convert gradient.png -function Polynomial 2,-.5   func_linear.gif
[IM Output]

Note however that you can not use 'Polynomial' to do a Threshold operation, due to the need for infinite coefficients to do so.

A single value naturally is just like an assignment, or the "-evaluate 'set' " method.

  convert gradient.png -function Polynomial 0.33   func_constant.gif
[IM Output]

The 'polynomial' method will be of particular interest to users who want to do Curve Level Adjustments, where you convert a set of 'level control points' into a polynomial expression to color adjust a whole image.


Using Mathematics on Gradient Images

Now the above functions provide some very basic transformations for gradient images. But you can also generate new gradient transforms by repeated applying different Evaluate and Function methods, or by using some of the Mathematical A Compose Methods (such as Plus and "Divide), or even just some basic Image Level Adjustments (such as Negate, Level, Normalize, and Sigmoidal Contrast).

For example, here I want to create a sine wave, but one that starts out small, but then gets larger in amplitude.

So first the sine wave, which we generate from a linear gradient...

  convert -size 5x300 gradient: -rotate 90   math_linear.png
  convert math_linear.png  -evaluate sine 12  math_sine.png
[IM Output] ==> [IM Output]

ASIDE: The graph images show the gradient at the bottom, and a profile graph of the intensity values in the image at the top. It was generated using the Gnuplot graph ploting program, via the script "im_profile" in the IM Examples, Scripts directory.

Now to attenuate this we multiply the sine wave with a linear gradient, using a Multiply alpha composition...

  convert math_sine.png  math_linear.png  \
          -compose Multiply -composite  math_sine_2.png
[IM Output] X [IM Output] ==> [IM Output]

But to use this in say a Water Ripples, Displacement Map the wave must remain centered around a perfect gray. To do this we need to add a bias to the original image. This happened to be negated the function we used to multiply the original image, and divided that by two....

  convert  math_linear.png -negate -evaluate divide 2  math_bias.png
  convert  math_sine_2.png  math_bias.png \
          -compose Plus -composite  math_attenuated.png
[IM Output] X [IM Output] ==> [IM Output]

And so we have a linearly attenuated Sine Wave Gradient.

Of course you can do the whole process all in the one command, and it does not have to be a simple linear attenuation either.

For example here I attenuate the high fequency Sine wave, using a negated Cosine wave, instead oa a linear gradient.

  convert math_linear.png  -evaluate cos 1 -negate  math_cosine_peak.png
  convert math_sine.png  math_cosine_peak.png \
          \( -clone 0,1 -compose multiply -composite \) \
          \( -clone 1 -negate -evaluate divide 2 \
             -clone 2 -compose plus -composite \) \
          -delete 0-2    math_cosine_atten.png
[IM Output] attenuate [IM Output] ==> [IM Output]


Note however that applying a function directly to the output of another function does NOT produce a simple result. The reason is that all these math functions are applied to the gradient 'value' of individual pixels.

For example....

  convert gradient.png  -evaluate sin 0.5 -normalize \
                        -evaluate cos  12  math_cos_var.gif
[IM Output]

Generates a very complex function that is essentually equivelent to
cos( 12 * sin( {value}/2 ) )
In other words a variable frequency, where the frequency varies with the gradient of the first sin curve.

Basically faster the gradient change in the original image the smaller the distance between the peaks. However the height (amplitude) of the peak do not vary.

This is actually how 'Frequency Modulation' works, where a seemingly simple function produces a very complex result.



Miscellaneous Image Transformation Techniques.

These have not been exampled yet, but are some basic IM developed transforms
that may provide useful.  If you have an interesting effect please contribute.

   pixelize an image
      resize an image down 10 then scale the image 10 to produce blocks
      of roughly averaged color.
      For example...
         convert input.jpg -resize 10% -sample 1000% output.jpg

  Deskew

    -deskew {threshold}
       straighten an image. A threshold of 40% works for most images.

    Use -set option:deskew:auto-crop {width} to auto crop the image. The set
    argument is the pixel width of the image background (e.g 40).

    Programmically we auto crop by running a median filter across the image
    to eliminate salt-n-pepper noise.  Next we get the image bounds of the
    median filter image with a fuzz factor (e.g. -fuzz 5%).  Finally we
    crop the original image by the bounds.  The code looks like this:

      median_image=MedianFilterImage(image,0.0,exception);
      geometry=GetImageBoundingBox(median_image,exception);
      median_image-DestoryImage(median_image);

      print("  Auto-crop geometry: %lux%lu%+ld%+ld",
                geometry.width,geometry.height, geometry.x,geometry.y);
      crop_image=CropImage(rotate_image,&geometry,exception);

    See Trimming 'Noisy' Images

  Segmentation
    look at scripts
       divide_vert
       segment_image
    for some simple scripts I wrote to segment well defined images into
    smaller parts.   I hope to get simple segmentation functions like this
    into the core library, to allow for things like automatic sub-division of
    GIF animations, and seperating images and diagrams from scanned documents.



Created: 15 March 2004
Updated: 9 January 2009
Author: Anthony Thyssen, <A.Thyssen@griffith.edu.au>
Examples Generated with: [version image]
URL: http://www.imagemagick.org/Usage/transform/