- Index
ImageMagick Examples Preface and Index
Art-like Transformations
Computer Vision Transformations
Shade 3D Highlighting
Using FX, the DIY Image Operator
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 5x5 rose_raise.gif
| |
|
A inverted sunken effect can be generated using the 'plus' form of the
operator...
convert rose: +raise 5x5 rose_sunken.gif
| |
|
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.
Note that the operator only works on rectangular images, and will fail for
images with a transparent background. 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
| |
|
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
| |
|
Vignette Photo Transform
A special operator to make a image circular with a soft blury outline was
a fairly recent addition to IM.
convert rose: -background black -vignette 0x5 rose_vignette.gif
| |
|
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
| |
|
OR with transparency (and PNG format)...
convert rose: -matte -background none -vignette 0x3 rose_vignette.png
| |
|
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.
  |
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 controled 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
| |
|
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
| |
|
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
| |
|
  |
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 tendancy to produce a 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 effectially sharpens the resulting image, and more importantally 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 is quite the same way.
convert -caption 'Spiral Staircase, Arc de Trumph, 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
| |
|
As you can see, even though we used a much smalled font pointsize, the caption
text is very sharp, clear and readable. The same as any fine details 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 revered 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
|
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 suposed to produce areas of a single solid color, at
large radius values, it has a tendancy 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
|
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 applys 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 infact 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
|
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
|
If anyone knows exactly the emboss algorthim is suposed 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
| |
|
|
|
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
| |
|
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
| |
|
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
| |
|
|
|
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
destory 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
| |
|
As a means of image copyright protection, it is useless, as the smallest
change will destroy the hidden message.
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 crack it quickly.
It's only 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" actuially encrypt
the image data.
That is the image content itself is no longer recognisable until the image is
later decypted. 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 passphrase I have saved in the file "
pass_phrase.txt".
convert message.gif -encipher pass_phrase.txt -depth 8 message_hidden.png
|
  |
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
| |
|
You can use a "
-" to read the pass phase from the output of
another command, instead of a clear text file. So you can use some other
commands to say, ask the user for the password, or extract that pass pharse
from some other source.
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 internal
gray-scale type. As such only a non-lossy image formats can be used, such as
PNG, MIFF, TIFF, or even
Pixel Enumeration Text.
However using a lossy image formats, such as JPEG, PNG8, and GIF, will corrupt
the image data, thus destory 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 phase, or 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 really confuse anyone trying to decrypt the
image.
echo "password" | convert message.gif -encipher - \
-transpose -depth 8 message_obfuscate.png
echo "password" | convert message_obfuscate.png -transpose \
-decipher - message_restored_2.png
|
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 expert box below) using a "
-roll" will not prevent the image from decyrpting, at least
partially.
Note that in the above I did not use a file for the 'pass-phrase' but feed the
phase into the "
convert" command using standard input.
The pass-phase does not even need to be a text string, but can be any binary
sequence of characters. As such you can make it extremely hard to decrypt
by using, say an known unchanging image as the pass-phase.
convert message.gif -encipher rose.gif -depth 8 message_binary.png
convert message_binary.png -decipher rose.gif message_restored_3.png
|
  |
