snibgo's ImageMagick pages

Standalone programs


I assume that:

See also my Process modules page.



My programs follow certain conventions. They may be unusual, even idiosyncratic.


The source code of my standalone programs reside in %IMSRC%\snibgo. %IMSRC% is the IM development directory, which varies with each version number. When I install a new version of IM, I copy the source code to the new directory. This means I always have a version of my code that works with previous versions of IM. This snibgo directory is at the same level in the IM directory structure as magick, wand and filters. blah.

Q? HDRI? I would like Quantum and HDRI to be ImageMagick run-time options, instead of compile-time optons. Sadly, this isn't the case.

I don't publish binaries. If you want to use my standalone programs, you need to compile them.

Building. Make?

cd %IMSRC%\snibgo
$ export PKG_CONFIG_PATH=~/iminst32f/lib/pkgconfig
$ cc -o wand wand.c `pkg-config --cflags --libs MagickWand`
$ cp wand.exe ~/iminst32f/bin/


cd %IMSRC%\snibgo
$ export PKG_CONFIG_PATH=${IMDEV}../lib/pkgconfig
$ cc -o wand wand.c `pkg-config --cflags --libs MagickWand`
$ cp wand.exe ${IMDEV}

%IMDEV% (or, as bash calls it, ${IMDEV}) is my usual Q32 HDRI development directory. Insead of %IMDEV%, I could choose for example %IMG16i% or other Quantum and integer/float build.

Compiled binaries are copied to %IMDEV%.

echo %IMDEV% 

For convenience, I put this in a bash script that I can call from Windows CMD.

pushd %IMSRC%\snibgo
bash  wand hellowld snibconv swim

Script or program or process module?

When awkward processing is needed, more than is available from a single convert command, we can write a script, or a standalone program, or a process module. The choice is guided by:

Simple programs

hellowld: Hello World

%IMDEV%hellowld --help 
option -h

Writes some text information.

Usage: hellowld {options}
  -h  --help     write usage data and exit
  -v  --verbose  write text information about the processing
  -i  --infile   filename(s) for input
  -o  --outfile  filename for output
  -m  --multiply one or three comma-separated numbers, to multiply each RGB channel
      --version  write version data and exit

If no infile is given, the program does nothing. infile is a comma-separated list of filenames. If a filename contains a space or comma, it must be quoted. The program reads all the inputs. multiply is optional, and is a list of three comma-separated floating-point numbers. The RGB channels will be multiplied by these numbers, with each image updated in place. If only one number is supplied, each channel is multiplied by that number. If outfile is supplied, the image or images are written to that output in the usual ImageMagick way.

%IMDEV%hellowld ^
  -i rose: ^
  --multiply 0.5,1,1.5 ^
  -o sp_rose_mult.jpg

Note: The IM function MagickReadImage() will accept bad names like "rose:.wizard:" and process as if the argument was simply "rose:". The usual convert program also has this behaviour.

snibconv: snibgo's convert

This processes no arguments, but passes them all to ConvertImageCommand(). So this program does whatever the convert program does.

%IMDEV%snibconv ^
  rose: ^
  -resize 400 ^

Swim: a simple GUI viewer and editor

Put the line ...

#include <windows.h>

... at the top of the program, and include whatever Microsoft Windows stuff you want.

See the SWIM page.

Calling process modules

Call MagickExport MagickBooleanType InvokeDynamicImageFilter(const char *tag, Image **images,const int argc,const char **argv,ExceptionInfo *exception) Check return status: MagickTrue is success.


This does the same processing as the script nLightest.bat, but faster.

  1. Read the input image.
  2. If debug output is required, clone the input image to the debug output.
  3. Call process module onewhite for the input image.
  4. If a lightest pixel was found, and debug output is required, update the debug output.
  5. Repeat from (3) until we have found enough or no more lightest pixels are found.
  6. If debug output is required, write it.


For convenience, .bat scripts are also available in a single zip file. See Zipped BAT files.


@rem From image %1, finds (%2) lightest points.
@rem After each point is found,
@rem    blacks out pixels within radius (r) of that point
@rem where r = min(width,height) * %3/100
@rem %3 defaults to 10 (percent).
@rem Returns number of points found. Could be < n, or even zero (?).
@rem Also uses:
@rem   nlDEBUG if 1, creates debugging image.
@rem   nlPOINTSIZE optional, pointsize for debug images.
@rem   nlSTROKEWIDTH for debug.
@rem   nlONLY_WHITE if 1, doesn't auto-level,
@rem      so finds only points that are exactly white.
@rem Returns:
@rem   nl_nFound number of points found, integer >= 0
@rem   echos list of coordinates.
@rem Updated:
@rem   24-May-2016 v7 needs -channel RGB for -auto-level.

