> pictools > doc > MakingLogos
> pictools > doc > MakingLogos
Pictools: Physpics Page Pieces
V3.3 (445)
Making Logos
Logos and haloes: making irregular images with ImageMagick
two mugs for ZweiBieren

A logo or icon is an image, usually small, displayed to represent an object, operation, or concept. Pictools pages display a logo on the right side of the title line. Though mostly decorative, the logo is per-directory and distinguishes one directory from another.

Many logos are irregular, that is, non-rectangular. No common image format supports irregular images; they must be constructed by making transparent the unwanted portions of a rectangular image. For the web the right choice for small illustrations is the png format. For photos, jpg is fine, especially if that is what your camera delivers. If it delivers large raw format images, your web site will respond faster if you convert them to .jpg. The ImageMagick command to reformat a file from, say, jpg to png is

		convert file.jpg file.png

Comments on popular image formats

Fmt sample file sizes image
80x72 1157x1447
.png 5316 1329918 excellent full or partial Designed for browser display.
.jpg 5087 726828 very good no Good compression but picture is blurred (imperceptibly).
.gif 2937 978947 speckled 0/1 Speckled because the number of colors is limited. A pixel must be fully opaque or fully transparent.
.tiff 5110 5029395 excellent full or partial Suitable for printing; file size can be large.
.bmp 23178 5024122 excellent full or partial Saves every single byte; usually huge files.

Clipping To an Irregular Image

Many tools are available for clipping an irregular shape from a rectangular one. Photoshop is great, but pricey. Clippingmagic.com does a great job of finding clip edges for free. They charge for downloading the result, though not for taking a screen dump. Others tools from a google search for "clipping image online" were too klutzy to mention. ImageMagick has some masking options that can help.

My poor-man's approach to making an irregular image relies on Window's paint amd ImageMagick. I start with an image in a window, be it a photoviewer, PowerPoint, paint, or some other. Typing ALT-PrtScrn copies the window to the cutbuffer. Then I open paint and paste the image. With paint I resize the image as needed and color the unwanted parts with some color not found in the image. (identify -verbose lists the colors that do appear in the image.) By flood-filling with various colors, I can spot the image pixels that shold not be part of the image. Editing the color gives me the exact color of the background as an rgb triple: rrr ggg bbb. After saving the image, I get a transparent background via ImageMagick:

    mogrify -transparent 'rgb(rrr,ggg,bbb)' file.png

The paint/mogrify steps can be repeated to refine the image.

Halo Removal

bear with halo
Mouse me!

The bear icon at left (originally from ablewebs.com) has a surrounding "halo" of white pixels. Roll your mouse over it and, tada, no halo. For several years I have held fast to the delusions that I could automate the process of removing a halo and understand ImageMagick. I am now disillusioned on both counts. My various halo algorithms either failed to remove the halo or removed pieces of the image along with the halo.

ImageMagick is indeed a useful and enormous of tools from many authors. And there's your problem: too many cooks. There is considerable diversity in defaults and code quality. If you have an image modification task, ImageMagick can do it; but be ready to invest some time figuring out how. Perhaps you will find useful my spreadsheet of ImageMagick option categories.

The simplest algorithm--appended below--is as good as, and faster than, my other attempts at an automated algorithm. It differs only in small ways from the mogrify -transparent command noted above. The transparent color is taken to be the color of the upper left pixel, so it need not be provided. A "fuzz" is applied to that color so a small range of colors is eliminated altogether. And an option is provided to ensure that transparency is applied only to outside pixels, so pixels within the image are not affected. Repeated application of paint and this algorithm with different background colors and remove multiple colors from the halo, leaving fewer to be manually removed.

The convert command

The heart of the program is a lengthy convert command. It extracts the background color, finds the pixels of that color in the image, and makes them transparent. Rather than operate on a single image, ImageMagick manipulate a sequence of images. The sequence can even be partitioned so the tail end is the current sequence while the rest awaits completion of that tail. Cleverly, the "(" operator starts a new partition and a ")" removes the most recent "(" appending the current sequence to the previous. The algorithm exploits this by creating each operand within a subsequence to build up a sequence of images to be com,bined in the final operation. Comments on each line show the contents of the sequence at that point. Here is what they mean:

