snibgo's ImageMagick pages

Standalone programs

blah.

I assume that:

See also my Process modules page.

blah.

Programming conventions

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

Environment

The source code of my standalone programs reside in %IMSRC%\snibgo. %IMSRC% is the IM v6 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.

There is also a %IM7SRC% directory, for v7 builds.

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?

A basic recipe for building programs, starting from the CMD prompt, is:

cd %IMSRC%\snibgo
bash
$ 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% 
C:\cygwin64\home\Alan\imdevins6937\bin\ 

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

pushd %IMSRC%\snibgo
bash buildone.sh  wand hellowld snibconv swim
bash buildwand.sh nlightest
popd

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

hellowld.c is a hello-world type program. It does nothing particularly useful, but shows the general principle of reading an image, processing it, and writing an output.

%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 given 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 given a list of one or 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
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

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

%IMDEV%snibconv ^
  rose: ^
  -resize 400 ^
  sp_rose_res.jpg
sp_rose_res.jpg

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.

Useful program: nlightest

The program nlightest.c find the (n) lightest pixels in an image, and lists the coordinates, with the lightest first. In addition, after finding each coordinate, it can then ignore all pixels within a certain radius of that coordinate. It writes the coordinates as text to stdout.

We already have a script that does this: nLightest.bat. The script's algorithm is:

  1. Read the input image and convert to grayscale. Write that to a file.
  2. Read the file; find the lightest pixel.
  3. If the lightest pixel isn't black, output the coordinates and draw a black circle on the image centred on that pixel.
  4. Write the image to a file.
  5. Repeat from (2) until we have found enough or no more non-black pixels are found.
  6. If output is required, write it.

The program's algorithm is:

  1. Read the input image and convert to grayscale.
  2. Find the lightest pixel.
  3. If the lightest pixel isn't black, output the coordinates and draw a black circle on the image centred on that pixel.
  4. Repeat from (2) until we have found enough or no more non-black pixels are found.
  5. If output is required, write it.

The compiled program does essentially the same processing as the script, but much faster because it doesn't need to store the result of each iteration in a file. The script reads an image twice and writes it once per lightest pixel found, plus an overhead. The compiled program does no IO per lightest pixel found, plus an overhead of one read.

The --help option lists the options:

%IMDEV%nlightest --help 
Writes coordinates of (n) lightest pixels to stdout.

Usage: nlightest {options}
  -h  --help       write usage data and exit
  -v  --verbose    write text information about the processing to stderr
  -i  --infile     filename for input
  -o  --outfile    filename for output
  -n  --numToFind  number to find [default 10]
  -r  --radius     radius to be ignored; can be suffixed % or c or p
      --version    write version data and exit
%IMDEV%nlightest --version 
nlightest v1.0 (c) 2017 Alan Gibson 5-May-2017
Copyright (C) 1999-2016 ImageMagick Studio LLC
ImageMagick 6.9.3-7 Q32 x86_64 2016-09-30 http://www.imagemagick.org

If no options are given, the program does nothing. To do anything useful, an --infile should be given.

The --numToFind option needs an integer. It limits the number of lightest pixels that will be found. The special value of 0 means there is no limit.

The --radius option gives a distance. No result will be within that distance of any other result. If the radius is 0, results may be adjacent. The number may be suffixed by a character '%' or 'c' or 'p'. '%' and 'c' both mean a percent of the minimum width or height, eg 12.5% or 12.5c. 'p' means a proportion of the minimum width or height, eg 0.125p.

Using "--numToFind 0 --radius 0" will list all the coordinates in the image, starting with the lightest. (If you want that result, a faster method is available: see Process modules: sort pixels blue.)

The --outfile option is not normally useful. When the process completes, the output image is completely black. When the process stops early because --numToFind has been used, the output will be grayscale, with black circles centered on the found coordinates.

We use the script, then the program, to find the ten lightest pixels in toes.png, with an exclusion radius that is 5% of the minimum dimension. We use a timing progam (timec.exe, unpublished) to measure the elapsed times.