@if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 nl

for /F "usebackq" %%L in (`cygpath %TEMP%`) do set CYGTEMP=%%L

@set TMPFILE=%TEMP%\%~n1_%sioCODE%.miff
@set CYGTMPFILE=%CYGTEMP%\%~n1_%sioCODE%.miff
@set DEBUGDRAW=%TEMP%\%~n1_%sioCODE%_dbgdrw.txt

del %DEBUGDRAW% 2>nul

if "%nlPOINTSIZE%"=="" set nlPOINTSIZE=20

if "%nlSTROKEWIDTH%"=="" set nlSTROKEWIDTH=1

set MAX_FIND=%2
if "%MAX_FIND%"=="" set MAX_FIND=10

set PC_RAD=%3
if "%PC_RAD%"=="" set PC_RAD=10

set AUTOLEV=-channel RGB -auto-level +channel
if "%nlONLY_WHITE%"=="1" set AUTOLEV=

for /F "usebackq" %%L in (`%IM%convert ^
  -ping ^
  %INFILE% ^
  -format "PIX_RAD=%%[fx:int(min(w,h)*%PC_RAD%/100+0.5)]" ^
  info:`) do set %%L

%IM%convert %INFILE% -colorspace Gray %AUTOLEV% %TMPFILE%

if "%nlDEBUG%"=="1" %IM%convert %INFILE% %TMPDEBUGFILE%

set /A nFound=0


set MAX=
set whiteX=

for /F "usebackq tokens=1-3 delims=:, " %%W ^
in (`%IMDEV%convert ^
    %TMPFILE% ^
    -process onewhite ^
    NULL: 2^>^&1`) ^
