ImageMagick, Part Three

The last of a three-part series on tools for creating and editing high-quality images.

The last two “Power Tools” columns introduced ImageMagick (IM), explained how to hide data in an image, and showed different resizing filters. This month, let’s finish the series on IM with a bit more of what this command-line toolbox can do — and discover some useful, general information about digital images.

Pixels and Channels

What’s an image? It’s made of pixels arranged in a grid. Each pixel has three colors: red, green, and blue. Each of the three color values in each pixel is stored as an eight-bit number. (A 16-bit image has 16-bits per color per pixel. While 16-bit provides better quality — 16 bits capture more information that 8 bits — many applications and printers can’t handle 16-bit images.) For instance, if the red value of a pixel is 0, there’s no red in that pixel. A blue value of 255 (decimal) or FF (hexadecimal) is a pixel with maximum blue.

An image is often said to have three channels: red, green, and blue. When you adjust the color balance in an image, you’re generally changing the overall value of one or more channels.

(A fourth channel, the alpha channel, controls transparency. The PNG and TIFF image formats can store an alpha channel, and GIF can store a binary “transparent” or “not transparent” value per pixel. JPEG doesn’t handle transparency.)

One common problem in digital cameras is areas of pixels at maximum brightness (255). Each area has no detail, implicitly; instead, it’s “blown out” and can look terrible in photographs. (In line art or other images with blocks of color, though, pixels with maximum values may be just what you want! In fact, that’s what the IM -normalize operator can do: stretch images to maximum values automatically. There’s an example later, shown in Figure Three.)

Printers have another problem: a common printer generally prints pixels that are quite dark (with a value below 10 or 5) as maximum dark, and very bright pixels (above 245 or 250) as maximum white. When you’re exposing or editing photographs, be careful about pixel values near the limits.

Checking Pixel Values

Once IM has an image in memory, it can tell you a lot about the pixels. For instance, IM can list the values of each pixel, one pixel per line. To do that, give an output filename ending with .txt (for example, convert myphoto.jpg myphoto.txt), or pipe the result to standard output in a text format with the operator txt-, like this:

$ convert hkmist.png txt:- | less
# ImageMagick pixel enumeration: 3609,2605,255,RGB
0,0: (178,194,208) #B2C2D0
.
144,108: (255,255, 0) yellow
.

Here, the hkmist.png file has 3,609 pixels horizontaally, 2,605 vertically, and 256-step (8-bit) channels of red, green, and blue. (There are more than nine million pixels, so the output is very be long!) The image’s top left pixel (0,0) has a red value of 178 decimal or B2 hex, a green value of 194 or C2, and blue of 208 or D0. IM can show color names (as in yellow) instead of hex values.

Your camera or image editor may show you pixels that are completely black or blown out. For example, the software may make those pixels flash. You can also check for these “problem pixels” with IM. Just search the output for, say, any decimal value between 250 and 255:

$ convert hkmist.png txt:- | grep '(.*25[0-5].*)'
144,108: (255,255, 0) yellow

There’s just one blown-out pixel; it probably won’t stand out in this big image.

Histograms

But a list of pixel values isn’t an intuitive way to check an image.

Your digital camera may show you a histogram — a graph of the image brightness range — when it plays back an image. Histograms are usually either for a single channel or for an average of all channels. Although this gives you a general idea of what’s in the image, the graph is usually small, and you may not be able to tell if just a few pixels are blown out. Still, histograms are useful for checking an image overall: Is it dark? Does it have a blue cast (much more blue than other colors)?

IM has a histogram: operator that converts the pixel enumeration into a three-color histogram, 256x200 pixels in size. To save the histogram in another format, pipe it to another convert process:

$ convert hkmist.png histogram:- | 
convert - hkmist_histogram.png

Figure One shows the photo, and Figure Two displays its histogram.

Figure One: A misty day in Hong Kong

Figure Two: The histogram for Figure One

You can see that the photo has no dark colors: everything is medium-bright (in the middle) or bright (toward the right edge). Little or none seems bright enough to be blown out. In general, to a well-exposed photo should have a range of values from dark to bright. This photo doesn’t, but, in this case, this isn’t a problem because it’s how the original scene looked! Sometimes, though, you’ll want to “stretch” the values to make a “snappier” photo.

Normalizing Images

Sophisticated image editors like The GIMP have a “Curves” tool that lets you see and adjust image channels individually or together. They also have an “auto-levels” or “normalize” feature to stretch pixel values from darkest to brightest.

Processing a lot of images with a GUI can be tedious, though. That’s where a command-line tool like ImageMagick is handy: you can fix all of the images with a shell for loop.

IM has several operators for this type of adjustment, including -normalize, -contrast-stretch, -levels, and -sigmoidal-contrast. We’ll start with the first one.

NOTE: Before IM version 6.2.5-5, -normalize normalized each channel individually. Although it could correct color casts (like an image that looked “too blue”), it could also cause a color shift (and turn grey into another color). From version 6.2.5-5, -normalize adjusts all channels equally. You still can normalize channels separately, though, with the operators -channel R -normalize -channel B -normalize -channel G -normalize.

Let’s normalize the logo in Figure Three.

$ convert logo_gr.png -normalize logo_bw.png
Figure Three: A grey-on-grey logo

Figure Four: Figure Three, normalized