First, using the script:

timec %PICTBAT%nLightest toes.png 10 5 
255,217
225,45
266,37
93,44
266,141
188,121
264,117
107,40
244,50
253,145

TimeC F:\pictures\nLightest toes.png 10 5
Seconds: 4.75

Now, using the program:

timec %IMDEV%nlightest -itoes.png -n10 -r5c 
255,217
225,45
266,37
93,44
266,141
188,121
264,117
107,40
244,50
253,145

TimeC C:\cygwin64\home\Alan\imdevins6937\bin\nlightest -itoes.png -n10 -r5c
Seconds: 0.20

We get the same result. The compiled program is about 20 times as fast.

Scripts and C source

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

nLightest.bat

@rem From image %1, finds (%2) lightest points.
@rem
@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
@rem Returns number of points found. Could be < n, or even zero (?).
@rem
@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
@rem Returns:
@rem   nl_nFound number of points found, integer >= 0
@rem   echos list of coordinates.
@rem
@rem Updated:
@rem   24-May-2016 v7 needs -channel RGB for -auto-level.
@rem   7-May-2017 "+antialias" the circles.



@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 LISTFILE=%BASENAME%_%sioCODE%.lis
@set TMPDEBUGFILE=%BASENAME%_%sioCODE%_dbg.miff
@set DEBUGFILE=%BASENAME%_%sioCODE%_dbg%EXT%
@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

:loop

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% ^
    +antialias -fill #000 ^
    -draw "circle %whiteX% %whiteY% %whiteX2% %whiteY%" ^
    %AUTOLEV% ^
    %TMPFILE%

  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% ^
  %DEBUGFILE%


call echoRestore

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

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

buildone.sh

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


while [ $# -gt 0 ]
  do

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

  cp "$1".exe ${IMDEV}

  shift   # next option
done

hellowld.c

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

#include "../vsn_defines.h"

#if IMV6OR7==6
#include <wand/MagickWand.h>
#elif IMV6OR7==7
#include <MagickWand/MagickWand.h>
#else
#error IMV6OR7 defined but not valid
#endif


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");
  printf ("%s\n", MagickGetCopyright());
  printf ("%s\n", MagickGetVersion(NULL));
  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:";

  MagickBooleanType
    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)
          break;
//        printf ("option %s", long_options[option_index].name);
//        if (optarg)
//          printf (" with arg %s", optarg);
//        printf ("\n");
        break;

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

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

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

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

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

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

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

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

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

      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;
        break;
      }

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

      default:
        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.
{
  MagickBooleanType
    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;
      p++;
      while (*p==' ') p++;
      pEnd = strchr (p, quote);
      if (pEnd) {
        printf ("pEnd:[%s]\n", pEnd);
        *pEnd = '\0';
        pEnd++;
      }
    } 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)
{
  RectangleInfo
    extent;

  PIX_INFO
    pixel;

  PixelWand
    **pixels;

  register long
    x;

  ProgOptT *po = context;

  printf ("%li ", y);

  extent=GetWandViewExtent(pixel_view);
  pixels=GetWandViewPixels(pixel_view);
  for (x=0; x < (long) (extent.width); x++)
  {
    PixelGetMagickColor(pixels[x],&pixel);
    pixel.red   *= po->multR;
    pixel.green *= po->multG;
    pixel.blue  *= po->multB;
#if IMV6OR7==6
    PixelSetMagickColor(pixels[x],&pixel);
#else
    PixelSetPixelColor(pixels[x],&pixel);
#endif
  }
  return(MagickTrue);
}


static void DoOneImage (ProgOptT *po, MagickWand *magick_wand)
{
  MagickBooleanType
    status;

  WandView
    *wand_view;

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

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

    wand_view=NewWandView(magick_wand);
    if (wand_view == (WandView *) NULL)
      ThrowWandException(magick_wand);

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

    wand_view=DestroyWandView(wand_view);
  }

}