do (
  if "%%W"=="onewhite" (
    set MAX=%%W
    set whiteX=%%X
    set whiteY=%%Y
    set /A whiteX2=%%X+%PIX_RAD%

if "!MAX!"=="onewhite" if "!whiteX!" neq "none" (
  %IM%convert ^
    %TMPFILE% ^
    -fill #000 -draw "circle %whiteX% %whiteY% %whiteX2% %whiteY%" ^
    %AUTOLEV% ^

  if "%nlDEBUG%"=="1" (
      echo fill None stroke #f00 circle %whiteX%,%whiteY%,%whiteX2%,%whiteY%
      echo fill #f00 stroke None text %whiteX%,%whiteY% '!nFound!'
    ) >>%DEBUGDRAW%

  set /A nFound+=1
  echo %whiteX%,%whiteY%
  if !nFound! LSS %MAX_FIND% goto loop

rem if "%nlDEBUG%"=="1" %IM%convert %TMPDEBUGFILE% %DEBUGFILE%

if "%nlDEBUG%"=="1" if exist %DEBUGDRAW% %IM%convert ^
  %INFILE% ^
  -strokewidth %nlSTROKEWIDTH% ^
  -pointsize %nlPOINTSIZE% ^
  -draw @%DEBUGDRAW% ^

call echoRestore

@endlocal & set nl_nFound=%nFound%& set nlDEBUG_FILE=%DEBUGFILE%

The following are in the directory %IMSRC%\snibgo:

export PKG_CONFIG_PATH=${IMDEV}../lib/pkgconfig

while [ $# -gt 0 ]

  cc -o "$1" "$1".c -mwindows -Wall -Wa,-march=corei7,-mtune=corei7 `pkg-config --cflags --libs MagickWand`

  cp "$1".exe ${IMDEV}

  shift   # next option


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wand/MagickWand.h>
#include <getopt.h>
#include <windows.h>
#include <winuser.h>

typedef struct {
  int version;
  int verbose;
  char * infile;
  char * outfile;
  MagickBooleanType doMult;
  double multR;
  double multG;
  double multB;
} ProgOptT;

static void testWin (void)
  printf ("testWin\n");
  HWND hw;
  printf ("GetDoubleClickTime=%i\n", GetDoubleClickTime ());
  //InitializeFlatSB (hw);

static void version (void)
  printf ("hellowld v1.0 (c) 2015 Alan Gibson 22-Dec-2015\n");
  exit (0);

static void usage (void)
  printf ("Writes some text information.\n\n");

  printf ("Usage: hellowld {options}\n");
  printf ("  -h  --help     write usage data and exit\n");
  printf ("  -v  --verbose  write text information about the processing\n");
  printf ("  -i  --infile   filename(s) for input\n");
  printf ("  -o  --outfile  filename for output\n");
  printf ("  -m  --multiply one or three comma-separated numbers, to multiply each RGB channel\n");
  printf ("      --version  write version data and exit\n");
  exit (0);

static void GetOptions (int argc, char **argv, ProgOptT * po)
  struct option long_options[] = {
    /* Second field is one of: no_argument, required_argument, optional_argument. */
    /* Third field is 0 or pointer to int. */

    /* These options set a flag. */
    {"version", no_argument,       &po->version, 1},
    {"verbose", no_argument,       &po->verbose, 1},
    {"brief",   no_argument,       &po->verbose, 0},
    /* These options don't set a flag.
       We distinguish them by their indices. */
    {"help",    no_argument,       0, 'h'},
    {"add",     no_argument,       0, 'a'},
    {"append",  no_argument,       0, 'b'},
    {"create",  required_argument, 0, 'c'},
    {"delete",  required_argument, 0, 'd'},
    {"file",    required_argument, 0, 'f'},
    {"infile",  required_argument, 0, 'i'},
    {"outfile", required_argument, 0, 'o'},
    {"multiply",required_argument, 0, 'm'},
    {0, 0, 0, 0}

  char * short_options = "hvabc:d:f:i:o:m:";

    okay = MagickTrue;

  po->version = po->verbose = 0;
  po->infile = po->outfile = NULL;
  po->doMult = MagickFalse;
  po->multR = po->multG = po->multB = 0;

  int c;

  while (1)
    int option_index = 0;

    c = getopt_long (argc, argv, short_options,
                     long_options, &option_index);

    /* Detect the end of the options. */
    if (c == -1) break;

//    printf ("char [%c]\n", c);

    switch (c)
      case 0:
//        printf ("long option with option_index `%i'\n", option_index);
        /* If this option set a flag, do nothing else now. */
        if (long_options[option_index].flag != 0)
//        printf ("option %s", long_options[option_index].name);
//        if (optarg)
//          printf (" with arg %s", optarg);
//        printf ("\n");

      case 'h':
        puts ("option -h\n");
        usage ();

      case 'v':
        puts ("option -v\n");
        po->verbose = 1;

      case 'a':
        puts ("option -a\n");

      case 'b':
        puts ("option -b\n");

      case 'c':
        printf ("option -c with value `%s'\n", optarg);

      case 'd':
        printf ("option -d with value `%s'\n", optarg);

      case 'f':
        printf ("option -f with value `%s'\n", optarg);

      case 'i':
        printf ("option -i with value `%s'\n", optarg);
        po->infile = optarg; // FIXME: is this safe?

      case 'o':
        printf ("option -o with value `%s'\n", optarg);
        po->outfile = optarg;

      case 'm': {
        po->doMult = MagickTrue;
        printf ("option -m with value `%s'\n", optarg);
        int n = sscanf (optarg, "%lg,%lg,%lg", &po->multR, &po->multG, &po->multB);
        if (n!=1 && n!=3) {
          printf ("--multiply needs 1 or 3 numbers.\n");
          okay = MagickFalse;
        if (n==1) po->multB = po->multG = po->multR;

      case '?':
        /* getopt_long already printed an error message. */

        abort ();

  if (po->version) {
    version ();

  /* Print any remaining command line arguments (not options). */
  if (optind < argc)
    printf ("non-option ARGV-elements: ");
    while (optind < argc)
      printf ("%s ", argv[optind++]);
    putchar ('\n');
    okay = MagickFalse;

  if (!okay) exit (-1);

static void WrOptions (ProgOptT *po)
  fprintf (stderr, "Options:\n");
  if (po->verbose) fprintf (stderr, "  --verbose\n");
  if (po->infile)  fprintf (stderr, "  --infile  %s\n", po->infile);
  if (po->outfile) fprintf (stderr, "  --outfile %s\n", po->outfile);
  if (po->doMult)  fprintf (stderr, "  --multiply %lg,%lg,%lg\n", po->multR, po->multG, po->multB);

#define ThrowWandException(wand) \
{ \
  char \
    *description; \
  ExceptionType \
    severity; \
  description=MagickGetException(wand,&severity); \
  (void) fprintf(stderr,"%s %s %lu %s\n",GetMagickModule(),description); \
  description=(char *) MagickRelinquishMemory(description); \
  exit(-1); \

static MagickBooleanType ReadInputs (ProgOptT *po, MagickWand *magick_wand)
// po->infile is comma-separated list of filenames.
// Each filename may be quoted with single-quote or double-quote characters.
    status = MagickTrue;

  const char
    sq = '\'',
    dq = '"',
    cSep = ',';

  char * p = po->infile;

  char quote='\0';

//  printf ("p:[%s]\n", p);

  char * pEnd;

  while (*p) {
    while (*p==' ') p++;
    if (*p==sq || *p==dq) {
      quote = *p;
      while (*p==' ') p++;
      pEnd = strchr (p, quote);
      if (pEnd) {
        printf ("pEnd:[%s]\n", pEnd);
        *pEnd = '\0';
    } else {
      pEnd = p;

    char * pSep = strchr (pEnd, cSep);
    if (pSep) {
//      printf ("pSep:[%s]\n", pSep);
      *pSep = '\0';
    if (po->verbose) fprintf (stderr, "reading:[%s]\n", p);

    status=MagickReadImage (magick_wand, p);
    if (status == MagickFalse) {
      ThrowWandException (magick_wand);
      return status;

    p = pSep ? pSep + 1 : strchr (p, '\0');

  return status;

static MagickBooleanType Multiply (
  WandView *pixel_view,
  const ssize_t y,const int id,void *context)



  register long

  ProgOptT *po = context;

  printf ("%li ", y);

  for (x=0; x < (long) (extent.width); x++)
    PixelGetMagickColor(pixels[x],&pixel);   *= po->multR; *= po->multG;  *= po->multB;

static void DoOneImage (ProgOptT *po, MagickWand *magick_wand)


  if (po->verbose) fprintf (stderr, "%lix%li\n",
    MagickGetImageWidth (magick_wand),
    MagickGetImageHeight (magick_wand)

  if (po->doMult)  {
    // Update the image in-place.

    if (wand_view == (WandView *) NULL)

    status=UpdateWandViewIterator(wand_view,Multiply,(void *) po);
    if (status == MagickFalse)



int main (int argc, char **argv)



  GetOptions (argc, argv, &ProgOpt);

  if (ProgOpt.verbose) {
    WrOptions (&ProgOpt);

  if (ProgOpt.infile) {
    MagickWandGenesis ();
    magick_wand=NewMagickWand ();  

    status = ReadInputs (&ProgOpt, magick_wand);

    if (status == MagickFalse)
      ThrowWandException (magick_wand);

      Do something with or to each image.
    MagickResetIterator (magick_wand);
    while (MagickNextImage (magick_wand) != MagickFalse) {
      DoOneImage (&ProgOpt, magick_wand);

      Write the image(s) then destroy it/them.
    if (ProgOpt.outfile) {
      if (ProgOpt.verbose) fprintf (stderr, "Writing [%s]\n", ProgOpt.outfile);
      status=MagickWriteImages (magick_wand, ProgOpt.outfile, MagickTrue);
      if (status == MagickFalse)
        ThrowWandException (magick_wand);

    magick_wand=DestroyMagickWand (magick_wand);
    MagickWandTerminus ();
  } else {
    if (ProgOpt.verbose) fprintf (stderr, "Nothing to do.");

  testWin ();



#include <stdio.h>
#include <stdlib.h>
#include <wand/MagickWand.h>

int main (int argc, char **argv)

  ExceptionInfo * exception = AcquireExceptionInfo ();
  ImageInfo * image_info = AcquireImageInfo ();

  status = ConvertImageCommand(image_info, argc, argv, NULL, exception);
  if (status == MagickFalse)

  image_info = DestroyImageInfo (image_info);
  exception = DestroyExceptionInfo (exception);

  return (status==MagickFalse) ? 1 : 0;

All images on this page were created by the commands shown, using:

%IM%identify -version
Version: ImageMagick 6.9.5-3 Q16 x86 2016-07-22
Copyright: Copyright (C) 1999-2015 ImageMagick Studio LLC
Visual C++: 180040629
Features: Cipher DPC Modules OpenMP 
Delegates (built-in): bzlib cairo flif freetype jng jp2 jpeg lcms lqr openexr pangocairo png ps rsvg tiff webp xml zlib
%IMDEV%identify -version
Version: ImageMagick 6.9.3-7 Q32 x86_64 2016-07-16
Copyright: Copyright (C) 1999-2016 ImageMagick Studio LLC
Features: Cipher DPC HDRI Modules OpenMP 
Delegates (built-in): bzlib cairo fftw fontconfig freetype fpx jbig jng jpeg lcms ltdl lzma pangocairo png rsvg tiff webp wmf x xml zlib

To improve internet download speeds, some images may have been automatically converted (by ImageMagick, of course) from PNG to JPG.

Source file for this web page is stanprog.h1. To re-create this web page, run "procH1 stanprog".

This page, including the images, is my copyright. Anyone is permitted to use or adapt any of the code, scripts or images for any purpose, including commercial use.

Anyone is permitted to re-publish this page, but only for non-commercial use.

Anyone is permitted to link to this page, including for commercial use.

Page version v1.0 23-March-2015.

Page created 30-Sep-2016 13:53:56.

Copyright © 2016 Alan Gibson.