ImageMagick Examples --
Image Transformations
- Index
-
ImageMagick Examples Preface and Index
-
Art-like Transformations
- Turn images into raised or sunken buttons
- Adding an Inside Border
- Random Pixel Spread
- Vignette photo transform
- Complex Polaroid Transform
- Oil Painting, blobs of color
- Charcoal, artists sketch of a scene
- Pencil Sketch Transform
- Emboss, creating a metallic impression
- Stegano, hiding a secrets within an image
- Encrypting Image Data
- Pixelate Images
- Grids of Pixels
- Spacing out Tiles
-
Computer Vision Transformations
-
Shade 3D Highlighting
-
Using FX, the DIY Image Operator
-
Evaluate and Function, Fast FX Operators
-
Mathematics on Gradients
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.
|
![]() |
|
![]() |
-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 an 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.
|
![]() |
-strokewidth
" of the rectangle. That is
{stroke width} = {border width} * 2 - 1
-strokewidth
" of 11. If you don't know the size of the image, then you can Shave the image then add the Border as normal. This is probably easier, though prehaps not as versatile.
|
![]() |
Random Pixel Spread
The "-spread
" would replace each pixel the color of a random nearby color from the source image. This random selection was made as per the use of Pixel Interpolation and Virtual-Pixel Setting.
For example...
|
![]() |
Also note that "-spread " also makes use of the Virtual-Pixel Setting.
|
![]() |
-interpolate Nearest
". To avoid the problems with virtual pixels and posible 'edge color bias', I recommend you use "-virtual-pixel Mirror
". As such this is a more traditonal random 'spread' of pixels...
|
![]() |
![](../img_www/const_barrier.gif)
![](../img_www/const_hole.gif)
+spread
" to actually swap pixels within the image, meaning that no pixel in the image will be duplicated or lost. Every pixel in the original image is still present, just displaced to to new location.However due to the way pixels are processed, pixels may be 'double-swapped'. That is, a specific pixel may be swapped, but then selected to be swapped again with a later pixel. That means a specific pixel could drift further than was requested by the spread argument. This double swapping also mean that pixels were likely to spread further toward the lower right corner. That movement is of course balanced but a smaller drift of a large number of pixels toward the upper-left.
For example, here I spread pixels with the original prepended as a referance.
|
![]() |
|
![]() |
Vignette Photo Transform
A special operator to make an image circular with a soft blurry outline.
|
![]() |
|
![]() |
|
![]() |
|
![]() |
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!.
|
![]() |
![]() ![]() |
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. |
-bordercolor
" setting), 'curls' the paper, and adds an 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.
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.
|
![]() |
The other standard text settings (as per "caption: "), allows you to control the look of the added caption.
|
![]() |
![]() ![]() |
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. |
-density
". Do not increase the fonts "-pointsize
" as that does not enlarge the text in quite the same way.
|
![]() |
Oil Painting, blobs of color
The "-paint
" operator is designed to magick 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.
magick rose: -paint 1 rose_paint_1.gif magick rose: -paint 3 rose_paint_3.gif magick rose: -paint 5 rose_paint_5.gif magick rose: -paint 10 rose_paint_10.gif magick rose: -blur 0x3 -paint 10 rose_blur_paint_10.gif |
![[IM Output]](../images/rose.gif)
![==>](../img_www/right.gif)
![[IM Output]](rose_paint_1.gif)
![[IM Output]](rose_paint_3.gif)
![[IM Output]](rose_paint_5.gif)
![[IM Output]](rose_paint_10.gif)
![==>](../img_www/right.gif)
![[IM Output]](rose_blur_paint_10.gif)
-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? There are alternative to using "-paint
". One is to use "-statistic Mode
" instead, which assigns each pixel with the 'predominate color' within the given rectangular neighbourhood, and can produce a nicer result.
|
![]() |
|
![]() |
|
![]() |
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 magick 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.
magick rose: -charcoal 1 rose_charcoal_1.gif magick rose: -charcoal 3 rose_charcoal_3.gif magick rose: -charcoal 5 rose_charcoal_5.gif |
![[IM Output]](../images/rose.gif)
![==>](../img_www/right.gif)
![[IM Output]](rose_charcoal_1.gif)
![[IM Output]](rose_charcoal_3.gif)
![[IM Output]](rose_charcoal_5.gif)
![]() ![]() |
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 an 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.
magick rose: -emboss 0x.5 rose_emboss_0x05.gif magick rose: -emboss 0x.9 rose_emboss_0x09.gif magick rose: -emboss 0x1 rose_emboss_0x10.gif magick rose: -emboss 0x1.1 rose_emboss_0x11.gif magick rose: -emboss 0x1.2 rose_emboss_0x12.gif magick rose: -emboss 0x2 rose_emboss_0x20.gif |
![[IM Output]](../images/rose.gif)
![==>](../img_www/right.gif)
![[IM Output]](rose_emboss_0x05.gif)
![[IM Output]](rose_emboss_0x09.gif)
![[IM Output]](rose_emboss_0x10.gif)
![[IM Output]](rose_emboss_0x11.gif)
![[IM Output]](rose_emboss_0x12.gif)
![[IM Output]](rose_emboss_0x20.gif)
magick rose: -colorspace Gray -emboss 0x.5 rose_g_emboss_0x05.gif magick rose: -colorspace Gray -emboss 0x.9 rose_g_emboss_0x09.gif magick rose: -colorspace Gray -emboss 0x1 rose_g_emboss_0x10.gif magick rose: -colorspace Gray -emboss 0x1.1 rose_g_emboss_0x11.gif magick rose: -colorspace Gray -emboss 0x1.2 rose_g_emboss_0x12.gif magick rose: -colorspace Gray -emboss 0x2 rose_g_emboss_0x20.gif |
![[IM Output]](../images/rose_grey.gif)
![==>](../img_www/right.gif)
![[IM Output]](rose_g_emboss_0x05.gif)
![[IM Output]](rose_g_emboss_0x09.gif)
![[IM Output]](rose_g_emboss_0x10.gif)
![[IM Output]](rose_g_emboss_0x11.gif)
![[IM Output]](rose_g_emboss_0x12.gif)
![[IM Output]](rose_g_emboss_0x20.gif)
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. First a warning...
Do not use JPEG, GIF, or any other 'lossy' image encoding with Stegano
For example, lets generate a cryptic message (image) that you want to send to your fellow spy...
|
![]() |
||
|
|
![]() |
|
![]() |
|
![]() |
||
|
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) for the container image. Also if you had the wrong 'offset key' you will not get the message...
|
![]() |
Encrypting Image Data
The operators "-encipher
" and "-decipher
" will basically encrypt image data into a garbled mess. That is, the image content itself is no longer recognisable at all 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. But first a warning...
Do not use JPEG, GIF, or any other 'lossy' image encoding with Encryption
For example lets encrypt that secret message image we created above, using a pass-phrase I have saved in a, not quite so 'secret', file "pass_phrase.txt".
![]() ![]() |
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.The " png24 " was also needed in the above to ensure that the output is not a palette or colormapped "png8:" image, which also does not work properly. |
|
![]() |
echo "password" | magick message.gif -encipher - \ -transpose -depth 8 png24:message_obfuscate.png echo "password" | magick message_obfuscate.png -transpose \ -decipher - message_restored_2.png |
![[IM Output]](message.gif)
![==>](../img_www/right.gif)
![[IM Output]](message_obfuscate.png)
![==>](../img_www/right.gif)
![[IM Output]](message_restored_2.png)
-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 just a "-roll
" of some sort 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 "magick
" 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 reference image. For instance here I use the signature of the "rose.gif
" image to encrypt and later decrypt the "message.gif
" image.
magick identify -format %# rose.gif |\ magick message.gif -encipher - -depth 8 png24:message_signed.png magick identify -format %# rose.gif |\ magick message_signed.png -decipher - message_restored_3.png |
![[IM Output]](message.gif)
![+](../img_www/plus.gif)
![[IM Output]](../images/rose.gif)
![==>](../img_www/right.gif)
![[IM Output]](message_signed.png)
![+](../img_www/plus.gif)
![[IM Output]](../images/rose.gif)
![==>](../img_www/right.gif)
![[IM Output]](message_restored_3.png)
-encipher
" and "-decipher
" can be a binary file. As such you could even directly use an image itself as the the passphrase.
magick message.gif -encipher rose.gif -depth 8 png24:message_binary.png magick message_binary.png -decipher rose.gif message_restored_4.png |
![[IM Output]](message.gif)
![+](../img_www/plus.gif)
![[IM Output]](../images/rose.gif)
![==>](../img_www/right.gif)
![[IM Output]](message_binary.png)
![+](../img_www/plus.gif)
![[IM Output]](../images/rose.gif)
![==>](../img_www/right.gif)
![[IM Output]](message_restored_4.png)
![]() ![]() |
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. |
![]() ![]() |
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. |
Pixelate an Image
Pixelating an image is basicaly used to magick an image into a set of large colored 'pixels' that only shows a vague outline of the original image. Both techniques involve shrinking the image (to generate fewer pixels), then enlarging them in such a way so as to create 'pixel block' using either a Scaling Operator or Sampling Operator to generate the block of color. It is just how the image is reduced that determines exactly what color wil be used. A single pixel sample, or a merged average color.
magick rose: -sample 25% -scale 70x46\! rose_pixelate_sampled.gif magick rose: -scale 25% -scale 70x46\! rose_pixelate_scaled.gif magick rose: -resize 25% -scale 70x46\! rose_pixelate_resized.gif |
![[IM Output]](../images/rose.gif)
![==>](../img_www/right.gif)
![[IM Output]](rose_pixelate_sampled.gif)
![[IM Output]](rose_pixelate_scaled.gif)
![[IM Output]](rose_pixelate_resized.gif)
Grids of Pixels
Gridding an image is very similar to pixelating an image. In this case we want only want to enlarge the image, to generate distinct pixel-level view of an image's details. Typically a very small image. The simplest way is like the previous example, simply Scale a small image, to enlarge the pixels.
magick rose: -crop 10x10+12+20 +resize grid_input.png magick grid_input.png -scale 1000% grid_scale.png |
![[IM Output]](grid_input.png)
![==>](../img_www/right.gif)
![[IM Output]](grid_scale.png)
Here we generate a white on black 'grid' which is overlayed using Screen Composition (overlay white, while leaving black areas as-is).
|
![]() |
scale*image_size+gap_size
(in this case 10*10+1 => 101). I also Swapped the two images so that the final image size comes from the tile image, rather than from the scaled image which is one pixel smaller in size. However this may lose any image meta-data that was in the original image as I used the tiled image for the destination.
Here I generate circular 'spots' of color, but this time used a Multiply Composition (overlay black, while leaving white areas as-is).
|
![]() |
For example, here I use Basic Morphology Operator to generate a diamond shaped 'holes' in a colored overlay. For this a single 'seed' pixel is drawn and expanded using a Diamond Morphology Kernel.
|
![]() |
![]() ![]() |
Note that the "tile: " coder will replace any transparency in the image with the current background color. If you want to preserve the transparency of the tiling image, either set "-background none " or "-compose Src ". The former is easier. |
Spacing Out Tiles
A similar problem is spacing out a grid of tiles in an image. this is not simply scaling up individual pixels into 'pixel blocks' but inserting space between rectangluar areas of an image. That is, Splicing extra pixels into an image at regular intervals. Currently the best solution is to break up the image into Rows and Columns and Splicing in the extra spacing onto each tile before Appending the tiles back together. For example...
magick rose: -background SkyBlue \ -crop 10x0 +repage -splice 3x0 +append \ -crop 0x10 +repage -splice 0x3 -append \ grid_tile.png |
![[IM Output]](../images/rose.gif)
![==>](../img_www/right.gif)
![[IM Output]](grid_tile.png)
|
![]() |
3
' in the above is the gap width to add, and '10
' is the tile size. All that you need is to add a border or other edge to the result. With a little more work you can even add some random 'jitter' to the placement of each tile in the above grid, for a less regular effect. The problem with both these methods is that generating lots of small images, only to join them back together does generate a lot of work. Especially for very small tiles sizes. A better method that has been proposed is a special extension to the Splice Operator, in the IM Forum Discussion Splice (adding tile gridding gaps).
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.
magick mask.gif -edge 1 mask_edge_1.gif magick mask.gif -edge 2 mask_edge_2.gif magick mask.gif -edge 3 mask_edge_3.gif magick mask.gif -edge 10 mask_edge_10.gif |
![[IM Output]](mask.gif)
![==>](../img_www/right.gif)
![[IM Output]](mask_edge_1.gif)
![[IM Output]](mask_edge_2.gif)
![[IM Output]](mask_edge_3.gif)
![[IM Output]](mask_edge_10.gif)
-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.
However by negating the image before doing the edge detecting, the twined lines go inward and join together, removing the 'twin line' effect.
|
![]() |
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.
|
![]() |
Canny Edge Detector
As on IM v6.8.9-0, IM now supports the canny edge detector. (See Announcment Examples on the IM Forum). This is a very advanced edge detection algorithm, that produces a very strong (binary) single pixel wide lines at all sharp edges, with very little noise interferance. For example, here we apply it to the test images we used above..
magick mask.gif -canny 0x1+10%+30% mask_canny.gif magick piglet.gif -canny 0x1+10%+30% piglet_canny.gif magick piglet.gif -negate -canny 0x1+10%+30% piglet_canny_neg.gif magick rose: -canny 0x1+10%+30% rose_canny.gif |
![[IM Output]](mask.gif)
![==>](../img_www/right.gif)
![[IM Output]](mask_canny.gif)
![[IM Output]](piglet_canny.gif)
![[IM Output]](piglet_canny_neg.gif)
![[IM Output]](rose_canny.gif)
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 '(
' ).
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
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 an aliased bitmap, to simulate a horrible bitmap image downloaded from the network. Such as the outline of a GIF image containing transparency.
|
![]() |
|
![]() |
A negated edge generates an edge image but for the inside of the black area.
|
![]() |
|
![]() |
EdgeIn
' morphology method, or others like it.
|
![]() |
-filter Cubic
" setting, or some other Resampling Filters.
|
![]() |
0.7
' about the best, with a 3 pixel limit to speed things up.
|
![]() |
-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 an 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.
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 magick this bitmap shape into a vector outline. Programs that can do this include: "ScanFont
", "CorelTrace
", "Streamline
" by Abobe, and "Vector Magic
". Most of these however cost you at least some money. "VectorMagick" and another tracing program "AutoTracer" have free to use online image converters available. Other free solutions are "AutoTrace
", or "PoTrace
". More suggestions are welcome. These trace programs are simple to use, but typically requires some form of 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 data, and thus 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 an image format autotrace understands. I can then magick that image into a SVG vector image.
|
![]() |
![]() |
|
![]() |
![]() |
autotrace:
" image input delegate. This only requires the "autotrace
" command to be installed. For example
|
![]() |
AutoTrace
" delegate library, you can also have IM directly generate the SVG image from an image in memory. For details of this see SVG Output Handling. For example....
Now the SVG output will of course represent a smoothed version of the original image, which is not what we actually want in this example. But as we now have the shape of the bitmap in vector form, we can simply 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...
|
![]() |
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 thicker outline of the shape with a red fill, all nicly anti-aliased.
|
![]() |
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. For another example of using the "AutoTrace
" program, see Skeleton using Autotrace.
Hough Line Detector
The Hough Line Detector ("-hough-lines
" added IM v6.8.9-1), is a very complex transform with a lot of stages (for details see Wikipedia, Hough Transform). Basically it is designed to examine an image, looking for white lines on a black background, and try to return the exact location of any line segments (linear sequences pixels) present in the image. This can be very important for things like removing image rotations, or determining the perspective transformation in an image, so it can be repeated, or removed. Here is the full set of options to the operator
-background {background} -stroke {line_color} -hough-lines {W}x{H}+{threshold} |
Now lets apply the Hough Line Detector to this image.
|
![]() |
-hough-line
" operator. From this you can see the first and last lines (which are close matches) are both roughly equal in strength, so it would be hard to pick one of them over another. If this happens to you, I suggest you try to improve your edge detection step. Note that MVG image does not have any colors defined. The color settings in this example were not actually used. The colors are only used if you actually 'draw' the vectors when converting the above result into a 'raster' image, as we did before.
You can also see the intermedate 'search image', or 'accumulator' that is looking for white pixels in every orientation, by using a special define.
|
![]() |
houghlines
" by Fred Wienhaus, though with a different 'perpendicular distance' accumulation handling.
Local Adaptive Thresholding
![](../img_www/const_barrier.gif)
![](../img_www/const_hole.gif)
-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. magick input.png -lat 17 output.pngIn 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.
ExampleA 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... magick input.png -lat 15x25 output.pngYou can also provide an 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.
magick 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. magick rose: -colorspace gray -lat 10x10+0% x: is roughly equivalent to... magick 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 magick rose: -colorspace gray -lat 10x10+10% x: is roughly equivalent to... magick 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 an anaglyph magick 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. |
-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.
|
![]() |
-shade
", but also for Masking Images 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...
magick shade_a_mask.gif -shade 0x45 shade_direction_0.gif magick shade_a_mask.gif -shade 45x45 shade_direction_45.gif magick shade_a_mask.gif -shade 90x45 shade_direction_90.gif magick shade_a_mask.gif -shade 135x45 shade_direction_135.gif magick shade_a_mask.gif -shade 180x45 shade_direction_180.gif |
![[diagram]](../img_diagrams/shade_elevation.gif)
magick shade_a_mask.gif -shade 90x0 shade_elevation_0.gif magick shade_a_mask.gif -shade 90x15 shade_elevation_15.gif magick shade_a_mask.gif -shade 90x30 shade_elevation_30.gif magick shade_a_mask.gif -shade 90x45 shade_elevation_45.gif magick shade_a_mask.gif -shade 90x60 shade_elevation_60.gif magick shade_a_mask.gif -shade 90x75 shade_elevation_75.gif magick shade_a_mask.gif -shade 90x90 shade_elevation_90.gif |
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 us to the first item of note. An 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 elevationof '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 an 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 elevationangle 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 elevationangle 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...
magick -size 320x100 xc:black \ -font Candice -pointsize 72 -fill white \ -draw "text 25,65 'Anthony'" \ -shade 120x45 shade_anthony.jpg |
![[IM Output]](shade_anthony.jpg)
-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 elevationlighting angle, then you can use the following command, to shade a flat solid color surface.
|
![[IM Text]](shade_elevation_45.txt.gif)
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.
magick shade_direction_135.gif shade_a_mask.gif \ -alpha Off -compose CopyOpacity -composite shade_beveled.png |
![[IM Output]](shade_direction_135.gif)
![+](../img_www/plus.gif)
![[IM Output]](shade_a_mask.gif)
![==>](../img_www/right.gif)
![[IM Output]](shade_beveled.png)
-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 elevationangle) can be used to just extract the beveled edge, leaving the center of the image hollow.
magick shade_direction_135.gif \ \( shade_elevation_90.gif -normalize -negate \) \ -alpha Off -compose CopyOpacity -composite shade_beveled_edge.png |
![[IM Output]](shade_direction_135.gif)
![+](../img_www/plus.gif)
![[IM Output]](shade_elevation_90.gif)
![==>](../img_www/right.gif)
![[IM Output]](shade_beveled_edge.png)
-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.
![]() ![]() ![]() ![]() ![]() ![]() ![]() |
||
![]() ![]() ![]() ![]() ![]() ![]() ![]() |
||
|
![]() |
|
![]() |
Shaded Shape Images
The Alpha Extract Operator will not only extract the alpha channel from a shaped images as a gray scale mask, but also 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
", preserving its detail. 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.
|
![]() |
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.
magick -size 50x50 xc:black -fill white -draw 'circle 25,25 20,10' \ shade_circle_mask.gif magick shade_circle_mask.gif -shade 120x45 shade_blur_0.gif magick shade_circle_mask.gif -blur 0x1 -shade 120x45 shade_blur_1.gif magick shade_circle_mask.gif -blur 0x2 -shade 120x45 shade_blur_2.gif magick shade_circle_mask.gif -blur 0x3 -shade 120x45 shade_blur_3.gif magick shade_circle_mask.gif -blur 0x4 -shade 120x45 shade_blur_4.gif magick shade_circle_mask.gif -blur 0x5 -shade 120x45 shade_blur_5.gif |
magick shade_blur_3n.gif shade_circle_mask.gif \ -alpha Off -compose CopyOpacity -composite shade_blur_3n_mask.png |
![[IM Output]](shade_blur_3n.gif)
![==>](../img_www/right.gif)
![[IM Output]](shade_blur_3n_mask.png)
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-Tonesto just recolor a grey-scale, or 'Overlay' alpha composition, to replace the grey areas with an image, both rely 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 is 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. For example, here I shade an image, then extract the top-left pixel to check the resulting color of a 'flat' part of the image.
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() |
|||
|
![]() |
||
|
-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...
|
![]() |
||
|
|
![]() |
||
|
-normalize
" operator, the "-blur
" value used for 'rounding edges' will no longer effect final intensity of the result. A much better method. In summery, normalizing a shade image will shift the mid-tones away from a perfect-grey color. 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...
magick -size 50x50 xc:black -fill white -draw 'circle 25,25 20,10' \ \( +clone -blur 0x2 -shade 120x21.78 -normalize \) \ +swap -alpha Off -compose CopyOpacity -composite shade_tint_0.png magick shade_tint_0.png -fill grey50 -colorize 10% shade_tint_10.png magick shade_tint_0.png -fill grey50 -colorize 30% shade_tint_30.png magick shade_tint_0.png -fill grey50 -colorize 50% shade_tint_50.png magick shade_tint_0.png -fill grey50 -colorize 80% shade_tint_80.png |
-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...
magick -size 50x50 xc:black -fill white -draw 'circle 25,25 20,10' \ \( +clone -blur 0x2 -shade 120x21.78 -contrast-stretch 0% \) \ +swap -alpha Off -compose CopyOpacity -composite shade_sig_0.png magick shade_sig_0.png -sigmoidal-contrast 10x50% shade_sig-10.png magick shade_sig_0.png -sigmoidal-contrast 5x50% shade_sig-5.png magick shade_sig_0.png -sigmoidal-contrast 2x50% shade_sig-2.png magick shade_sig_0.png +sigmoidal-contrast 2x50% shade_sig+2.png magick shade_sig_0.png +sigmoidal-contrast 5x50% shade_sig+5.png magick shade_sig_0.png +sigmoidal-contrast 10x50% shade_sig+10.png |
![]() ![]() ![]() ![]() ![]() ![]() |
||
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() |
||
|
![]() |
- 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 )
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. This also can be used ensure a shape gets roughly equal amounts of light and dark areas (or even unequal amounts), as I produce them in seperatally but in a completely controled way.
FUTURE: more detail hereSee the first Advanced 3D Logo for an example of using this technique.
Using FX, The DIY Image Operator
The 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,
- create canvases, gradients, mathematical colormaps.
- move color values between images and channels.
- adjust image colors in just about any way imaginable
- translate, flip, mirror, rotate, scale, shear and generally distort images.
- merge or composite multiple images together.
- tile image(s) in weird and wonderful ways.
- convolve or merge neighboring pixels together.
- generate image metrics or 'fingerprints'
- compare images in unusual ways.
-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 later built into IM's core library. As an example see DIY New Ordered Dither Replacement where I used "-fx
" to develop a revised version of the -ordered-dither
" operator. 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 an 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 provided are normalized into a 0.0 to 1.0 range of values. Results are also expected to be in this range. This includes the transparency or alpha channel, which goes from 0.0 (meaning fully transparent) to 1.0 (meaning fully opaque). The values represent 'alpha transparency' and is actually the negative of how IM normally stores the transparency internally (as matte values). It is however more mathematically correct and easier to use in this form. 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 ('RGB
') of the original image. Any existing transparency in that image will not be modified, unless the "-channel
" setting is changed, to include the alpha ('A
') channel. The expression is executed once for each pixel, as well an once for each color channel in the pixel that is being processed. Also as the expression is re-parsed each time it is executed, 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.
|
![]() |
|
![]() |
![]() ![]() |
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 ". |
-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 Image List 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. Unless told otherwise it will process each of the RGB color values (as set by the default "-channel
" setting), for each and every pixel in the image. That is, 3*w*h calculations which modifies all the values in the image by the expression given. For example, here we take the IM built-in "rose:
" image and multiply all pixel values by 50%.
|
![]() |
-fx
" formulas to recolor images are explored in Mathematical Color Adjustments and Histogram Curves. As we can also reference any image in the current image sequence, as part of the expression for modifing the first image, we can merge two, or even more images, in 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.
magick -size 64x64 gradient:black-blue -rotate -90 fx_blue.gif magick fx_red.gif fx_blue.gif \ -channel B -fx 'v' fx_combine.gif |
![[IM Output]](fx_red.gif)
![+](../img_www/plus.gif)
![[IM Output]](fx_blue.gif)
![==>](../img_www/right.gif)
![[IM Output]](fx_combine.gif)
![]() ![]() |
Of course we could have just used a Channel Coping Composition Method instead which would be a lot faster. But that is not point. Though the reverse is also true. Just about every IM image operation could be replaced by a FX equivelent function. |
-fx
" first creates a copy of just the first image. It then modifies that image according to the formula, using all the other images given. And finally it junks all the input images replacing them with the modified copy of the first image. You can also calculate values based on each pixel location within the image. values 'i,j
' is the current position of the pixel being processed, while 'w,h
' gives the size of the image (the first image unless a specific image qualifier is given). For example, here we generate a DIY Gradient Image.
|
![]() |
Or something more complex using both 'i,j ' position values.
|
![]() |
G
' or green channel in the above example. This channel can then be Separated to generate the final 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. You can use the position information to lookup specific pixels from the source image using the 'p{x,y}
' syntax. For example you can easily make your own 'mirror image' type function (like the "-flop
" image operator), that replaces each pixel, with the color values from the 'mirror' position of the original source.
|
![]() |
|
![]() |
0.5
' values added to the expression is needed to correctly magick between Pixel Coodinates used for input coordinates 'i,j
' and location lookup 'v.p{...}
, while the more mathematically correct Image Coordinates is needed for the actual mathematical calculations (scaling). The above is actually the exact methodology used by any form of Image Distortion. You can see this FX equivelent for most distortions by turning on the Verbose Distortion Summery. This reports a FX equivelent for most image distortions, as a way to double check the distortion is doing what it is expected to do. The use of the FX DIY Operator to do image distortions, shows just how powerful this operator really is. If it wasn't for this operator I doubt may of the new operations, such as distortions, sparse-color, or ordered dithers would have been added to the ImageMagick Core Library. Here is something a little simplier, swapping the red and blue channels of the rose image. See if you can figure how it works.
|
![]() |
![]() ![]() |
A faster and better way to do the same thing, is to use "-separate " and "-combine "). See Combining RGB Channel Images. Alternatively you can also use a "-color-matrix " to do the same thing faster still.Do you see a trend here? |
-channel
" setting, it 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.
|
![]() |
rose:
" actually had an 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 distance from a particular point (assigned to the variables 'xx
' and 'yy
'). Without the use of the variables this formula could have become very hard to read.
|
![]() |
![]() ![]() |
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. |
-fx
" expressions started to require external files, so the standard '@filename
' can now be used to read the expression from a file.
|
![]() |
-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.
![]() ![]() |
Some More functions were added at various times IM v6.3.6 : hypot() IM v6.7.3-4 : while(), not(), guass(), squish() |
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.
|
![]() |
debug()
" was not limited to just one pixel, even for this small image.
FX-like Built-in Operations
The-fx
operator represents a way to develop new image processing functions that previously did 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, usually added as part of a group of similar operators. These include the follow general image operator and there methods...
-evaluate |
Direct pixel, color values, channel modification functions. (See Evaluate below). |
-function |
More Complex pixel, color value, channel modification functions. (See Function below). |
-evaluate-sequence |
Merge a multi-image sequence of images mathematically (See Evaluate-Sequence below). |
-sparse-color |
General Image Re-Coloring Operator. (See Sparse Color Gradients ) |
-compose |
General Multi-Image combining and overlaying method. (See Alpha Composition). |
-distort |
General Image Distortion operator, using reversed pixel mapping. (See the Distort Operator ) |
-morphology |
General Area Effect Convolution/Morphology function. (See the Morphology Operator and the Convolve Operator ) |
-fx
" operator first. When they have it worked out that 'method' is then converted into a new super-fast built-in operator in the ImageMagick Core library. Users are welcome to contribute their own "-fx
" expressions (or other defined functions) that they feel would be an 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. If "-fx
" speed and complexity is starting to become a problem then it is probably better to move on to an 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 in the current image sequence. The FX Expression however is modified slightly during processing. Specifically...
- The current pixel coordinates '
i
', 'j
' is fixed to the value 0, so on its own an image variable only returns the value from pixel 0,0, unless a 'p{}
' index is used. - Unless a color channel is selected only the red channel value is returned.
- The default image reference '
s
' is set to current image, being annotated or identified. - The index '
t
' returns the index of the image referred to by 's
'.
![]() ![]() |
Before IM v6.6.8-6 both FX expression values of "t " image index and "n " total number of images, were broken, and only returned a value of 0 and 1 respectively for ALL images. The same goes for the equivalent percent escapes '%p ' and '%n '. |
-annotate
" each image with the color of the top left corner of each image.
|
![]() ![]() ![]() |
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.
magick -size 300x100 gradient:yellow-limegreen \ -gravity NorthWest -annotate 0 '%[pixel:s.p{0,0}]' \ -gravity Center -annotate 0 '%[pixel:s.p{0,50}]' \ -gravity SouthEast -annotate 0 '%[pixel:s.p{0,99}]' \ annotate_pixel.gif |
![[IM Output]](annotate_pixel.gif)
-format
" with the "identify
" command.
|
![]() |
pi
'. You can generate random numbers. For example to generate an integer between -5 and 10 inclusive. Here I use the "info:
" equivalent to the "magick identify
" command.
|
![]() |
All the above will essentially run the "
-format
" and thus any containing FX Expression one for each image in the current image sequence. The "-print
" operator will work much like "-identify
" except that it is only run once, with access to ALL the images in the current image sequence. With this operator you can use 'u[{i}]
' to access values from any image, unlike the above.Fx Expressions 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.
|
![]() |
gold
', 'yellow
', and 'khaki
'.
|
![]() |
|
![]() |
-print
" to print information. This is applied only once against the whole image sequence. That means you can use this operator to calculate much more complex '%[fx:...]
' expressions involving multiple images.
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!
|
![]() |
|
![]() |
option:
' tag, tells the "-set
" option that you want the given setting saved as a global Artifact, rather than as an image 'Attribute' or 'Properties' string, just as "-define
" can. However the "-set
" form allows you to expand Percent Escapes in setting the Artifact, where as "-define
" does not. When the "label:
" operator expands its percent escapes, the given 'key' is looked for first as a per image 'attribute' or 'proprieties', but if it fails to find anything, it will then look for the 'key' in the global Artifact settings. As such the global 'artifact' we created from the previous image is used, even though that image is no longer present at the time the Artifact was created. Basically 'Artifact' settings are global during the life time of the "magick
" command, and thus can be used to pass information from one image to another. For programmed API's this situation can be avoided as you can read the required data directly from the image and generate the label string yourself, without needing IM to store that information in such a convoluted way.
Evaluate and Function, Freeform Channel Modifiers
Because the FX Operator is an interpretted expression handler, 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 Background Removal, Highlight and Shadow Overlays, and the generation and fine-tuning of Image 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 couple of months). However it is limited to just one simple operation, using a single user provided constant number. You can find out what functions have been built into evaluate using
magick -list evaluate |
add
', 'subtract
', 'multiply
', and 'divide
'. against constant values. 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 just simply replace all color values in an image to a 50% gray level is very simple and very fast, using 'Set
'
|
![]() |
-evaluate
" operator also includes the typical mathematical functions 'add
', 'subtract
', 'multiply
', and 'divide
'. 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.
|
![]() |
-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. That means that after any individual "-evaluate
" operation, the values could be clipped by the 'QuantumRange
'. As such if you try to apply a contrast enhancement function (equivalent to "-fx '2*u-.25'
") directly as it stands, you will fail to get the correct results, as the doubled value will be clipped, before the subtraction is made.
|
![]() |
multiply
' will clip all the large color values to the maximum value, then the 'subtract
' will clip the lower bound values. the result is an incorrect clipping of the upper bounds, producing a dark and color distorted result. The direct solution is to 'subtract
' the appropriate constant first (doing the final but correct clip of the lower bounds), before multiplying, effectively using the equivalent formula '(u-.125)*2
'
|
![]() |
-evaluate
" methods.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.
|
![]() |
-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 an image that has been gray-scaled using a specific set of color ratios.
|
![]() |
Evaluate Math Functions
Included in Evaluate, 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.Power Of
The 'Pow
' function (added IM v6.4.1-9) for example works with normalized color values, and allows users to do image brightness modifications. It is exactly equivalent to the pow() C function, (using normalize color values in a 0 - 1 range)
value = pow(value, constant)As such to create a 'parabolic' gradient you can use an argument of '
2
'. Or use a value of '0.5
' to create a 'square root' gradient. For example...
magick -size 20x600 gradient: -rotate 90 gradient.png magick gradient.png -evaluate Pow 2 eval_pow_parabola.png magick gradient.png -evaluate Pow 0.5 eval_pow_sq_root.png |
![[IM Output]](gradient.png)
![[IM Output]](eval_pow_parabola.png)
![[IM Output]](eval_pow_sq_root.png)
![[IM Output]](gradient_pf.gif)
![==>](../img_www/right.gif)
![[IM Output]](eval_pow_parabola_pf.gif)
![[IM Output]](eval_pow_sq_root_pf.gif)
![]() ![]() |
The three lower images show the profile of the gradient produced both a graph and the original image itself. This makes it easier to see how one gradient image was modified to become another. It was generated using the Gnuplot graph ploting program, via the script "im_profile " in the IM Examples, Scripts directory. |
-gamma 2
" operation would be equivalent to an "-evaluate pow 0.5
" or a 'square root' operation function. Similarly "-gamma 0.5
" is equivelent to squaring using "-evaluate pow 2
"By doing some special gradient manipulations, you can use this method to magick a linear gradient into a complex circular arc.
![]() |
![]() |
sqrt(1-u^2)
'. This generates a single quarter circle arc, which is then Flopped, and Appended together, to produce a half circular arc. It is also a lot faster than using an FX expression, even though it requires many more individual (smaller) steps. See also the more advanced Polynomial Function.
Logrithmic
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)For example...
![]() |
![]() |
Log
' will produce an appreciable slope as it approaches '0
', where 'Pow
' will produce a vertial slope. The value controls the slope. A logrithmic function is also closely related to an exponential function, which is currently only implemented as Sigmoidal Contrast Adjustment operator. This contains the same slope features you can see in the above logrithmic curves. This explains why "-sigmoidal-contrast
" is a better technique for enhancing images involving low light conditions, than a Gamma Adjustment or 'power of' curve.
Sine and Cosine
As of IM v6.4.8-8 the 'sin
' and 'cos
' methods were added. These methods take the value given in the image and normalize it into an 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 * sin( constant*value*2*PI ) + 0.5 value = 0.5 * cos( constant*value*2*PI ) + 0.5In 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.
magick gradient.png -evaluate sin 1 eval_sin_1.png magick gradient.png -evaluate cos 1 eval_cos_1.png |
![[IM Output]](eval_sin_1.png)
![[IM Output]](eval_cos_1.png)
![[IM Output]](gradient_pf.gif)
![==>](../img_www/right.gif)
![[IM Output]](eval_sin_1_pf.gif)
![[IM Output]](eval_cos_1_pf.gif)
![]() |
![]() |
![]() |
![]() |
-evaluate
" methods are rarely used as they have been superseded by a more general Sinusoid Function (see below) that provide more control options, beyond that of a simple frequency option.
Function, Multi-Argument Evaluate
The above wave generators proved to be extremely useful, especially with Distortion Image Mapping. But it was found that a much finer control of the functions was needed, requiring more than one parameter. Because of this the "-function
" operator was added in IM v6.4.8-9. Basically "-function
" is a multi-argument form of "-evaluate
". However unlike the Evaluate Operator, these operators like the mathematical operators, all the functions above work only on normalised channel values (0.0 to 1.0 range) of the image, which in most cases makes them easier to use.
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,...
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 this polynomial function produces a parabolic curve going from 1.0 to 0.0 then back to 1.0, over the input ('u
') color range 0.0 to 1.0. That is, it will make, black and white colors 'white', and make perfect grays, 'black'.
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Polynomial
' to do a full Threshold operation, due to the need for infinite coefficients to do so, though you can get pretty close. A single value is naturally just a constant, and results in a direct assignment of that value. In other words it is just like the "-evaluate Set
" method, in this case to a 33% gray value.
![]() |
![]() |
Polynomial
' with other math functions you can create even more complex gradient modifications. For example by taking the square root of a polynomial, I can create a true circular arc over a linear gradient. The equivalent "-fx
" expression 'sqrt( -4*u^2 + 4*u + 0 )
'...
![]() |
![]() |
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
value = ampl * sin(2*PI( freq*value + phase/360 ) ) + biasThis may seem complex but it ensures the function is easily to use. 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.
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
0.75 ±0.25
, or 0.5 to 1.0
), starting and finishing on white.
![]() |
![]() |
Arcsin Function
The inverse sinusoid function 'Arcsin
' was added to IM v6.5.3-0. This is a special curve that was needed to generate a Cylindrical Displacement Map. It parameters are...
-function Arcsin width,center,range,bias
value = range/PI * asin(2/width*( value - center ) ) + biasBy default values (if not defined) '
1, 0.5, 1, 0.5
' ensures the the function is centered so as to cover the whole color range from 0,0
to 1,1
.
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
bias ±range/2
', as you would expect. Note that if either the 'width' or the 'range' are made negative the slope of the function will be flipped, as a result of that negative value.
![]() |
![]() |
Arctan Function
The 'Arctan
' method was added to IM v6.5.3-1. Its parameters are...
-function Arctan slope,center,range,bias
value = range/PI * atan(slope*PI*( value - center ) ) + biasAs you can see it is almost exactly the same as the 'Arcsin' function, with only a small change to make it more useful. It even has the same set of default values (if not defined) '
1, 0.5, 1.0, 0.5
'. This means that if you specify a slope value of '1.0
' the slope of the histogram change will produce a 1:1 change around pure gray, (no scaling) while making white and black a more gray value. For example
![]() |
![]() |
![]() |
![]() |
Arctan
' function will NEVER actually reach the output range limits of pure black and white. It will approach those limits but never cross them. Similarly to the previous functions, (and Sigmoidal Contrast) the second argument will adjust the position of the curve relative to the input gradient values.
![]() |
![]() |
![]() |
![]() |
Arctan
' gradient function to create a curve that will very quickly approach a specific value but not exceed that value. It is these limiting values that the 'range' and 'bias' arguments control. For example, this curve will modify the gradient in an image to produce very sharp threshold around the input gray level of 0.7, but with the values changing between the range limits of 0.5 and 1.0
![]() |
![]() |
Mathematics on Gradient Images
Now the above functions provide some very basic transformations for gradient images. But what if you want to do some mathematics with two or more gradient images. That is, modify one gradient using the gradient of another image. For this you need to use the special Mathematical Compose Methods (such as "Plus
" and "Divide
"). Before we start however, I would like to give you one word of warning. If your gradient images are purely grey-scale images, with no alpha channels, then you can use the Mathematical Compose Methods directly. However if you want to limit these methods to a specific channel, or apply them to the alpha (transparency) channel, then you need to ensure that you set the appropriate "-channel
" setting, with no special 'Sync
' channel flag. See Image Mathematics using Image Composition for more details. Normally using Mathematical Compose Methods is not really that difficult. The complications arise when you have gradients that also contain a 'bias'. That is, the gradient should represent a value of 'zero' at '50% grey, and cover a range from -1 (black) to +1 (white). Such images are often used for Distortion Image Mapping. As such performing maths on 'biased gradients' is the real problem, and what will be looked at more specifically here.
Attenuate a Biased Gradient
For example, here I want to create a sine wave, but one that starts out small, but then gets larger in amplitude. This known as 'attenuating' a biased gradient. Or putting it another way, multiply a biased gradient by another absolute gradient. It is also how 'Amplitude Modulation' such as in AM radio works! So first we need a sine wave, which we can simply generate from a linear gradient...
magick -size 5x300 gradient: -rotate 90 math_linear.png magick math_linear.png -evaluate sine 12 math_sine.png |
![[IM Output]](math_linear_pf.gif)
![==>](../img_www/right.gif)
![[IM Output]](math_sine_pf.gif)
magick math_linear.png -negate -evaluate divide 2 math_bias.png magick math_sine_2.png math_bias.png \ -compose Plus -composite math_attenuated.png |
![[IM Output]](math_sine_2_pf.gif)
![X](../img_www/plus.gif)
![[IM Output]](math_bias_pf.gif)
![==>](../img_www/right.gif)
![[IM Output]](math_attenuated_pf.gif)
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 frequency Sine wave, using a negated Cosine wave, instead of a linear gradient.
magick math_linear.png -evaluate cos 1 -negate math_cosine_peak.png magick math_sine.png math_cosine_peak.png \ \( -clone 0,1 -compose multiply -composite \) \ \( -clone 1 +level 50%,0 \ -clone 2 -compose plus -composite \) \ -delete 0--2 math_cosine_atten.png |
![[IM Output]](math_sine_pf.gif)
![[IM Output]](math_cosine_peak_pf.gif)
![==>](../img_www/right.gif)
![[IM Output]](math_cosine_atten_pf.gif)
Sc*Dc-.5*Sc+.5
or the arguments, "1,-.5,0,.5
".
|
![]() |
|
![]() |
Multiply Biased Gradients
But what if BOTH functions are biased so a perfect gray means zero, and black and white represent the range from -1 to +1? Well this is a little more complex as you can't just multiply them and expect it to come out right, as the multiplication can consist of a negative values. This requires some care so as to ensure you don't end up clipping the values and getting the right negation of the curve in the resulting image. The trick is to break up the multiplication into multiple steps. That isA × B
can also be written as A × abs(B) × sign(B)
. By doing this you avoid multiplying by a negative value, which can't be stored in a normal gradient image. So all we need to do is take one of the bias gradients and separate it into two parts so they can be applied to the other gradient appropriately. The 'sign()
' of a biased gradient, or getting a mask of what parts are negative, can get extracted by using a Threshold on the gradient at the bias level. You can later selectively negate the other gradient using a Composite Difference, with that threshold image. The 'abs()
' of a biased gradient can be extracted easily using Solarize, then negating and doubling (using Level) that to get the absolute value of the gradient ranging from 0.0 to 1.0. As we will also need the bias offset as part of the multiply (as per Attenuate above), you can directly use the negated and half-scaled solarize output, before it is converted into the gradients absolute value. So lets magick one gradient into these three components.
magick math_cosine_peak.png -threshold 50% -negate math_m_sign.png magick math_cosine_peak.png -solarize 50% math_m_bias.png magick math_m_bias.png -level 50%,0 math_m_abs.png |
![]() |
Sign of Gradient white = negative |
|
![]() ![]() |
![]() |
Bias Offset |
![]() |
Absolute Value | |
magick math_sine.png math_m_abs.png \ -compose Multiply -composite math_m_1.png magick math_m_1.png math_m_bias.png \ -compose Plus -composite math_m_2.png magick math_m_2.png math_m_sign.png \ -compose Difference -composite math_multiply.png |
![]() |
![]() |
![]() ![]() ![]() |
![]() |
![]() ![]() ![]() |
|
sign |
![]() ![]() ![]() |
|
magick math_sine.png math_cosine_peak.png \ \( -clone 1 -threshold 50% -negate \) \ \( -clone 1 -solarize 50% \) \ \( -clone 3 -level 50%,0 \) \ \( -clone 0,4 -compose multiply -composite \ -clone 3 -compose plus -composite \ -clone 2 -compose difference -composite \) \ -delete 0--2 math_multiply_2.png |
![[IM Output]](math_sine_pf.gif)
![X](../img_www/multiply.gif)
![[IM Output]](math_cosine_peak_pf.gif)
![==>](../img_www/right.gif)
![[IM Output]](math_multiply_2_pf.gif)
2*Sc*Dc-Sc-Dc+1
, as of IM v6.5.4-3, you can implement the above complex steps as a single 'Mathematics
' compose method using the argument "2,-1,-1,1
".
|
![]() |
Exclusion
' compose method. Weird but true. As such the following will also generate the same zero baised multiply.
|
![]() |
Adding Biased Gradients
With the advent of the 'Mathematics
' compose method, adding biased gradients is also relativally easy. The equivelent FX formula is "u+v-0.5
" or a compose argument of "0,1,1,-.5
". For example the following was a Fourier Transform Example that I had hand generated, requiring the addition of 3 biased sinusoids, and a constant DC value.
magick math_linear.png -function sinusoid 3.5,0,.25 wave_1.png magick math_linear.png -function sinusoid 1.5,-90,.13 wave_2.png magick math_linear.png -function sinusoid 0.6,-90,.07 wave_3.png magick wave_1.png wave_2.png wave_3.png -background gray40 \ -compose Mathematics -set option:compose:args 0,1,1,-.5 \ -flatten added_waves.png |
![[IM Output]](wave_1_pf.gif)
![+](../img_www/plus.gif)
![[IM Output]](wave_2_pf.gif)
![+](../img_www/plus.gif)
![[IM Output]](wave_3_pf.gif)
![==>](../img_www/right.gif)
![[IM Output]](added_waves_pf.gif)
-flatten
" operator with a "-background
" setting to implement a mutliple image composition. Or in this case a 'Biased Sum' of all the given images plus the background constant.
Frequency Modulation
By applying a function directly to the output of another function, you do NOT produce a simple result. The reason is that all these math functions are applied to the gradient 'value' of individual pixels, and not against the x value of the pixel in the gradient. For example....
![]() |
![]() |
cos( 8 * sin( {value}/2 ) )
![](../img_www/const_barrier.gif)
![](../img_www/const_hole.gif)
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... magick input.jpg -resize 10% -sample 1000% output.jpg De-skew slightly rotated images -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.