int main (int argc, char **argv)
{
  MagickBooleanType
    status;

  MagickWand
    *magick_wand;

  ProgOptT
    ProgOpt;

  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 ();

  return(0);
}

snibconv.c

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

int main (int argc, char **argv)
{
  MagickBooleanType
    status;

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

  status = ConvertImageCommand(image_info, argc, argv, NULL, exception);
  if (status == MagickFalse)
    MagickError(exception->severity,exception->reason,exception->description);

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

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

nlightest.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include "../vsn_defines.h"

#if IMV6OR7==6
#include <wand/MagickWand.h>
#elif IMV6OR7==7
#include <MagickWand/MagickWand.h>
#else
#error IMV6OR7 defined but not valid
#endif


typedef struct {
  int  version;
  int  verbose;
  char * infile;
  char * outfile;
  int  numToFind;
  char * sRadius;
} ProgOptT;


static void version (void)
{
  printf ("nlightest v1.0 (c) 2017 Alan Gibson 5-May-2017\n");
  printf ("%s\n", MagickGetCopyright());
  printf ("%s\n", MagickGetVersion(NULL));
  exit (0);
}

static void usage (void)
{
  printf ("Writes coordinates of (n) lightest pixels to stdout.\n\n");

  printf ("Usage: nlightest {options}\n");
  printf ("  -h  --help       write usage data and exit\n");
  printf ("  -v  --verbose    write text information about the processing to stderr\n");
  printf ("  -i  --infile     filename for input\n");
  printf ("  -o  --outfile    filename for output\n");
  printf ("  -n  --numToFind  number to find [default 10]\n");
  printf ("  -r  --radius     radius to be ignored; can be suffixed %% or c or p\n");
  printf ("      --version    write version data and exit\n");
  exit (0);
}

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);
  fprintf (stderr, "  --numToFind %i\n", po->numToFind);
  fprintf (stderr, "  --radius %s\n", po->sRadius);
}