The "-encipher" and
"-decipher"
operators was added to IM v6.3.8-6. However it is currently experimental
and requires you to include a "--enable-cipher" option during
the build configuration.
|
  |
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 destory by transmition error, even though some part of the image
may have been destroyed. You also do not need to have 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
|
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 intensional, 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 wierd result.
convert piglet.gif -colorspace Gray -edge 1 -negate piglet_edge.gif
|
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
| |
|
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
| |
|
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
|
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-Alised 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
| |
|
And here is its edge detected image...
convert voice.gif -edge 1 -negate voice_edge.gif
| |
|
As you can see it looks horrible, with some minor anti-alising on the outside
of the edge, and a total aliased (staircase) look on the inside of the line.
The negating the image generated a simular 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
| |
|
An alturnative 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 defference 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
| |
|
Note that the this does not produce a good edge for horizontal sloped edges.
However by combining both a horizontal and vertical jittered difference
images, 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
| |
|
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 horible 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
| |
|
So we have this horible 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
| |
|
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
| |
|
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
| |
|
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
| |
|
And a simular 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
| |
|
Or for more controled fuzzier effect you can just blur the shape and extract
the edge in a simular way. I find a blur of '
0.7' about the
best, with a 1 pixel limit to speed things up.
convert heart.gif -blur 1x.7 -solarize 50% -level 50%,0 heart_blur.gif
| |
|
Note the use of
Level Operator, which is the
equivelent of the "
-evaluate multiply 2 -negate" used in the
previous example.
With a 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 positions 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
|
Now this SVG vector image, is a smooth form of the bitmap shape, using a very
set of smooth
Cubic Bezier Curve line segments.
The resulting vector image also has very sharp points and corners, which is
very difficult to achieve from a bitmap image using any other edging
technique.
As of IM v6.2.4-6 you can also use an "
autotrace" system
delegate, when reading the image, so as to avoid the need to generate
temporary PBM format image. As such the above can now be simplified to...
convert autotrace:heart.gif heart_autotraced.gif
|
Also with this version you can generate the SVG from the bitmap image as well
because of the
Direct Delegate
Conversion feature of IM.
convert autotrace:heart.gif heart_autotraced.svg
|
To this SVG output to generate a simple outline of the shape we need to adjust
the '
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
| |
|
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
| |
|
Such a perfect looking heart could not have been generated from a basic shape
in any other way.
We could 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.
-lat (local adaptive threshold, edge detection)
threshold only along define edges of objects.
Used in computer vision to locate the objects in the field of view.
-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 applys 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
| |
|
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
|
You get the idea. The light can come from any direction.
The other argument is the azimuth, 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.
convert shade_a_mask.gif -shade 90x0 shade_azimuth_0.gif
convert shade_a_mask.gif -shade 90x15 shade_azimuth_15.gif
convert shade_a_mask.gif -shade 90x30 shade_azimuth_30.gif
convert shade_a_mask.gif -shade 90x45 shade_azimuth_45.gif
convert shade_a_mask.gif -shade 90x60 shade_azimuth_60.gif
convert shade_a_mask.gif -shade 90x75 shade_azimuth_75.gif
convert shade_a_mask.gif -shade 90x90 shade_azimuth_90.gif
|
As you can see with an azimuth 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 it 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.
As the light gets higher over the 'high field'. The resulting image will
become brighter, until at 'high-noon' or an azimuth 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).
This 'noon' image is another special case that is a bit like a edge detection
system, though 3 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.
If the azimuth 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 azimuth 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 usally 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
|
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 3
pixels, 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 azimuth 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:-
|
As you can see a azimuth 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 3d
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 3D
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
|
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' degreee azimuth angle) can be used to just extract
the beveled edge, leaving the center of the image hollow.
convert shade_direction_135.gif \
\( shade_azimuth_90.gif -normalize -negate \) \
+matte -compose CopyOpacity -composite shade_beveled_edge.png
|
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_azimuth_90.gif -normalize -negate \
shade_a_mask.gif -compose screen -composite \) \
+matte -compose CopyOpacity -composite shade_beveled_plus.png
| |
|
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
| |
|
Rounding Shade Edges
By blurring the image shape mask, the 'slope' of edge 'cliffs' will be
flattened out and smoothed, as if worn down by time, or rounded off. 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
|
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
|
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
|
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 azimuth 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:-
| |
| |
|
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.
That means 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:-
| |
| |
|
After some further experimentation however I found that using a 21.78 degree
shade azimuth 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:-
| |
| |
|
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
|
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
|
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
| |
|
- 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 preformed), 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 usful a
'midday' or 'highnoon' shade image (using an azimuth 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 azimuth 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,
- 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.
Of course many of these technqies 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 offical 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
| |
|
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
| |
|
  |
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
| |
|
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
|
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.
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
| |
|
This type of 'image distortion' was made more powerful by creating Image Distortion Maps, or Color Lookup Tables.
You can also use the pixel position generate your own mathematically generated
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
| |
|
  |
When generating gray-scale gradients, you can make the -fx operator 3 times
faster, simply by asking it to only generate one color channel only, 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.
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
| |
|
Note that a better and faster way was later developed using "-separate" and "-combine"). See Combining RGB Channel Images.
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: -matte -channel A -fx '0.5' fx_rose_trans.png
| |
|
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 "-matte" option.
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 imposible
any other way.
For example, here I create a gradient basied 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
| |
|
  |
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
recomended 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 uses expression 'rr=sqrt(xx*xx+yy*yy)'.
Of course if you need the distance squared, you should avoid the
'hypot()' function.
|
For more examples of some really complex expressions see More Complex DIY Gradients, which
would be imposible 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
| |
|
This also means you can use more complex scripts to generate the specific
expression you need for a particular job.
FX Debugging
The 'debug(expr)' is essentually a way of printing a
floating point value, each time the FX espression is calculated. This in turn
provides a method of debugging your expressions.
However you can limit the output from the "debug()" by using a
teriary 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:
| |
|
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 Future
Well "-fx" operations is
slow, very slow when processing large images with very complex expresions. As
such what is needed is a FX espression compiler, that will pre-interprete the
expression into a tighter and faster executable form. Someone was going to
look into this but has since dissappeared.
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:
| |
|
Will mathematically calculate and return the value of PI, though this
value is available as the built-in variable 'pi'.
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 images. 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 the default image reference '
s' is set to current image,
being annotated or identified. Otherwise 's' is the set to
look at the first image, 'u'.
- The index '
t' returns the index of the image referred to
by 's'.
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
| |
|
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 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 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 +matte \
-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
|
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:
| |
|
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:
| |
|
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 builtin
"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
| |
|
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
| |
|
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, Faster Simple Math Operations
The "-evaluate"
operator is basically a faster, but very simple version of "-fx" operator (actually pre-dates its
addition to IM by just a couple of months). However it only applys one fixed
mathematical operation with one constant argument.
You can find out what functions have been built into evaluate using
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
noticable change.
However if you add a '%' to the argument, that argument will represent a
percentage of the maximum color value (known as 'MaxRGB' and is
equal to ('2quality-1'). This means you can
make your "-evaluate"
arguments IM quality level independany by the appropriate use of percentages.
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
| |
|
This is a couple of orders of magnatude 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 posible.
The major problem with "-evaluate" is that all results are clipped to the 0 to
'MaxRGB' range, as they are save back into the image data. Which
happens at the end of every such operation.
As such this contrast enhancement function (equivelent 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_constrast.gif
| |
|
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 correct result can be achieved by swapping the order of the operations, to
ensure things are correct (eq | | | | |