Figure Four shows the result. The light grey letters have turned white, and the darker grey is now black. But the globe in the middle looks odd because the colors make sudden shifts from dark to light.

Normalizing high-contrast line art and graphics can be great. But normalized photos may look unreal, and, as was said earlier, may not print well either. The -contrast-stretch operator can limit the “boundaries” of the normalization, but the -levels and/or -sigmoidal-contrast operator can make “smoother” adjustments.

Resetting Levels

Figure Five is overexposed. It doesn’t have any dark pixels (as a histogram can show you), so it looks “washed out.” The -level operator can fix this by “stretching” the brightness range. It wants one argument with three parameters:

*The first two are the levels that become the new black and white points, respectively. It’s best to give these as percentages. For instance, an argument of 0%,100% makes no change. An argument of 10%,90% means that pixels in the lowest (left-most) 10% of a histogram assume values of 0 (completely dark), the highest 10% of pixels assume values of 255 (maximum bright), and pixels in-between are adjusted proportionately.

*The third parameter is the gamma. Generally speaking, a low gamma darkens the image, a gamma of 1.0 makes no change, and a high gamma is bright.

The left-most 20% or so the histogram for Figure Five is empty (no pixel values there), so set the black point at 20%. The brighest pixels are near the right edge, though, so let’s leave the white point where it is: 100%. Use a gamma of 1.

$ convert rd.jpg -level 20%,100%,1.0 rd2.png
Figure Five: An overexposed photo of the Drakensberg Mountains

Figure Six: An improved version of Figure Five

This “stretching” will increase the image contrast (because it’s increasing the difference between brightest and darkest pixels), but that’s often what you want with an overexposed image. For more control over contrast, try the -sigmoidal-contrast operator.

Making Multiple Test Images

One problem with a non-interactive application like ImageMagick comes when you don’t know what values to use for a parameter. You can guess, as was done with the minimum level in the previous section. Or you can make multiple versions of the image, quickly, then compare them with an image viewer.

For an example, let’s write a bash function that uses -sigmoidal-contrast with multiple settings. Let’s take advantage of an ImageMagick principle: after IM reads an image into memory, it can do multiple operations on that same image without re-reading it. This can really speed up your output.

The -sigmoidal-contrast operator, which appeared in IM version 6.2.1, changes image contrast in a way that should prevent colors from being “blown-out.”

-sigmoidal-contrast expects one comma-separated argument with two parts. The first is a contrast value (0.5 is low, 10 is high); let’s compare several settings. The second argument is a center value for the contrast function; let’s choose 50%).

Listing One has the bash code. The dosig() function calls makesig() with three parameters: the base image filename (without extension), the contrast value, and the center point. The makesig() function simply outputs those parameters as IM operators; command substitution passes that output to the IM convert program.

The IM parenthesis operators, which we saw last month, make an image “on the side” that’s handled separately from the rest of the image sequence. (Escape the parentheses, using \( and \), so bash doesn’t interpret them.) The +clone, -write, and +delete operators do the rest of the job.

Listing One: bash functions to make multiple contrast adjustments

# makesig - outputs -sigmoidal-contrast instruction
# strings to standard output for use by dosig().
makesig()
{
echo “+clone -sigmoidal-contrast ${2},${3}% \
-write ${1}_${2}_${3}.png +delete”
}

# dosig - make multiple -sigmoidal-contrast adjustments
# on file named in $1 by calling makesig() repeatedly.
# Also creates histogram file of original image.
dosig()
{
base=${1%.*} # $1 without extension
convert "$1" \
\( $(makesig $base 0.5 50) \) \
\( $(makesig $base 2 50) \) \
\( $(makesig $base 5 50) \) \
\( $(makesig $base 8 50) \) \
\( $(makesig $base 10 50) \) \
histogram:- | convert - ${base}_histogram.png
}

So, for example, running dosig road.jpg produces an output file named road_0.5_50.png made with -sigmoidal-contrast 0.5,50%, a file named road_2_50.png made with a contrast setting of 2, and so on. It also makes road_histogram.png with a histogram of the original file (here, road.jpg). You can compare the output files to get an idea of what settings are best for a particular image.

This packs a lot of work into a few lines of code, but we’re really just scratching the surface of what IM can do.

Making Thumbnails

Another operation you’ll probably want to automate with ImageMagick is the making of thumbnails, or small versions of images, often used as clickable icons.

Resizing your image to a smaller size isn’t all you may need to do. Some programs, such as Adobe Photoshop, can add proprietary “profile” information to an image file. ImageMagick preserves this profile when it resizes an image. However, the profile can make a small image file much bigger than it needs to be. When you’re making a thumbnail, use IM’s -thumbnail operator (instead of -resize) to strip any profile.

Wrapping Up...

Although you’ve read about ImageMagick for three months, what was shown is maybe one or two percent of ImageMagick. It’s a sophisticated package that can do a lot, and it’s worth learning if you ever work with images.

Anthony Thyssen read these last three columns and made many helpful suggestions. Thanks, Anthony! For many more details and lots of examples, see Anthony’s IM examples at http://www.cit.gu.edu.au/~anthony/graphics/imagick6/.

Jerry Peek is a freelance writer and instructor who has used Unix and Linux for 25 years. He’s happy to hear from readers; see https://www.jpeek.com/contact.html.

[Read previous article] [Read next article]
[Read Jerry’s other Linux Magazine articles]