static void GetOptions (int argc, char **argv, ProgOptT * po)
// If there is a problem, this doesn't return.
{
  struct option long_options[] = {
    /* Second field is one of: no_argument, required_argument, optional_argument. */
    /* Third field is 0 or pointer to int. */
    /* Fourth field is:
         an integer, if third is a pointer to int;
         a character index, otherwise.
    */

    /* 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'},
    {"infile",  required_argument, 0, 'i'},
    {"outfile", required_argument, 0, 'o'},
    {"numToFind", required_argument, 0, 'n'},
    {"radius", required_argument, 0, 'r'},
    {0, 0, 0, 0}
  };

  char * short_options = "hvi:o:n:r:";

  MagickBooleanType
    okay = MagickTrue;

  po->version = po->verbose = 0;
  po->infile = po->outfile = NULL;
  po->numToFind = 10;
  po->sRadius = "";

  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 sets a flag, do nothing else now. */
        if (long_options[option_index].flag != 0)
          break;
        //printf ("option %s", long_options[option_index].name);
        if (optarg)
          printf (" with arg %s", optarg);
        printf ("\n");
        break;

      case 'h':
        usage ();
        break;

      case 'v':
        po->verbose = 1;
        break;

      case 'i':
        po->infile = optarg;
        break;

      case 'o':
        po->outfile = optarg;
        break;

      case 'n':
        po->numToFind = atoi(optarg);
        break;

      case 'r':
        po->sRadius = optarg;
        break;

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

      default:
        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 (po->verbose) {
    WrOptions (po);
  }

  if (!okay) exit (-1);
}


// ThrowWandException() doesn't return.

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


static MagickBooleanType FindLightest (ProgOptT *po)
{
  if (!po->infile) return MagickFalse;

  MagickWandGenesis();

  MagickWand *magick_wand = NewMagickWand();  
  if (!MagickReadImage (magick_wand, po->infile))
    ThrowWandException(magick_wand);

  ssize_t radius = 0;

  // FIXME: we should make grayscale.

  if (!MagickTransformImageColorspace(magick_wand, GRAYColorspace))
    ThrowWandException(magick_wand);

  if (po->verbose) fprintf (stderr, "numToFind=%i\n", po->numToFind);

  if (po->sRadius) {
    ssize_t minDim = MagickGetImageWidth (magick_wand);
    if (minDim > MagickGetImageHeight (magick_wand)) {
      minDim = MagickGetImageHeight (magick_wand);
    }

    char * chLast;
    double val = InterpretLocaleValue (po->sRadius, &chLast);

    if (*chLast == '%') *chLast = 'c';

    if (*chLast == 'c') {
      radius = floor (val * minDim / 100.0 + 0.5);
    } else if (*chLast == 'p') {
      radius = floor (val * minDim + 0.5);
    } else {
      radius = floor (val + 0.5);
    }
    if (po->verbose) fprintf (stderr, "val=%g radius=%li\n", val, radius);
  }

  PixelIterator *iterator = NewPixelIterator (magick_wand);

  if (iterator == (PixelIterator *) NULL)
    ThrowWandException(magick_wand);

  PixelWand
    **pixels;

  PIX_INFO
    pixel;

  DrawingWand *d_wand = NewDrawingWand();
  PixelWand *c_wand = NewPixelWand();

  ssize_t
    y, x, yAtMax=0, xAtMax=0;

  size_t width;

  int nFound = 0;

  Quantum maxVal;

  do {

    maxVal = 0;

    for (y=0; y < (ssize_t) MagickGetImageHeight(magick_wand); y++)
    {
      pixels = PixelGetNextIteratorRow (iterator,&width);
      if (pixels == (PixelWand **) NULL) break;
      for (x=0; x < (ssize_t) width; x++)
      {
        PixelGetMagickColor(pixels[x],&pixel);
        if (maxVal < pixel.red) {
          maxVal = pixel.red;
          yAtMax = y;
          xAtMax = x;
        }
      }
    }
    if (y < (ssize_t) MagickGetImageHeight(magick_wand))
      ThrowWandException(magick_wand);

    if (maxVal != 0) {
      if (po->verbose) fprintf (stderr,
        "max val=%g @ %li,%li\n", (double) maxVal, xAtMax, yAtMax);

      printf ("%li,%li\n", xAtMax, yAtMax);

      PixelSetColor (c_wand, "black");
      DrawSetFillColor (d_wand, c_wand);
      DrawSetStrokeAntialias (d_wand, MagickFalse);

      if (radius < 1) {
        DrawPoint (d_wand, xAtMax, yAtMax);
      } else {
        DrawCircle (d_wand, xAtMax, yAtMax, xAtMax+radius, yAtMax);
      }
      MagickDrawImage (magick_wand, d_wand);
      ClearDrawingWand (d_wand);
      PixelResetIterator (iterator);
      nFound++;
    }
  } while (maxVal != 0 && (po->numToFind==0 || nFound < po->numToFind));

  if (po->verbose) fprintf (stderr, "Number found: %i\n", nFound);

  c_wand = DestroyPixelWand (c_wand);
  d_wand = DestroyDrawingWand (d_wand);

  if (po->outfile) {
    if (!MagickWriteImages (magick_wand, po->outfile, MagickTrue))
      ThrowWandException (magick_wand);
  }
  magick_wand = DestroyMagickWand (magick_wand);
  MagickWandTerminus ();

  return MagickTrue;
}


int main(int argc,char **argv)
{
  ProgOptT ProgOpt;

  GetOptions (argc, argv, &ProgOpt);

  FindLightest (&ProgOpt);

  return(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 http://www.imagemagick.org
Copyright: Copyright (C) 1999-2015 ImageMagick Studio LLC
License: http://www.imagemagick.org/script/license.php
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-09-30 http://www.imagemagick.org
Copyright: Copyright (C) 1999-2016 ImageMagick Studio LLC
License: http://www.imagemagick.org/script/license.php
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 07-May-2017 17:18:11.

Copyright © 2017 Alan Gibson.