orig Original image. The upper left pixel dictates the background color.
( Sub-sequence separator. The current sequence is to the right.
bkgd All pixels are the background color.
diff Difference between orig and bkgd.
mask Black for transparent and white for the image to retain. If -i is set, noislands deletes interior blobs from the mask.
masked Result, but not yet trimmed.

Guided by the contents of the sequence, you may (perhaps just barely) be able to follow the code in these steps:

find the background pixel and makes a image of just that color -sparse-color
subtract the background from  the image to get an image where pixels close to the background are zero -composite
convert to zero those values that are close to zero -threshold
optionally remove islands of transparent pixels from the central image ${xaX:xx}...
revise the original image to be transparent according to the mask -composite
trim the image to remove transparent edge rows and colum,ns $trim
write the file as a png $outfile

The convert command

convert $infile                                        \
                                         # orig        \
    \( -clone 0 -sparse-color                          \
                                         # orig ( orig \
        voronoi '0,0 %[pixel:p{0,0}]' \)               \
                                         # orig bkgd   \
    \( -clone 0 -clone -1                              \
                            # orig bkgd ( orig bkgd    \
        -compose Difference -composite \) -delete -2   \
                                         # orig diff   \
    \( -clone -1 -threshold $fuzzpct% \) -delete -2    \
                                         # orig mask   \
    ${xiA:gx} ${xiB:q} ${xiC:gx} ${xiD:gx} ${xiE:gx}   \
                                         # orig mask   \
    -alpha Off -compose CopyOpacity -composite         \
                                         # masked      \
    $trim                                              \
exit 0

Managing the image sequence

After considerable tinkering with (, -clone, -swap, -delete, and ), I evolved a clear, simple strategy for managing the seqence of images. When I need a sequence of images derived from an original, I derive one at a time appending each to the sequence before going on. The result looks like the line

\( -clone -1 -threshold $fuzzpct% \) -delete -2

The initial ( starts a new sequence. The -clone fetches the operand from the pending sequence. The -threshold modifies the operand to produce the result of the subsequence. The ) removes the sequence separator inserted by (. finally, the -delete removes the no-longer-needed source for the operation. Such a sequence is used thrice; once each to generate bkgd, diff, and mask. It also appears in the island removal code.

Island removal

basket with gardening tools - the handle suirrounds a hole in the image where the background shows through

The script (below) affords the option -i, which ensures that there are no transparent pixels embedded in the image. This is not the default. It is not a good choice when the image has a hole like that at the right. But it is useful becuase some images have inside pixels that may be close to the background color. The script adds extra steps to the convert command by setting values for the xiX... shell variables. (It cannot be one variable in csh due to quoting issues that are solved with :gx vs :q)

set xiA=" ( -clone -1 -channel RGB -bordercolor Black -compose src"
set xiB="     -border 1 -evaluate Multiply .5 -fill white -draw "
set xiC="color 0,0 floodfill"
set xiD=" -threshold 90% -negate -shave 1x1 ) " 
set xiE=" -delete -2 "      

This sequence is seriously convoluted, involving switching the mask between black-white and white-black. Understanding it is left as an exercise for the reader.

The complete script


# Convert image so the background is transparent and without halo
# nohalo [-f fuzz-percent] [-i] [-t]  file [outfile]
# Converts  to , while making transparent all
# pixels close in color to the original upper left corner pixel.

# -f fuzz-percent -- Range of transparent colors.
#     Pixels having colors within this percent of the 
#  	  transparency color are also deemed  to be transparent.  
set fuzzpct="5"

# -i -- no islands. The transparent region strictly surrounds the image.
#     Pixels of the background color within the image are unaffected;
#     there will be no "islands" of transparency.
set xiA=""
set xiB=""
set xiC=""
set xiD=""

# -t -- Omit the -trim step. Otherwise the default behavior is to 
#     trim the final image, removing all transparent border edges.
#     Trimming will reduce the size of the image.
set trim="-trim"

# file -- the input file. 
set infile=""

# outfile -- the output file. If omitted, use input file name,
#     replacing its extension with "-nohalo.png"
set outfile=""

#   It is an error to list more than two file names. 

while ( $# != 0 )
  switch ("$1") 
    case "-f": 
      set fuzzpct=$2 
    case "-i":   # four variables because csh sucks
      set xiA=" ( -clone -1 -channel RGB -bordercolor Black -compose src  \
              -border 1 -evaluate Multiply .5 -fill white -draw "
      set xiB="color 0,0 floodfill"
      set xiC=" -threshold 90% -negate -shave 1x1 ) "  
	  set xiD=" -delete -2 "
    case "-t":
      set trim=""
    case "-*":
      echo unexpected "'$1'"
      goto usage

# process input and output filenames
if ( $# == 0 ) then 
  echo no infile
  goto usage

set infile=$1
if ( $# != 0 ) then
  set outfile=$1
  # generate outfile name from infile
  set outfile=${infile:r}-nohalo.png
if ( $# != 0 ) then
  echo unexpected "'$*'"
  goto usage

# make sure input file does not have transparency
if ( `identify -format '%A\n'` == "True" ) then 
	echo "\*\*\* Image $infile already has an alpha channel"
	exit 3

# The ImageMagick command follow. Comments on each line show 
# the set of images constructed to that point. A left parens
# divides saved images from operands for the next operation.
#     orig - the original image
#         the upper left pixel dictates the background color
#     bkgd -- all pixels are the background color 
#     diff -- difference between orig and bkgd
#     mask -- a copy of image with black for transparent
#          and white for the image to retain
#          if -g is set, noislands deletes interior blobs from the mask, 
#          but does not otherwise alter the stack 
#     masked -- desired result, but not yet trimmed

convert $infile                              # orig                   \
    \( -clone 0 -sparse-color                # orig ( orig            \
        voronoi '0,0 %[pixel:p{0,0}]' \)     # orig bkgd              \
    \( -clone 0 -clone -1                    # orig bkgd ( orig bkgd  \
        -compose Difference -composite \) -delete -2  # orig diff     \
    \( -clone -1 -threshold $fuzzpct% \) -delete -2   # orig mask     \
    ${xiA:gx} ${xiB:q} ${xiC:gx} ${xiD:gx}      # orig mask           \
    -alpha Off -compose CopyOpacity -composite  # masked              \
    $trim                                                             \
exit 0      
 echo "*** usage: csh nohalo.csh [-i] [-f fuzz-precent] [-t] file [outfile]"
 echo "***  -i removes islands   -t skips trimming the result"
 exit 99
Copyright © 2023 ZweiBieren, All rights reserved. Feb 5, 2023 17:05 GMT Page maintained by ZweiBieren