about summary refs log tree commit diff
diff options
context:
space:
mode:
authorgiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2021-03-27 19:16:06 +0000
committergiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2021-03-27 19:16:06 +0000
commitfcfa49ef6735be96386bda87bab2d0976475f585 (patch)
treefccbaafc41fb0f98682d4d48f7ff584bdbb81904
parenta7fca291a0c78333da13e855bdb73aa819ba8649 (diff)
downloadnetpbm-mirror-fcfa49ef6735be96386bda87bab2d0976475f585.tar.gz
netpbm-mirror-fcfa49ef6735be96386bda87bab2d0976475f585.tar.xz
netpbm-mirror-fcfa49ef6735be96386bda87bab2d0976475f585.zip
Promote Development to Advanced, Release 10.94.00
git-svn-id: http://svn.code.sf.net/p/netpbm/code/advanced@4076 9d0c8265-081b-0410-96cb-a4ca84ce46f8
-rwxr-xr-xbuildtools/configure.pl242
-rw-r--r--common.mk4
-rw-r--r--converter/other/jpeg2000/libjasper_compat.h2
-rw-r--r--converter/other/jpeg2000/pamtojpeg2k.c18
-rw-r--r--converter/other/pamtowinicon.c4
-rw-r--r--converter/other/pgmtopbm.c10
-rw-r--r--converter/other/pngtopam.c24
-rw-r--r--converter/other/pnmtopng.c92
-rw-r--r--converter/other/pnmtops.c4
-rw-r--r--converter/pbm/pbmtox10bm15
-rw-r--r--converter/ppm/hpcdtoppm/Makefile2
-rw-r--r--doc/HISTORY54
-rw-r--r--editor/Makefile3
-rw-r--r--editor/pamaddnoise.c128
-rw-r--r--editor/pamditherbw.c43
-rw-r--r--editor/pamhomography.c677
-rw-r--r--editor/pammixmulti.c88
-rw-r--r--editor/pamrecolor.c50
-rw-r--r--editor/pamrubber.c144
-rw-r--r--editor/pbmreduce.c49
-rwxr-xr-xeditor/pnmflip10
-rwxr-xr-xeditor/pnmquant43
-rwxr-xr-xeditor/pnmquantall15
-rw-r--r--editor/pnmremap.c12
-rw-r--r--editor/specialty/pampaintspill.c32
-rw-r--r--editor/specialty/ppmshift.c124
-rw-r--r--editor/specialty/ppmspread.c206
-rw-r--r--generator/pamcrater.c60
-rw-r--r--generator/pamstereogram.c116
-rwxr-xr-xgenerator/pgmcrater12
-rw-r--r--generator/pgmnoise.c118
-rw-r--r--generator/ppmforge.c173
-rw-r--r--generator/ppmpat.c460
-rwxr-xr-xgenerator/ppmrainbow30
-rw-r--r--generator/ppmrough.c507
-rw-r--r--lib/Makefile4
-rw-r--r--lib/util/Makefile4
-rw-r--r--lib/util/rand.c261
-rw-r--r--lib/util/rand.h107
-rw-r--r--lib/util/randmersenne.c192
-rw-r--r--lib/util/randsysrand.c39
-rw-r--r--lib/util/randsysrandom.c40
-rw-r--r--other/pamexec.c77
-rwxr-xr-xtest/Execute-Tests11
-rw-r--r--test/Makefile21
-rw-r--r--test/all-in-place.ok1
-rwxr-xr-xtest/all-in-place.test1
-rwxr-xr-xtest/pamditherbw-random.ok6
-rwxr-xr-xtest/pamditherbw-random.test23
-rwxr-xr-xtest/pamexec.test5
-rwxr-xr-xtest/pamrecolor.ok11
-rwxr-xr-xtest/pamrecolor.test43
-rwxr-xr-xtest/pbmlife.ok63
-rwxr-xr-xtest/pbmlife.test13
-rw-r--r--test/pgmnoise.rand-ok3
-rwxr-xr-xtest/pgmnoise.test88
-rw-r--r--test/ppmforge-parameters.ok8
-rwxr-xr-xtest/ppmforge-parameters.test26
-rw-r--r--test/ppmforge.rand-ok3
-rw-r--r--test/ppmpat-random.rand-ok7
-rwxr-xr-xtest/ppmpat-random.test18
-rw-r--r--test/ppmrough.rand-ok3
-rwxr-xr-xtest/ppmshift.ok14
-rwxr-xr-xtest/ppmshift.test27
-rwxr-xr-xtest/ppmspread.ok6
-rwxr-xr-xtest/ppmspread.test12
-rw-r--r--test/random-generator.ok219
-rwxr-xr-xtest/random-generator.test68
-rw-r--r--test/winicon-roundtrip2.ok2
-rwxr-xr-xtest/winicon-roundtrip2.test10
-rw-r--r--version.mk4
71 files changed, 3754 insertions, 1257 deletions
diff --git a/buildtools/configure.pl b/buildtools/configure.pl
index 6a152a10..a2a1fd22 100755
--- a/buildtools/configure.pl
+++ b/buildtools/configure.pl
@@ -9,15 +9,15 @@ use Cwd 'abs_path';
 use Fcntl;
 use Config;
 #use File::Temp "tempfile";   Not available before Perl 5.6.1
-    
+
 
 my ($TRUE, $FALSE) = (1,0);
 
 # This program generates config.mk, which is included by all of the
-# Netpbm makefiles.  You run this program as the first step in building 
+# Netpbm makefiles.  You run this program as the first step in building
 # Netpbm.  (The second step is 'make').
 
-# This program is only a convenience.  It is supported to create 
+# This program is only a convenience.  It is supported to create
 # config.mk any way you want.  In fact, an easy way is to copy
 # config.mk.in and follow the instructions in the comments therein
 # to uncomment certain lines and make other changes.
@@ -28,7 +28,7 @@ my ($TRUE, $FALSE) = (1,0);
 # user, but it isn't the normal build process.
 
 # The argument to this program is the filepath of the config.mk.in
-# file.  If unspecified, the default is 'config.mk.in' in the 
+# file.  If unspecified, the default is 'config.mk.in' in the
 # Netpbm source directory.
 
 # For explanations of the stuff we put in the make files, see the comments
@@ -105,7 +105,7 @@ sub promptYesNo($) {
 
     while (!defined($retval)) {
         my $response = prompt("(y)es or (n)o", $default);
-        
+
         if (uc($response) =~ m{ ^ (Y|YES) $ }x)  {
             $retval = $TRUE;
         } elsif (uc($response) =~ m{ ^ (N|NO) $ }x)  {
@@ -131,11 +131,11 @@ sub flow($) {
     my $retval;
 
     my @words = split(m{\s+}, $unflowed);
-    
+
     my $currentLine;
-    
+
     $currentLine = "";
-    
+
     foreach my $word (@words) {
         my $mustSpill;
         if (length($currentLine) == 0) {
@@ -148,7 +148,7 @@ sub flow($) {
             } else {
                 $separator = " ";
             }
-                
+
             if (length($currentLine) + length($separator) + length($word)
                 <= 72) {
                 $currentLine .= $separator;
@@ -194,7 +194,7 @@ sub tmpdir() {
 # basic Perl some time after Perl 5.005_03.
 
     my $retval;
-    
+
     if ($ENV{'TMPDIR'}) {
         $retval = $ENV{'TMPDIR'};
     } elsif ($ENV{'TEMP'}) {
@@ -217,8 +217,8 @@ sub tempFile($) {
 
 # Here's what we'd do if we could expect Perl 5.6.1 or later, instead
 # of calling this subroutine:
-#    my ($cFile, $cFileName) = tempfile("netpbmXXXX", 
-#                                       SUFFIX=>".c", 
+#    my ($cFile, $cFileName) = tempfile("netpbmXXXX",
+#                                       SUFFIX=>".c",
 #                                       DIR=>File::Spec->tmpdir(),
 #                                       UNLINK=>0);
     my ($suffix) = @_;
@@ -275,7 +275,7 @@ sub commandExists($) {
 # from system() a lot different than if system() were to try to
 # execute the program directly.
 
-    return(system("$command 1</dev/null 1>/dev/null 2>/dev/null")/256 != 127); 
+    return(system("$command 1</dev/null 1>/dev/null 2>/dev/null")/256 != 127);
 }
 
 
@@ -304,20 +304,20 @@ sub testCflags() {
 
     my $cflags;
 
-    $cflags = "";  # initial value 
-    
+    $cflags = "";  # initial value
+
     if ($ENV{"CPPFLAGS"}) {
         $cflags = $ENV{"CPPFLAGS"};
     } else {
         $cflags = "";
     }
-    
+
     if ($ENV{"CFLAGS"}) {
         $cflags .= " " . $ENV{"CFLAGS"};
     }
-    
+
     return $cflags;
-}    
+}
 
 
 
@@ -325,21 +325,21 @@ sub testCompile($$$) {
     my ($cflags, $cSourceCodeR, $successR) = @_;
 #-----------------------------------------------------------------------------
 #  Do a test compile of the program in @{$cSourceCodeR}.
-#  
+#
 #  Return $$successR == $TRUE iff the compile succeeds (exit code 0).
 #-----------------------------------------------------------------------------
     my ($cFile, $cFileName) = tempFile(".c");
 
     print $cFile @{$cSourceCodeR};
-    
+
     my ($oFile, $oFileName) = tempFile(".o");
     # Note: we tried using /dev/null for the output file and got complaints
     # from the Sun compiler that it has the wrong suffix.  2002.08.09.
-    
+
     my $compileCommand = "$testCc -c -o $oFileName $cflags $cFileName";
     print ("Doing test compile: $compileCommand\n");
     my $rc = system($compileCommand);
-    
+
     unlink($oFileName);
     close($oFile);
     unlink($cFileName);
@@ -354,22 +354,22 @@ sub testCompileLink($$$) {
     my ($flags, $cSourceCodeR, $successR) = @_;
 #-----------------------------------------------------------------------------
 #  Do a test compile and link of the program in @{$cSourceCodeR}.
-#  
+#
 #  Return $$successR == $TRUE iff the compile succeeds (exit code 0).
 #-----------------------------------------------------------------------------
     my ($cFile, $cFileName) = tempFile('.c');
 
     print $cFile @{$cSourceCodeR};
-    
+
     my ($oFile, $oFileName) = tempFile('');
 
     # Note that $flags may contain -l options, which where static linking
     # is involved have to go _after_ the base object file ($oFileName).
-    
+
     my $compileCommand = "$testCc -o $oFileName $cFileName $flags";
     print ("Doing test compile/link: $compileCommand\n");
     my $rc = system($compileCommand);
-    
+
     unlink($oFileName);
     close($oFile);
     unlink($cFileName);
@@ -410,14 +410,14 @@ sub askAboutCygwin() {
 
     print("Are you building in/for the Cygwin environment?\n");
     print("\n");
-    
+
     my $default;
     if ($OSNAME eq "cygwin") {
         $default = "y";
     } else {
         $default = "n";
     }
-    
+
     my $retval = promptYesNo($default);
 
     return $retval;
@@ -429,14 +429,14 @@ sub askAboutDjgpp() {
 
     print("Are you building in/for the DJGPP environment?\n");
     print("\n");
-    
+
     my $default;
     if ($OSNAME eq "dos") {
         $default = "y";
     } else {
         $default = "n";
     }
-    
+
     my $retval = promptYesNo($default);
 }
 
@@ -446,7 +446,7 @@ sub askAboutMingw() {
 
     print("Are you building for the MinGW environment?\n");
     print("\n");
-    
+
     my $retval = promptYesNo("n");
 
     return $retval;
@@ -488,7 +488,7 @@ sub getPlatform() {
     computePlatformDefault(\$default);
 
     print("Which of the following best describes your platform?\n");
- 
+
     print("gnu      GNU/Linux\n");
     print("win      Windows/DOS (Cygwin, DJGPP, Mingw32)\n");
     print("sun      Solaris or SunOS\n");
@@ -658,7 +658,7 @@ sub getCompiler($$$) {
 #    make variable (e.g. make CC=gcc2).
 #
 #  - Configure needs to do test compiles.  The test is best if it uses
-#    the same compiler that the build eventually will use, but it's 
+#    the same compiler that the build eventually will use, but it's
 #    useful even if not.
 #
 # The value this subroutine returns is NOT the command name to invoke the
@@ -714,11 +714,11 @@ sub gccLinker() {
     # First, we assume that the compiler calls 'collect2' as the linker
     # front end.  The specs file might specify some other program, but
     # it usually says 'collect2'.
-    
+
     my $retval;
 
     my $collect2 = qx{gcc --print-prog-name=collect2};
-    
+
     if (defined($collect2)) {
         chomp($collect2);
         my $linker = qx{$collect2 -v 2>&1};
@@ -749,14 +749,14 @@ sub getLinker($$$$) {
             print("\n");
 
             my $default;
-            
+
             if ($compiler eq "gcc") {
                 $default = gccLinker();
             } else {
                 $default = "sun";
             }
             my $response = prompt("sun or gnu", $default);
-            
+
             if ($response eq "gnu") {
                 $$baseLinkerR = "GNU";
             } elsif ($response eq "sun") {
@@ -811,11 +811,11 @@ sub getLibTypes($$$$$$$$) {
 
     my $default = ($default_target eq "merge") ? "static" : "shared";
 
-    my ($netpbmlibtype, $netpbmlibsuffix, $shlibprefixlist, 
+    my ($netpbmlibtype, $netpbmlibsuffix, $shlibprefixlist,
         $willBuildShared, $staticlib_too);
-    
+
     my $response = prompt("static or shared", $default);
-    
+
     if ($response eq "shared") {
         $willBuildShared = $TRUE;
         if ($platform eq "WINDOWS") {
@@ -823,7 +823,7 @@ sub getLibTypes($$$$$$$$) {
             $netpbmlibsuffix = "dll";
             if ($subplatform eq "cygwin") {
                 $shlibprefixlist = "cyg lib";
-            } 
+            }
         } elsif ($platform eq "DARWIN") {
             $netpbmlibtype = "dylib";
             $netpbmlibsuffix = "dylib";
@@ -846,7 +846,7 @@ sub getLibTypes($$$$$$$$) {
         $netpbmlibtype = "unixstatic";
         $netpbmlibsuffix = "a";
         # targets, but needed for building
-        # libopt 
+        # libopt
     } else {
         print("'$response' isn't one of the choices.  \n" .
               "You must choose 'static' or 'shared'.\n");
@@ -857,18 +857,18 @@ sub getLibTypes($$$$$$$$) {
 
     # Note that we can't do both a static and shared library for AIX, because
     # they both have the same name: libnetpbm.a.
-    
-    if (($netpbmlibtype eq "unixshared" or 
-         $netpbmlibtype eq "irixshared" or 
+
+    if (($netpbmlibtype eq "unixshared" or
+         $netpbmlibtype eq "irixshared" or
          $netpbmlibtype eq "dll") and $netpbmlibsuffix ne "a") {
         print("Do you want to build static libraries too (for linking to \n");
         print("programs not in the Netpbm package?\n");
         print("\n");
-        
+
         my $default = "y";
-        
+
         my $response = prompt("(y)es or (n)o", $default);
-        
+
         if (uc($response) =~ /^(Y|YES)$/)  {
             $staticlib_too = "Y";
         } elsif (uc($response) =~ /^(N|NO)$/)  {
@@ -919,16 +919,16 @@ sub inttypesDefault() {
 
         my @candidateList = ("<inttypes.h>", "<sys/inttypes.h>",
                              "<types.h>", "<sys/types.h>");
-        
+
         for (my $i = 0; $i < @candidateList && !$works; ++$i) {
             my $candidate = $candidateList[$i];
             my @cSourceCode = (
                                "#include $candidate\n",
                                "int_fast32_t testvar;\n"
                                );
-            
+
             testCompile($cflags, \@cSourceCode, \my $success);
-            
+
             if ($success) {
                 $works = $candidate;
             }
@@ -961,7 +961,7 @@ sub getInttypes($) {
     print("\n");
 
     my $default = inttypesDefault();
-    
+
     while (!$gotit) {
         my $response = prompt("'#include' argument or NONE", $default);
 
@@ -1000,9 +1000,9 @@ sub getInt64($$) {
                            "#include $inttypes_h\n",
                            "int64_t testvar;\n"
                            );
-            
+
         testCompile($cflags, \@cSourceCode, \my $success);
-            
+
         if ($success) {
             print("You do.\n");
             $$haveInt64R = 'Y';
@@ -1034,9 +1034,9 @@ sub determineSseCapability($) {
         my @cSourceCode = (
                            "#include <emmintrin.h>\n",
                            );
-            
+
         testCompile($cflags, \@cSourceCode, \my $success);
-            
+
         if ($success) {
             print("It does.\n");
             $$haveEmmintrinR = $TRUE;
@@ -1115,9 +1115,9 @@ sub getTiffLibrary($@) {
         my $default = "libtiff" . libSuffix($platform);
 
         print("What is your TIFF (graphics format) library?\n");
-        
+
         my $response = prompt("library filename or 'none'", $default);
-        
+
         if ($response ne "none") {
             $tifflib = $response;
         }
@@ -1137,9 +1137,9 @@ sub getTiffLibrary($@) {
             $default = "default";
         }
         print("Where are the interface headers for it?\n");
-        
+
         my $response = prompt("TIFF header directory", $default);
-        
+
         if ($response ne "default") {
             $tiffhdr_dir = $response;
         }
@@ -1161,9 +1161,9 @@ sub getJpegLibrary($@) {
         my $default = "libjpeg" . libSuffix($platform);
 
         print("What is your JPEG (graphics format) library?\n");
-        
+
         my $response = prompt("library filename or 'none'", $default);
-        
+
         if ($response ne "none") {
             $jpeglib = $response;
         }
@@ -1178,9 +1178,9 @@ sub getJpegLibrary($@) {
             $default = "default";
         }
         print("Where are the interface headers for it?\n");
-        
+
         my $response = prompt("JPEG header directory", $default);
-        
+
         if ($response ne "default") {
             $jpeghdr_dir = $response;
         }
@@ -1218,9 +1218,9 @@ sub getPngLibrary($@) {
             my $default = "libpng" . libSuffix($platform);
 
             print("What is your PNG (graphics format) library?\n");
-            
+
             my $response = prompt("library filename or 'none'", $default);
-            
+
             if ($response ne "none") {
                 $pnglib = $response;
             }
@@ -1233,9 +1233,9 @@ sub getPngLibrary($@) {
             } else {
                 $default = "default";
             }
-            
+
             print("Where are the interface headers for it?\n");
-            
+
             my $response = prompt("PNG header directory", $default);
 
             if ($response ne "default") {
@@ -1258,9 +1258,9 @@ sub getZLibrary($@) {
         my $default = "libz" . libSuffix($platform);
 
         print("What is your Z (compression) library?\n");
-        
+
         my $response = prompt("library filename or 'none'", $default);
-        
+
         if ($response ne "none") {
             $zlib = $response;
         }
@@ -1273,11 +1273,11 @@ sub getZLibrary($@) {
         } else {
             $default = "default";
         }
-        
+
         print("Where are the interface headers for it?\n");
-        
+
         my $response = prompt("Z header directory", $default);
-        
+
         if ($response ne "default") {
             $zhdr_dir = $response;
         }
@@ -1315,9 +1315,9 @@ sub getX11Library($@) {
                 $default = "libX11" . libSuffix($platform);
             }
             print("What is your X11 (X client) library?\n");
-            
+
             my $response = prompt("library filename or 'none'", $default);
-            
+
             if ($response ne "none") {
                 $x11lib = $response;
             }
@@ -1328,9 +1328,9 @@ sub getX11Library($@) {
             $default = "default";
 
             print("Where are the interface headers for it?\n");
-            
+
             my $response = prompt("X11 header directory", $default);
-            
+
             if ($response ne "default") {
                 $x11hdr_dir = $response;
             }
@@ -1371,27 +1371,27 @@ sub getLinuxsvgaLibrary($@) {
         } else {
             $default = 'none';
         }
-            
+
         print("What is your Svgalib library?\n");
-        
+
         my $response = prompt("library filename or 'none'", $default);
-            
+
         if ($response ne 'none') {
             $svgalib = $response;
         }
     }
     if (defined($svgalib) && $svgalib ne 'none') {
         my $default;
-        
+
         if (-d('/usr/include/svgalib')) {
             $default = '/usr/include/svgalib';
         } else {
             $default = "default";
         }
         print("Where are the interface headers for it?\n");
-        
+
         my $response = prompt("Svgalib header directory", $default);
-        
+
         if ($response ne "default") {
             $svgalibhdr_dir = $response;
         }
@@ -1414,13 +1414,13 @@ sub symlink_command() {
     # simulation via a "ln" command, but have a "cp" command which works
     # in a pinch.  Some Windows environments have "ln", but it won't do
     # symbolic links.
-    
+
     if (commandExists("ln")) {
         # We assume if Perl can do symbolic links, so can Ln, and vice
         # versa.
 
         my $symlink_exists = eval { symlink("",""); 1 };
-        
+
         if ($symlink_exists) {
             $retval = "ln -s";
         } else {
@@ -1459,7 +1459,7 @@ sub gnuOptimizeOpt($) {
 #  that causes -O3 to generate incorrect code (symptom: pnmtojpeg --quality=95
 #  generates a syntax error message from shhopt).
 #-----------------------------------------------------------------------------
-# I don't know what are exactly the cases that Gcc is broken.  I know 
+# I don't know what are exactly the cases that Gcc is broken.  I know
 # Red Hat 7.1 and 7.2 and Mandrake 8.2, running gcc 2.96.1, commonly have
 # the problem.  But it may be limited to a certain subrelease level or
 # CPU type or other environment.  People who have reported the problem have
@@ -1472,7 +1472,7 @@ sub gnuOptimizeOpt($) {
     my @gccVerboseResp = `$gccCommandName --verbose 2>&1`;
 
     my $brokenCompiler;
-    
+
     if (@gccVerboseResp ==2) {
         if ($gccVerboseResp[1] =~ m{gcc version 2.96}) {
             $brokenCompiler = $TRUE;
@@ -1513,13 +1513,13 @@ sub wnostrictoverflowWorks($) {
     my ($cFile, $cFileName) = tempFile(".c");
 
     print $cFile "int x;";
-    
+
     my $compileCommand =
         "$gccCommandName -c -o /dev/null -Wno-strict-overflow $cFileName";
     print("Doing test compile to see if -Wno-strict-overflow works: "
           . "$compileCommand\n");
     my $rc = system($compileCommand);
-    
+
     unlink($cFileName);
     close($cFile);
 
@@ -1534,7 +1534,7 @@ sub gnuCflags($) {
     my $flags;
 
     $flags = gnuOptimizeOpt($gccCommandName) . " -ffast-math " .
-        " -pedantic -fno-common " . 
+        " -pedantic -fno-common " .
         "-Wall -Wno-uninitialized -Wmissing-declarations -Wimplicit " .
         "-Wwrite-strings -Wmissing-prototypes -Wundef " .
         "-Wno-unknown-pragmas ";
@@ -1577,7 +1577,7 @@ sub findProcessManagement($) {
     my @cSourceCode = (
                        "#include <sys/wait.h>\n",
                        );
-    
+
     testCompile($cflags, \@cSourceCode, \my $success);
 
     if (!$success) {
@@ -1637,7 +1637,7 @@ sub testCompileJpeglibH($$) {
                        "#include <stdio.h>\n",
                        "#include <jpeglib.h>\n",
                        );
-    
+
     testCompile($cflags, \@cSourceCode, $successR);
 }
 
@@ -1646,7 +1646,7 @@ sub testCompileJpeglibH($$) {
 sub testCompileJpegMarkerStruct($$) {
     my ($cflags, $successR) = @_;
 #-----------------------------------------------------------------------------
-#  Do a test compile to see if struct jpeg_marker_struct is defined in 
+#  Do a test compile to see if struct jpeg_marker_struct is defined in
 #  jpeglib.h.  Assume it is already established that the compiler works
 #  and can find jpeglib.h.
 #-----------------------------------------------------------------------------
@@ -1668,7 +1668,7 @@ sub printMissingHdrWarning($$) {
 
     warnUser("You said the compile-time part of the $name library " .
              "(the header files) is in " .
-             
+
              (defined($hdr_dir) ?
               "directory '$hdr_dir', " :
               "the compiler's default search path, ") .
@@ -1719,7 +1719,7 @@ sub testJpegHdr($) {
         } else {
             # We can get to something named jpeglib.h, but maybe it's an old
             # version we can't use.  Check it out.
-            testCompileJpegMarkerStruct("$generalCflags $jpegIOpt", 
+            testCompileJpegMarkerStruct("$generalCflags $jpegIOpt",
                                         \my $success);
             if (!$success) {
                 print("\n");
@@ -1789,7 +1789,7 @@ sub testCompileZlibH($$) {
     my @cSourceCode = (
                        "#include <zlib.h>\n",
                        );
-    
+
     testCompile($cflags, \@cSourceCode, $successR);
 }
 
@@ -1804,7 +1804,7 @@ sub testCompilePngH($$) {
     my @cSourceCode = (
                        "#include <png.h>\n",
                        );
-    
+
     testCompile($cflags, \@cSourceCode, $successR);
 }
 
@@ -1829,7 +1829,7 @@ sub testPngHdr($$) {
         } else {
             my $pngIOpt = $pnghdr_dir ? "-I$pnghdr_dir" : "";
 
-            testCompilePngH("$generalCflags $zlibIOpt $pngIOpt", 
+            testCompilePngH("$generalCflags $zlibIOpt $pngIOpt",
                             \my $success);
 
             if (!$success) {
@@ -1903,10 +1903,10 @@ sub testLinkPnglib($$) {
     } else {
         my $pngLdflags = qx{libpng-config --ldflags};
         chomp($pngLdflags);
-        
+
         testCompileLink("$generalCflags $pngCflags $pngLdflags",
                         \@cSourceCode, \my $success);
-        
+
         if (!$success) {
             testCompileLink("$generalCflags $pngCflags $pngLdflags -lz -lm",
                         \@cSourceCode, \my $lzLmSuccess);
@@ -1947,7 +1947,7 @@ sub testCompileXmlreaderH($$) {
     my @cSourceCode = (
                        "#include <libxml/xmlreader.h>\n",
                        );
-    
+
     testCompile($cflags, \@cSourceCode, $successR);
 }
 
@@ -1979,7 +1979,7 @@ sub testCompileXmlReaderTypes($$) {
                        "#include <libxml/xmlreader.h>\n",
                        "xmlReaderTypes dummy;\n",
                        );
-    
+
     testCompile($cflags, \@cSourceCode, $successR);
 }
 
@@ -2083,7 +2083,7 @@ if (@ARGV > 0) {
         } else {
             die("Unrecognized option: $ARGV[0]");
         }
-    } 
+    }
     $configInPathArg = $ARGV[0];
 }
 
@@ -2161,7 +2161,7 @@ print("\n");
 {
     my $default = "regular";
     my $response = prompt("regular or merge", $default);
-    
+
     if ($response eq "regular") {
         $default_target = "nonmerge";
     } elsif ($response eq "merge") {
@@ -2220,18 +2220,18 @@ my ($jpeglib, $jpeghdr_dir) = getJpegLibrary($platform);
 print("\n");
 my ($tifflib, $tiffhdr_dir) = getTiffLibrary($platform, $jpeghdr_dir);
 print("\n");
-my ($pnglib, $pnghdr_dir)   = getPngLibrary($platform, 
+my ($pnglib, $pnghdr_dir)   = getPngLibrary($platform,
                                             $tiffhdr_dir, $jpeghdr_dir);
 print("\n");
-my ($zlib, $zhdr_dir)       = getZLibrary($platform, 
+my ($zlib, $zhdr_dir)       = getZLibrary($platform,
                                           $pnghdr_dir,
                                           $tiffhdr_dir,
                                           $jpeghdr_dir);
 print("\n");
-my ($x11lib, $x11hdr_dir) = getX11Library($platform); 
+my ($x11lib, $x11hdr_dir) = getX11Library($platform);
 
 print("\n");
-my ($linuxsvgalib, $linuxsvgahdr_dir) = getLinuxsvgaLibrary($platform); 
+my ($linuxsvgalib, $linuxsvgahdr_dir) = getLinuxsvgaLibrary($platform);
 
 print("\n");
 
@@ -2274,7 +2274,7 @@ if (-f("GNUmakefile")) {
     while (!$done) {
         print("Where is the Netpbm source code?\n");
 
-        $srcdir = prompt("Netpbm source directory", 
+        $srcdir = prompt("Netpbm source directory",
                          abs_path(dirname($0) . "/.."));
 
         if (-f("$srcdir/GNUmakefile")) {
@@ -2283,7 +2283,7 @@ if (-f("GNUmakefile")) {
             print("That doesn't appear to contain Netpbm source code.\n");
             print("There is no file named 'GNUmakefile' in it.\n");
             print("\n");
-        }    
+        }
     }
     unlink("GNUmakefile");
     symlink("$srcdir/GNUmakefile", "GNUmakefile");
@@ -2293,7 +2293,7 @@ if (-f("GNUmakefile")) {
     open(SRCDIR, ">srcdir.mk");
     print(SRCDIR "SRCDIR = $srcdir\n");
     close(SRCDIR);
-    
+
     $defaultConfigInPath = "$srcdir/config.mk.in";
 }
 
@@ -2320,7 +2320,7 @@ open (CONFIG_IN,"<$configInPath") or
 
 @config_mk = <CONFIG_IN>;
 
-unshift(@config_mk, 
+unshift(@config_mk,
         "####This file was automatically created by 'configure.'\n",
         "####Many variables are set twice -- a generic setting, then \n",
         "####a system-specific override at the bottom of the file.\n",
@@ -2360,7 +2360,7 @@ if ($platform eq "GNU") {
         push(@config_mk, gnuCflags('cc'));
     }
 # The merged programs have a main_XXX subroutine instead of main(),
-# which would cause a warning with -Wmissing-declarations or 
+# which would cause a warning with -Wmissing-declarations or
 # -Wmissing-prototypes.
     push(@config_mk, "CFLAGS_MERGE = " .
          "-Wno-missing-declarations -Wno-missing-prototypes\n");
@@ -2376,7 +2376,7 @@ if ($platform eq "GNU") {
     } else {
         makeCompilerGcc(\@config_mk);
     }
-    # Before Netpbm 10.20 (January 2004), we set this to -R for 
+    # Before Netpbm 10.20 (January 2004), we set this to -R for
     # $compiler == cc and -rpath otherwise.  But now we know that the GNU
     # compiler can also invoke a linker that needs -R, so we're more flexible.
     if ($baseLinker eq "GNU") {
@@ -2416,7 +2416,7 @@ if ($platform eq "GNU") {
              "-L/usr/local/lib\n");
         push(@config_mk, "LDSHLIB = -shared -expect_unresolved \"*\"\n");
     } else {
-        # We've never tested this.  This is just here to give a user a 
+        # We've never tested this.  This is just here to give a user a
         # headstart on submitting to us the necessary information.  2002.07.04.
         push(@config_mk, "CC = gcc\n");
         push(@config_mk, 'CFLAGS = -O3', "\n");
@@ -2452,7 +2452,7 @@ if ($platform eq "GNU") {
 #    }
     push(@config_mk, 'SYMLINK = ', symlink_command(), "\n");
     push(@config_mk, 'DLLVER=$(NETPBM_MAJOR_RELEASE)', "\n");
-    push(@config_mk, "LDSHLIB = " . 
+    push(@config_mk, "LDSHLIB = " .
          '-shared -Wl,--image-base=0x10000000 -Wl,--enable-auto-import', "\n");
     if ($subplatform ne "cygwin") {
         push(@config_mk, "MSVCRT = Y\n");
@@ -2463,7 +2463,7 @@ if ($platform eq "GNU") {
 } elsif ($platform eq "BEOS") {
     push(@config_mk, "LDSHLIB = -nostart\n");
 } elsif ($platform eq "OPENBSD") {
-    # vedge@vedge.com.ar says on 2001.04.29 that there are a ton of 
+    # vedge@vedge.com.ar says on 2001.04.29 that there are a ton of
     # undefined symbols in the Fiasco stuff on OpenBSD.  So we'll just
     # cut it out of the build until someone feels like fixing it.
     push(@config_mk, "BUILD_FIASCO = N\n");
@@ -2482,7 +2482,7 @@ if ($platform eq "GNU") {
         push(@config_mk, "SHLIB_CLIB =\n");
     } else {
         makeCompilerGcc(\@config_mk);
-        push(@config_mk, "LDSHLIB = -shared\n"); 
+        push(@config_mk, "LDSHLIB = -shared\n");
     }
     push(@config_mk, "NETWORKLD = -lsocket -lresolve\n");
 } elsif ($platform eq "DARWIN") {
@@ -2520,7 +2520,7 @@ if (!$flex_result) {
         print("You do not appear to have the 'flex' or 'lex' pattern \n");
         print("matcher generator on your system, so we will not build \n");
         print("programs that need it (Thinkjettopbm)\n");
-        
+
         print("\n");
         print("Press ENTER to continue.\n");
         my $key = <STDIN>;
@@ -2534,7 +2534,7 @@ if (!$flex_result) {
         print("find 'flex' on your system.\n");
         print("\n");
 
-        push(@config_mk, "LEX = lex\n"); 
+        push(@config_mk, "LEX = lex\n");
     }
 }
 
@@ -2628,4 +2628,4 @@ if ($warned) {
 print("\n");
 
 
-exit 0;          
+exit 0;
diff --git a/common.mk b/common.mk
index 749488c2..91b476b9 100644
--- a/common.mk
+++ b/common.mk
@@ -150,7 +150,7 @@ IMPORTINC_LIB_HEADERS := \
 IMPORTINC_LIB_UTIL_HEADERS := \
   bitarith.h bitio.h bitreverse.h filename.h intcode.h floatcode.h io.h \
   matrix.h mallocvar.h \
-  nsleep.h nstring.h pm_c_util.h runlength.h shhopt.h token.h
+  nsleep.h nstring.h pm_c_util.h rand.h runlength.h shhopt.h token.h
 
 IMPORTINC_HEADERS := \
   $(IMPORTINC_ROOT_HEADERS) \
@@ -470,7 +470,7 @@ empty.c:
 # 2000.06.15
 
 # DJGPP can do SYMKINKs for programs but not for ordinary files, so
-# it define SYMLINKEXE, other system don't need it
+# it defines SYMLINKEXE, other system don't need it
 ifeq ($(SYMLINKEXE)x,x)
   SYMLINKEXE := $(SYMLINK)
 endif
diff --git a/converter/other/jpeg2000/libjasper_compat.h b/converter/other/jpeg2000/libjasper_compat.h
index 45dd904b..103b1d09 100644
--- a/converter/other/jpeg2000/libjasper_compat.h
+++ b/converter/other/jpeg2000/libjasper_compat.h
@@ -49,7 +49,7 @@ pmjas_image_decode(jas_stream_t * const in,
 
     if (jasperP) {
         *imagePP = jasperP;
-        *errorP  = errorP;
+        *errorP  = NULL;
     } else {
         pm_asprintf(errorP, "Failed.  Details may have been written to "
                     "Standard Error");
diff --git a/converter/other/jpeg2000/pamtojpeg2k.c b/converter/other/jpeg2000/pamtojpeg2k.c
index a886c390..ee5189ce 100644
--- a/converter/other/jpeg2000/pamtojpeg2k.c
+++ b/converter/other/jpeg2000/pamtojpeg2k.c
@@ -35,7 +35,7 @@ enum compmode {COMPMODE_INTEGER, COMPMODE_REAL};
 
 enum progression {PROG_LRCP, PROG_RLCP, PROG_RPCL, PROG_PCRL, PROG_CPRL};
 
-struct cmdlineInfo {
+struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
@@ -73,7 +73,7 @@ struct cmdlineInfo {
 
 static void
 parseCommandLine(int argc, char ** argv,
-                 struct cmdlineInfo * const cmdlineP) {
+                 struct CmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
    Note that many of the strings that this function returns in the
    *cmdline_p structure are actually in the supplied argv array.  And
@@ -372,9 +372,11 @@ convertToJasperImage(struct pam *   const inpamP,
 
 static void
 writeJpc(jas_image_t *      const jasperP,
-         struct cmdlineInfo const cmdline,
-         FILE *             const ofP) {
-
+         struct CmdlineInfo const cmdline,
+         int                const ofd) {
+/*----------------------------------------------------------------------------
+   Write the image *jasperP to open file 'ofd'.
+-----------------------------------------------------------------------------*/
     jas_stream_t * outStreamP;
     const char * options;
     const char * ilyrratesOpt;
@@ -459,7 +461,7 @@ writeJpc(jas_image_t *      const jasperP,
     pm_strfree(ilyrratesOpt);
 
     /* Open the output image file (Standard Output) */
-    outStreamP = jas_stream_fdopen(fileno(ofP), "w+b");
+    outStreamP = jas_stream_fdopen(ofd, "w+b");
     if (outStreamP == NULL)
         pm_error("Unable to open output stream.  jas_stream_fdopen() "
                  "failed");
@@ -500,7 +502,7 @@ writeJpc(jas_image_t *      const jasperP,
 int
 main(int argc, char **argv)
 {
-    struct cmdlineInfo cmdline;
+    struct CmdlineInfo cmdline;
     FILE * ifP;
     struct pam inpam;
     jas_image_t * jasperP;
@@ -526,7 +528,7 @@ main(int argc, char **argv)
 
     convertToJasperImage(&inpam, &jasperP);
 
-    writeJpc(jasperP, cmdline, stdout);
+    writeJpc(jasperP, cmdline, fileno(stdout));
 
 	jas_image_destroy(jasperP);
 
diff --git a/converter/other/pamtowinicon.c b/converter/other/pamtowinicon.c
index 7df8f60d..3c2c06bf 100644
--- a/converter/other/pamtowinicon.c
+++ b/converter/other/pamtowinicon.c
@@ -974,6 +974,10 @@ convertOneImage(unsigned int     const imageNum,
 
     doingPng = pam.width * pam.height >= pngThreshold;
 
+    if (verbose)
+        pm_message("Image %2u: encoding as %s",
+                   imageNum, doingPng ? "PNG" : "BMP");
+
     readAndScalePam(&pam, doingPng, tuples);
 
     determineImageType(&pam, tuples, &getPixel,
diff --git a/converter/other/pgmtopbm.c b/converter/other/pgmtopbm.c
index 64dc814b..d5f67a06 100644
--- a/converter/other/pgmtopbm.c
+++ b/converter/other/pgmtopbm.c
@@ -17,6 +17,7 @@
 #include "pgm.h"
 #include "dithers.h"
 #include "mallocvar.h"
+#include "rand.h"
 
 enum halftone {QT_FS, QT_THRESH, QT_DITHER8, QT_CLUSTER, QT_HILBERT};
 
@@ -462,14 +463,19 @@ createFsConverter(unsigned int const cols,
     /* Initialize Floyd-Steinberg error vectors. */
     MALLOCARRAY_NOFAIL(stateP->thiserr, cols + 2);
     MALLOCARRAY_NOFAIL(stateP->nexterr, cols + 2);
-    srand(randomSeedSpec ? randomSeed : pm_randseed());
 
     {
         /* (random errors in [-fs_scale/8 .. fs_scale/8]) */
         unsigned int col;
+        struct pm_randSt randSt;
+        pm_randinit(&randSt);
+        pm_srand2(&randSt, randomSeedSpec, randomSeed);
+
         for (col = 0; col < cols + 2; ++col)
             stateP->thiserr[col] =
-                (long)(rand() % fs_scale - half_fs_scale) / 4;
+                (long)(pm_rand(&randSt) % fs_scale - half_fs_scale) / 4;
+
+        pm_randterm(&randSt);
     }
 
     stateP->fs_forward = TRUE;
diff --git a/converter/other/pngtopam.c b/converter/other/pngtopam.c
index 9098154a..3c0f81a5 100644
--- a/converter/other/pngtopam.c
+++ b/converter/other/pngtopam.c
@@ -668,10 +668,16 @@ dumpPngInfo(struct pngx * const pngxP) {
                    background.blue);
     }
 
-    if (pngx_chunkIsPresent(pngxP, PNG_INFO_tRNS))
-        pm_message("tRNS chunk (transparency): %u entries",
-                   pngx_trns(pngxP).numTrans);
-    else
+    if (pngx_chunkIsPresent(pngxP, PNG_INFO_tRNS)) {
+        struct pngx_trns const trns = pngx_trns(pngxP);
+
+        pm_message("tRNS chunk (transparency):");
+        pm_message("  %u palette entries", trns.numTrans);
+        pm_message("  transparent color = (%u,%u,%u)",
+                   trns.transColor.red,
+                   trns.transColor.green,
+                   trns.transColor.blue);
+    } else
         pm_message("tRNS chunk (transparency): not present");
 
     if (pngx_chunkIsPresent(pngxP, PNG_INFO_gAMA))
@@ -868,17 +874,17 @@ paletteHasPartialTransparency(struct pngx * const pngxP) {
         if (pngx_chunkIsPresent(pngxP, PNG_INFO_tRNS)) {
             struct pngx_trns const trans = pngx_trns(pngxP);
 
-            bool foundGray;
+            bool foundPartial;
             unsigned int i;
 
-            for (i = 0, foundGray = FALSE;
-                 i < trans.numTrans && !foundGray;
+            for (i = 0, foundPartial = FALSE;
+                 i < trans.numTrans && !foundPartial;
                  ++i) {
                 if (trans.trans[i] != 0 && trans.trans[i] != pngxP->maxval) {
-                    foundGray = TRUE;
+                    foundPartial = TRUE;
                 }
             }
-            retval = foundGray;
+            retval = foundPartial;
         } else
             retval = FALSE;
     } else
diff --git a/converter/other/pnmtopng.c b/converter/other/pnmtopng.c
index 8ad21a6a..23bd7d66 100644
--- a/converter/other/pnmtopng.c
+++ b/converter/other/pnmtopng.c
@@ -56,6 +56,7 @@
 #endif                               /*  2 for warnings (1 == error) */
 
 #include <assert.h>
+#include <stdbool.h>
 #include <string.h> /* strcat() */
 #include <limits.h>
 #include <png.h>
@@ -140,12 +141,6 @@ typedef struct _jmpbuf_wrapper {
   jmp_buf jmpbuf;
 } jmpbuf_wrapper;
 
-#ifndef TRUE
-#  define TRUE 1
-#endif
-#ifndef FALSE
-#  define FALSE 0
-#endif
 #ifndef NONE
 #  define NONE 0
 #endif
@@ -394,8 +389,8 @@ parseCommandLine(int argc, const char ** argv,
 
 
     opt.opt_table = option_def;
-    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
-    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+    opt.short_allowed = false;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = false;  /* We have no parms that are negative numbers */
 
     pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
@@ -511,6 +506,8 @@ parseCommandLine(int argc, const char ** argv,
         cmdlineP->inputFileName = argv[1];
     else
         pm_error("Program takes at most one argument:  input file name");
+
+    free(option_def);
 }
 
 
@@ -671,7 +668,7 @@ lookupColorAlpha(coloralphahash_table const caht,
 
 
 /* The following variables belong to getChv() and freeChv() */
-static bool getChv_computed = FALSE;
+static bool getChv_computed = false;
 static colorhist_vector getChv_chv;
 
 
@@ -722,7 +719,7 @@ getChv(FILE *             const ifP,
             else
                 pm_message("Too many colors (more than %u) found", maxColors);
         }
-        getChv_computed = TRUE;
+        getChv_computed = true;
     }
     *chvP = getChv_chv;
     *colorsP = getChv_colors;
@@ -736,7 +733,7 @@ static void freeChv(void) {
         if (getChv_chv)
             ppm_freecolorhist(getChv_chv);
 
-    getChv_computed = FALSE;
+    getChv_computed = false;
 }
 
 
@@ -750,7 +747,7 @@ pgmBitsAreRepeated(unsigned int const repeatedSize,
                    xelval       const maxval,
                    int          const format) {
 /*----------------------------------------------------------------------------
-   Return TRUE iff all the samples in the image in file 'ifP',
+   Return true iff all the samples in the image in file 'ifP',
    described by 'cols', 'rows', 'maxval', and 'format', consist in the
    rightmost 'repeatedSize' * 2 bits of two identical sets of
    'repeatedSize' bits.
@@ -774,7 +771,7 @@ pgmBitsAreRepeated(unsigned int const repeatedSize,
 
     pm_seek2(ifP, &rasterPos, sizeof(rasterPos));
 
-    mayscale = TRUE;  /* initial assumption */
+    mayscale = true;  /* initial assumption */
 
     for (row = 0; row < rows && mayscale; ++row) {
         unsigned int col;
@@ -785,7 +782,7 @@ pgmBitsAreRepeated(unsigned int const repeatedSize,
             xelval const testbits1 = testbits2 & mask1;
                 /* The lower half of the bits of interest in the sample */
             if (((testbits1 << repeatedSize) | testbits1) != testbits2)
-                mayscale = FALSE;
+                mayscale = false;
         }
     }
     pnm_freerow(xelrow);
@@ -878,7 +875,7 @@ meaningful_bits_ppm(FILE *         const ifp,
     maxMeaningfulBits = pm_maxvaltobits(maxval);
 
     if (maxval == 65535) {
-        mayscale = TRUE;   /* initial assumption */
+        mayscale = true;   /* initial assumption */
         pm_seek2(ifp, &rasterPos, sizeof(rasterPos));
         for (row = 0; row < rows && mayscale; ++row) {
             unsigned int col;
@@ -888,7 +885,7 @@ meaningful_bits_ppm(FILE *         const ifp,
                 if ((PPM_GETR(p) & 0xff) * 0x101 != PPM_GETR(p) ||
                     (PPM_GETG(p) & 0xff) * 0x101 != PPM_GETG(p) ||
                     (PPM_GETB(p) & 0xff) * 0x101 != PPM_GETB(p))
-                    mayscale = FALSE;
+                    mayscale = false;
             }
         }
         if (mayscale)
@@ -902,7 +899,7 @@ meaningful_bits_ppm(FILE *         const ifp,
 
 
 static void
-tryTransparentColor(FILE *     const ifp,
+tryTransparentColor(FILE *     const ifP,
                     pm_filepos const rasterPos,
                     int        const cols,
                     int        const rows,
@@ -912,7 +909,14 @@ tryTransparentColor(FILE *     const ifp,
                     gray       const alphaMaxval,
                     pixel      const transcolor,
                     bool *     const singleColorIsTransP) {
+/*----------------------------------------------------------------------------
+   Find out if the transparent pixels identified by alpha mask 'alphaMask'
+   (whose maxval is 'alphaMaxval') are exactly the pixels of color
+   'transcolor'.  Return answer as *singleColorIsTransP.
 
+   The image we analyze is that on input stream *ifP, starting at position
+   'rasterPos', and we leave that stream positioned arbitrarily.
+-----------------------------------------------------------------------------*/
     int const pnmType = PNM_FORMAT_TYPE(format);
 
     xel * xelrow;
@@ -922,13 +926,13 @@ tryTransparentColor(FILE *     const ifp,
 
     xelrow = pnm_allocrow(cols);
 
-    pm_seek2(ifp, &rasterPos, sizeof(rasterPos));
+    pm_seek2(ifP, &rasterPos, sizeof(rasterPos));
 
-    singleColorIsTrans = TRUE;  /* initial assumption */
+    singleColorIsTrans = true;  /* initial assumption */
 
     for (row = 0; row < rows && singleColorIsTrans; ++row) {
         int col;
-        pnm_readpnmrow(ifp, xelrow, cols, maxval, format);
+        pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
         for (col = 0 ; col < cols && singleColorIsTrans; ++col) {
             if (alphaMask[row][col] == 0) { /* transparent */
                 /* If we have a second transparent color, we're
@@ -936,16 +940,16 @@ tryTransparentColor(FILE *     const ifp,
                 */
                 if (pnmType == PPM_TYPE) {
                     if (!PPM_EQUAL(xelrow[col], transcolor))
-                        singleColorIsTrans = FALSE;
+                        singleColorIsTrans = false;
                 } else {
                     if (PNM_GET1(xelrow[col]) != PNM_GET1(transcolor))
-                        singleColorIsTrans = FALSE;
+                        singleColorIsTrans = false;
                 }
             } else if (alphaMask[row][col] != alphaMaxval) {
                 /* Here's an area of the mask that is translucent.  That
                    disqualified us.
                 */
-                singleColorIsTrans = FALSE;
+                singleColorIsTrans = false;
             } else {
                 /* Here's an area of the mask that is opaque.  If it's
                    the same color as our candidate transparent color,
@@ -953,10 +957,10 @@ tryTransparentColor(FILE *     const ifp,
                 */
                 if (pnmType == PPM_TYPE) {
                     if (PPM_EQUAL(xelrow[col], transcolor))
-                        singleColorIsTrans = FALSE;
+                        singleColorIsTrans = false;
                 } else {
                     if (PNM_GET1(xelrow[col]) == PNM_GET1(transcolor))
-                        singleColorIsTrans = FALSE;
+                        singleColorIsTrans = false;
                 }
             }
         }
@@ -990,8 +994,8 @@ analyzeAlpha(FILE *       const ifP,
   of a certain color fully transparent and every other pixel opaque,
   we can simply identify that color in the PNG.
 
-  We have to do this before any scaling occurs, since alpha is only
-  possible with 8 and 16-bit.
+  We have to do this before any scaling occurs, since alpha is possible
+  only with 8 and 16-bit.
 -----------------------------------------------------------------------------*/
     xel * xelrow;
     bool foundTransparentPixel;
@@ -1034,7 +1038,7 @@ analyzeAlpha(FILE *       const ifP,
             pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
             for (col = 0; col < cols && !foundTransparentPixel; ++col) {
                 if (alphaMask[row][col] == 0) {
-                    foundTransparentPixel = TRUE;
+                    foundTransparentPixel = true;
                     transcolor = pnm_xeltopixel(xelrow[col], format);
                 }
             }
@@ -1115,16 +1119,16 @@ determineTransparency(struct cmdlineInfo const cmdline,
         if (alphaCanBeTransparencyIndex && !cmdline.force) {
             if (verbose)
                 pm_message("converting alpha mask to transparency index");
-            *alphaP       = FALSE;
+            *alphaP       = false;
             *transparentP = 2;
             *transColorP  = alphaTranscolor;
         } else if (allOpaque) {
             if (verbose)
                 pm_message("Skipping alpha because mask is all opaque");
-            *alphaP       = FALSE;
+            *alphaP       = false;
             *transparentP = -1;
         } else {
-            *alphaP       = TRUE;
+            *alphaP       = true;
             *transparentP = -1;
         }
         *alphaMaxvalP = alphaMaxval;
@@ -1134,17 +1138,17 @@ determineTransparency(struct cmdlineInfo const cmdline,
            use with trans[], which can have stuff in it if the user specified
            a transparent color.
         */
-        *alphaP       = FALSE;
+        *alphaP       = false;
         *alphaMaxvalP = 255;
 
         if (cmdline.transparent) {
             const char * transstring2;
             /* The -transparent value, but with possible leading '=' removed */
             if (cmdline.transparent[0] == '=') {
-                *transExactP = TRUE;
+                *transExactP = true;
                 transstring2 = &cmdline.transparent[1];
             } else {
-                *transExactP = FALSE;
+                *transExactP = false;
                 transstring2 = cmdline.transparent;
             }
             /* We do this funny PPM_DEPTH thing instead of just passing 'maxval'
@@ -1206,7 +1210,7 @@ hasColor(FILE *       const ifP,
             for (col = 0; col < cols && isGray; ++col) {
                     xel const p = xelrow[col];
                 if (PPM_GETR(p) != PPM_GETG(p) || PPM_GETG(p) != PPM_GETB(p))
-                    isGray = FALSE;
+                    isGray = false;
             }
         }
 
@@ -1335,10 +1339,10 @@ compute_nonalpha_palette(colorhist_vector const chv,
             int j;
             bool found;
 
-            found = FALSE;
+            found = false;
             for (j = 0; j < ordered_palette_size && !found; ++j) {
                 if (PNM_EQUAL(ordered_palette[j], chv[colorIndex].color))
-                    found = TRUE;
+                    found = true;
             }
             if (!found)
                 pm_error("failed to find color (%d, %d, %d), which is in the "
@@ -1636,7 +1640,7 @@ compute_alpha_palette(FILE *         const ifP,
    MAXPALETTEENTRIES elements.
 
    If there are more than MAXPALETTEENTRIES color/alpha pairs in the image,
-   don't return any palette information -- just return *tooBigP == TRUE.
+   don't return any palette information -- just return *tooBigP == true.
 -----------------------------------------------------------------------------*/
     colorhist_vector chv;
     unsigned int colors;
@@ -1714,9 +1718,9 @@ makeOneColorTransparentInPalette(xel            const transColor,
    can do a better job when the opaque entries are all last in the
    color/alpha palette).
 
-   If the specified color is not there and exact == TRUE, return
+   If the specified color is not there and exact == true, return
    without changing anything, but issue a warning message.  If it's
-   not there and exact == FALSE, just find the closest color.
+   not there and exact == false, just find the closest color.
 
    We assume every entry in the palette is opaque upon entry.
 
@@ -2369,7 +2373,8 @@ writeRaster(struct pngx *        const pngxP,
     /* max: 3 color channels, one alpha channel, 16-bit */
     MALLOCARRAY(line, cols * 8);
     if (line == NULL)
-        pm_error("out of memory allocating PNG row buffer");
+        pm_error("out of memory allocating PNG row buffer for %u columns",
+                 cols);
 
     for (pass = 0; pass < pngxP->numPassesRequired; ++pass) {
         unsigned int row;
@@ -2386,6 +2391,7 @@ writeRaster(struct pngx *        const pngxP,
             pngx_writeRow(pngxP, line);
         }
     }
+    free(line);
     pnm_freerow(xelrow);
 }
 
@@ -2837,9 +2843,9 @@ convertpnm(struct cmdlineInfo const cmdline,
         if (verbose)
             pm_message("Not using color map.  %s", noColormapReason);
         pm_strfree(noColormapReason);
-        colorMapped = FALSE;
+        colorMapped = false;
     } else
-        colorMapped = TRUE;
+        colorMapped = true;
 
     computeColorMapLookupTable(colorMapped, palettePnm, paletteSize,
                                transPnm, transSize, alpha, alphaMaxval,
diff --git a/converter/other/pnmtops.c b/converter/other/pnmtops.c
index 8c9167fb..3ca158b5 100644
--- a/converter/other/pnmtops.c
+++ b/converter/other/pnmtops.c
@@ -9,7 +9,7 @@
 
          We use methods we learned from Dirk Krause's program Bmeps.
          Previous versions used raster encoding code based on Bmeps
-         code.  This program does not used any code from Bmeps.
+         code.  This program does not use any code from Bmeps.
 
       2) Use our own filters and redefine /readstring .  This is aboriginal
          Netpbm code, from when Postscript was young.  The filters are
@@ -1268,7 +1268,7 @@ putFilters(unsigned int const postscriptLevel,
     assert(postscriptLevel > 1);
 
     /* We say to decode flate, then rle, so Caller must ensure it encodes
-       rel, then flate.
+       rle, then flate.
     */
 
     if (ascii85)
diff --git a/converter/pbm/pbmtox10bm b/converter/pbm/pbmtox10bm
index deb3aeab..ca82fcd2 100644
--- a/converter/pbm/pbmtox10bm
+++ b/converter/pbm/pbmtox10bm
@@ -37,6 +37,16 @@ exec perl -w -x -S -- "$0" "$@"
 use strict;
 use File::Basename;
 use Cwd 'abs_path';
+use IO::Handle;
+
+
+
+sub pm_message($) {
+    STDERR->print("pbmtox10bm: $_[0]\n");
+}
+
+
+
 
 sub doVersionHack($) {
     my ($argvR) = @_;
@@ -56,13 +66,12 @@ my $infile;
 foreach (@ARGV) {
     if (/^-/) {
         # It's an option.  But Pbmtox10bm didn't have any options.
-        print(STDERR "Invalid option '$_'\n");
+        pm_message("Invalid option '$_'");
         exit(10);
     } else {
         # It's a parameter
         if (defined($infile)) {
-            print(STDERR
-                  "You may specify at most one non-option parameter.\n");
+            pm_message("You may specify at most one non-option parameter.");
             exit(10);
         } else {
             $infile = $_;
diff --git a/converter/ppm/hpcdtoppm/Makefile b/converter/ppm/hpcdtoppm/Makefile
index 59ba3630..5777a84f 100644
--- a/converter/ppm/hpcdtoppm/Makefile
+++ b/converter/ppm/hpcdtoppm/Makefile
@@ -19,7 +19,7 @@ install.bin install.merge: install.bin.local
 install.bin.local: $(PKGDIR)/bin
 # In June 2002, pcdovtoppm replaced pcdindex
 	cd $(PKGDIR)/bin ; \
-	$(SYMLINK) pcdovtoppm$(EXE) pcdindex$(EXE) 
+	$(SYMLINK) pcdovtoppm pcdindex
 
 
 FORCE:
diff --git a/doc/HISTORY b/doc/HISTORY
index 922320de..e150a3e2 100644
--- a/doc/HISTORY
+++ b/doc/HISTORY
@@ -4,29 +4,40 @@ Netpbm.
 CHANGE HISTORY 
 --------------
 
-21.03.22 BJH  Release 10.93.03
+21.03.27 BJH  Release 10.94.00
 
-              pnmtops: Fix incorrect output (arithmetic overflow) when
-              bounding box is exactly INT_MAX high or wide.  Always broken.
-              Pnmtops was in primordial Netpbm.
+              Add pamhomography:  Thanks Scott Pakin.
+
+              pamstereogram: Add -yfillshift .
+
+              pamtowinicon: Add BMP/PNG encoding to verbose output.
 
-21.03.10 BJH  Release 10.93.02
+              Use internal random number generator everywhere random numbers
+              are used except ppmtoilbm, so seeded results are the same on
+              all platforms.
+
+              pamexec: Issue message instead of being killed by a signal when
+              the exec'ed program does not read the whole image"
+
+              ppmforge: Fail if -dimension is greater than 5, which is
+              useless.
+
+              pamscale: Fix bogus "bad magic number" or similar failure most
+              of the time with -nomix.  Broken since Netpbm 10.49 (December
+              2009).
 
-              pnmtopng: fix incorrect transparency in output when requesting
+              pnmtopng: Fix incorrect transparency in output when requesting
               transparency.  Introduced after Netpbm 10.35 (August 2006) but
               not after Netpbm 10.47 (June 2009).
 
-              pnmtopng: fix buffer overrun or bogus "too many color/
+              pnmtopng: Fix buffer overrun or bogus "too many color/
               transparency pairs" failure when requesting transparency.
               Introduced after Netpbm 10.26 (January 2005) but not after
               Netpbm 10.35 (August 2006).
 
-21.03.07 BJH  Release 10.93.01
+              pamtojpeg2k: Fix constant failure with message about file
+              close failing.
 
-              pamscale: fix bogus "bad magic number" or similar failure most
-              of the time with -nomix.  Broken since Netpbm 10.49 (December
-              2009).
-              
               libnetpbm: pm_system: Fix bug: standard input feeder process
               repositions unrelated files.  Always broken (pm_system was new
               in Netpbm 10.13 (September 2003).
@@ -35,6 +46,15 @@ CHANGE HISTORY
               (result of pm_system bug above).  Always broken (Pamtowinicon
               was new in Netpbm 10.63 (June 2013).
 
+              pnmtopng: Fix trivial memory leaks.
+
+              pnmtops: Fix incorrect output (arithmetic overflow) when
+              bounding box is exactly INT_MAX high or wide.  Always broken.
+              Pnmtops was in primordial Netpbm.
+
+              make package: fix no such file pcdovtoppm.exe failure on
+              Windows.
+
 20.12.28 BJH  Release 10.93.00
 
               pamarith: Add -equal.
@@ -43,18 +63,18 @@ CHANGE HISTORY
               it makes sense (all but -subtract, -difference, -compare,
               -divide, -shiftleft, and -shiftright).
 
-              pamrith: fail if operand images have different depth and not
+              pamarith: fail if operand images have different depth and not
               depth 1.
 
               ppmshift: Add -seed .
 
               pamaddnoise: Fix incorrect output for -type poisson.  Always
-              broken.  (pamaddnoise was new to Netpbm in Netpbm 10.29 (August
-              2005)).
+              broken.  (pamaddnoise's precursor pnmaddnoies was new to Netpbm
+              in Netpbm 10.29 (August 2005)).
 
               pamaddnoise: fix bug: garbage output with -type impulse.  Always
-              broken (pamaddnoise's precursors pnmaddnoise was new to Netpbm
-              in Netpbm 10.29 (August 2005).
+              broken (pamaddnoise's precursor pnmaddnoise was new to Netpbm
+              in Netpbm 10.29 (August 2005)).
 
 20.09.26 BJH  Release 10.92.00
 
diff --git a/editor/Makefile b/editor/Makefile
index 88409dad..395deaf4 100644
--- a/editor/Makefile
+++ b/editor/Makefile
@@ -19,7 +19,8 @@ SUBDIRS = pamflip specialty
 PORTBINARIES = pamaddnoise pamaltsat pambackground pambrighten pamcomp pamcut \
 	       pamdice pamditherbw pamedge \
 	       pamenlarge \
-	       pamfunc pamhue pamlevels pammasksharpen pammixmulti \
+	       pamfunc pamhomography pamhue pamlevels \
+	       pammasksharpen pammixmulti \
 	       pamperspective pamrecolor pamrubber \
 	       pamscale pamsistoaglyph pamstretch pamthreshold pamundice \
 	       pamwipeout \
diff --git a/editor/pamaddnoise.c b/editor/pamaddnoise.c
index 20c68d99..9ca80394 100644
--- a/editor/pamaddnoise.c
+++ b/editor/pamaddnoise.c
@@ -33,18 +33,20 @@
 
 #include "pm_c_util.h"
 #include "mallocvar.h"
+#include "rand.h"
 #include "shhopt.h"
 #include "pm_gamma.h"
 #include "pam.h"
 
 static double const EPSILON = 1.0e-5;
+static double const SALT_RATIO = 0.5;
 
 
 
 static double
-rand1() {
+rand1(struct pm_randSt * const randStP) {
 
-    return (double)rand()/RAND_MAX;
+    return (double)pm_rand(randStP)/RAND_MAX;
 }
 
 
@@ -67,6 +69,7 @@ struct CmdlineInfo {
 
     enum NoiseType noiseType;
 
+    unsigned int seedSpec;
     unsigned int seed;
 
     float lambda;
@@ -119,7 +122,7 @@ parseCommandLine(int argc, const char ** const argv,
 
     unsigned int option_def_index;
 
-    unsigned int typeSpec, seedSpec, lambdaSpec, lsigmaSpec, mgsigmaSpec,
+    unsigned int typeSpec, lambdaSpec, lsigmaSpec, mgsigmaSpec,
         sigma1Spec, sigma2Spec, toleranceSpec;
 
     const char * type;
@@ -128,21 +131,21 @@ parseCommandLine(int argc, const char ** const argv,
 
     option_def_index = 0;   /* incremented by OPTENT3 */
     OPTENT3(0,   "type",            OPT_STRING,   &type,
-            &typeSpec,         0);
+            &typeSpec,           0);
     OPTENT3(0,   "seed",            OPT_UINT,     &cmdlineP->seed,
-            &seedSpec,         0);
+            &cmdlineP->seedSpec, 0);
     OPTENT3(0,   "lambda",          OPT_FLOAT,    &cmdlineP->lambda,
-            &lambdaSpec,       0);
+            &lambdaSpec,         0);
     OPTENT3(0,   "lsigma",          OPT_FLOAT,    &cmdlineP->lsigma,
-            &lsigmaSpec,       0);
+            &lsigmaSpec,         0);
     OPTENT3(0,   "mgsigma",         OPT_FLOAT,    &cmdlineP->mgsigma,
-            &mgsigmaSpec,      0);
+            &mgsigmaSpec,        0);
     OPTENT3(0,   "sigma1",          OPT_FLOAT,    &cmdlineP->sigma1,
-            &sigma1Spec,       0);
+            &sigma1Spec,         0);
     OPTENT3(0,   "sigma2",          OPT_FLOAT,    &cmdlineP->sigma2,
-            &sigma2Spec,       0);
+            &sigma2Spec,         0);
     OPTENT3(0,   "tolerance",       OPT_FLOAT,    &cmdlineP->tolerance,
-            &toleranceSpec,    0);
+            &toleranceSpec,      0);
 
     opt.opt_table = option_def;
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
@@ -193,7 +196,7 @@ parseCommandLine(int argc, const char ** const argv,
     if (!toleranceSpec)
         cmdlineP->tolerance = 0.10;
 
-    if (!seedSpec)
+    if (!cmdlineP->seedSpec)
         cmdlineP->seed = pm_randseed();
 
     if (argc-1 > 1)
@@ -211,11 +214,12 @@ parseCommandLine(int argc, const char ** const argv,
 
 
 static void
-addGaussianNoise(sample   const maxval,
-                 sample   const origSample,
-                 sample * const newSampleP,
-                 float    const sigma1,
-                 float    const sigma2) {
+addGaussianNoise(sample             const maxval,
+                 sample             const origSample,
+                 sample *           const newSampleP,
+                 float              const sigma1,
+                 float              const sigma2,
+                 struct pm_randSt * const randStP) {
 /*----------------------------------------------------------------------------
    Add Gaussian noise.
 
@@ -225,11 +229,11 @@ addGaussianNoise(sample   const maxval,
     double x1, x2, xn, yn;
     double rawNewSample;
 
-    x1 = rand1();
+    x1 = rand1(randStP);
 
     if (x1 == 0.0)
         x1 = 1.0;
-    x2 = rand1();
+    x2 = rand1(randStP);
     xn = sqrt(-2.0 * log(x1)) * cos(2.0 * M_PI * x2);
     yn = sqrt(-2.0 * log(x1)) * sin(2.0 * M_PI * x2);
 
@@ -242,40 +246,42 @@ addGaussianNoise(sample   const maxval,
 
 
 static void
-addImpulseNoise(sample   const maxval,
-                sample   const origSample,
-                sample * const newSampleP,
-                float    const tolerance) {
+addImpulseNoise(sample             const maxval,
+                sample             const origSample,
+                sample *           const newSampleP,
+                float              const tolerance,
+                double             const saltRatio,
+                struct pm_randSt * const randStP) {
 /*----------------------------------------------------------------------------
    Add impulse (salt and pepper) noise
 -----------------------------------------------------------------------------*/
 
-    double const low_tol  = tolerance / 2.0;
-    double const high_tol = 1.0 - (tolerance / 2.0);
-    double const sap = rand1();
+    double const pepperRatio = 1.0 - saltRatio;
+    double const loTolerance = tolerance * pepperRatio;
+    double const hiTolerance = 1.0 - tolerance * saltRatio;
+    double const sap         = rand1(randStP);
 
-    if (sap < low_tol)
-        *newSampleP = 0;
-    else if ( sap >= high_tol )
-        *newSampleP = maxval;
-    else
-        *newSampleP = origSample;
+    *newSampleP =
+        sap < loTolerance ? 0 :
+        sap >= hiTolerance? maxval :
+        origSample;
 }
 
 
 
 static void
-addLaplacianNoise(sample   const maxval,
-                  double   const infinity,
-                  sample   const origSample,
-                  sample * const newSampleP,
-                  float    const lsigma) {
+addLaplacianNoise(sample             const maxval,
+                  double             const infinity,
+                  sample             const origSample,
+                  sample *           const newSampleP,
+                  float              const lsigma,
+                  struct pm_randSt * const randStP) {
 /*----------------------------------------------------------------------------
    Add Laplacian noise
 
    From Pitas' book.
 -----------------------------------------------------------------------------*/
-    double const u = rand1();
+    double const u = rand1(randStP);
 
     double rawNewSample;
 
@@ -297,11 +303,12 @@ addLaplacianNoise(sample   const maxval,
 
 
 static void
-addMultiplicativeGaussianNoise(sample   const maxval,
-                               double   const infinity,
-                               sample   const origSample,
-                               sample * const newSampleP,
-                               float    const mgsigma) {
+addMultiplicativeGaussianNoise(sample             const maxval,
+                               double             const infinity,
+                               sample             const origSample,
+                               sample *           const newSampleP,
+                               float              const mgsigma,
+                               struct pm_randSt * const randStP) {
 /*----------------------------------------------------------------------------
    Add multiplicative Gaussian noise
 
@@ -311,14 +318,14 @@ addMultiplicativeGaussianNoise(sample   const maxval,
     double rawNewSample;
 
     {
-        double const uniform = rand1();
+        double const uniform = rand1(randStP);
         if (uniform <= EPSILON)
             rayleigh = infinity;
         else
             rayleigh = sqrt(-2.0 * log( uniform));
     }
     {
-        double const uniform = rand1();
+        double const uniform = rand1(randStP);
         gauss = rayleigh * cos(2.0 * M_PI * uniform);
     }
     rawNewSample = origSample + (origSample * mgsigma * gauss);
@@ -363,10 +370,11 @@ poissonPmf(double       const lambda,
 
 
 static void
-addPoissonNoise(struct pam * const pamP,
-                sample       const origSample,
-                sample *     const newSampleP,
-                float        const lambdaOfMaxval) {
+addPoissonNoise(struct pam *       const pamP,
+                sample             const origSample,
+                sample *           const newSampleP,
+                float              const lambdaOfMaxval,
+                struct pm_randSt * const randStP) {
 /*----------------------------------------------------------------------------
    Add Poisson noise
 -----------------------------------------------------------------------------*/
@@ -376,7 +384,7 @@ addPoissonNoise(struct pam * const pamP,
 
     double const lambda  = origSampleIntensity * lambdaOfMaxval;
 
-    double const u = rand1();
+    double const u = rand1(randStP);
 
     /* We now apply the inverse CDF (cumulative distribution function) of the
        Poisson distribution to uniform random variable 'u' to get a Poisson
@@ -416,12 +424,14 @@ main(int argc, const char ** argv) {
     const tuple * newtuplerow;
     unsigned int row;
     double infinity;
+    struct pm_randSt randSt;
 
     pm_proginit(&argc, argv);
 
     parseCommandLine(argc, argv, &cmdline);
 
-    srand(cmdline.seed);
+    pm_randinit(&randSt);
+    pm_srand2(&randSt, cmdline.seedSpec, cmdline.seed);
 
     ifP = pm_openr(cmdline.inputFileName);
 
@@ -448,35 +458,40 @@ main(int argc, const char ** argv) {
                     addGaussianNoise(inpam.maxval,
                                      tuplerow[col][plane],
                                      &newtuplerow[col][plane],
-                                     cmdline.sigma1, cmdline.sigma2);
+                                     cmdline.sigma1, cmdline.sigma2,
+                                     &randSt);
                     break;
 
                 case NOISETYPE_IMPULSE:
                     addImpulseNoise(inpam.maxval,
                                     tuplerow[col][plane],
                                     &newtuplerow[col][plane],
-                                    cmdline.tolerance);
+                                    cmdline.tolerance, SALT_RATIO,
+                                    &randSt);
                    break;
 
                 case NOISETYPE_LAPLACIAN:
                     addLaplacianNoise(inpam.maxval, infinity,
                                       tuplerow[col][plane],
                                       &newtuplerow[col][plane],
-                                      cmdline.lsigma);
+                                      cmdline.lsigma,
+                                      &randSt);
                     break;
 
                 case NOISETYPE_MULTIPLICATIVE_GAUSSIAN:
                     addMultiplicativeGaussianNoise(inpam.maxval, infinity,
                                                    tuplerow[col][plane],
                                                    &newtuplerow[col][plane],
-                                                   cmdline.mgsigma);
+                                                   cmdline.mgsigma,
+                                                   &randSt);
                     break;
 
                 case NOISETYPE_POISSON:
                     addPoissonNoise(&inpam,
                                     tuplerow[col][plane],
                                     &newtuplerow[col][plane],
-                                    cmdline.lambda);
+                                    cmdline.lambda,
+                                    &randSt);
                     break;
 
                 }
@@ -484,6 +499,7 @@ main(int argc, const char ** argv) {
         }
         pnm_writepamrow(&outpam, newtuplerow);
     }
+    pm_randterm(&randSt);
     pnm_freepamrow(newtuplerow);
     pnm_freepamrow(tuplerow);
 
diff --git a/editor/pamditherbw.c b/editor/pamditherbw.c
index ae91a26f..694b2c21 100644
--- a/editor/pamditherbw.c
+++ b/editor/pamditherbw.c
@@ -14,12 +14,14 @@
 #include <string.h>
 
 #include "pm_c_util.h"
-#include "pam.h"
-#include "dithers.h"
+#include "rand.h"
 #include "mallocvar.h"
 #include "shhopt.h"
+#include "pam.h"
+#include "dithers.h"
 #include "pm_gamma.h"
 
+
 enum halftone {QT_FS,
                QT_ATKINSON,
                QT_THRESH,
@@ -588,7 +590,9 @@ fsDestroy(struct converter * const converterP) {
 
 static struct converter
 createFsConverter(struct pam * const graypamP,
-                  float        const threshFraction) {
+                  float        const threshFraction,
+                  bool         const randomseedSpec,
+                  unsigned int const randomseed) {
 
     struct fsState * stateP;
     struct converter converter;
@@ -605,9 +609,17 @@ createFsConverter(struct pam * const graypamP,
 
     {
         /* (random errors in [-1/8 .. 1/8]) */
+
         unsigned int col;
+        struct pm_randSt randSt;
+
+        pm_randinit(&randSt);
+        pm_srand2(&randSt, randomseedSpec, randomseed);
+
         for (col = 0; col < graypamP->width + 2; ++col)
-            stateP->thiserr[col] = ((float)rand()/RAND_MAX - 0.5) / 4;
+            stateP->thiserr[col] = (pm_drand(&randSt) - 0.5) / 4;
+
+        pm_randterm(&randSt);
     }
 
     stateP->halfWhite = threshFraction;
@@ -725,7 +737,9 @@ atkinsonDestroy(struct converter * const converterP) {
 
 static struct converter
 createAtkinsonConverter(struct pam * const graypamP,
-                        float        const threshFraction) {
+                        float        const threshFraction,
+                        bool         const randomseedSpec,
+                        unsigned int const randomseed) {
 
     struct atkinsonState * stateP;
     struct converter converter;
@@ -743,11 +757,18 @@ createAtkinsonConverter(struct pam * const graypamP,
     {
         /* (random errors in [-1/8 .. 1/8]) */
         unsigned int col;
+        struct pm_randSt randSt;
+
+        pm_randinit(&randSt);
+        pm_srand2(&randSt, randomseedSpec, randomseed);
+
         for (col = 0; col < graypamP->width + 2; ++col) {
-            stateP->error[0][col] = ((float)rand()/RAND_MAX - 0.5) / 4;
+            stateP->error[0][col] = (pm_drand(&randSt) - 0.5) / 4;
             stateP->error[1][col] = 0.0;
             stateP->error[2][col] = 0.0;
         }
+
+        pm_randterm(&randSt);
     }
 
     stateP->halfWhite = threshFraction;
@@ -934,8 +955,6 @@ main(int argc, char *argv[]) {
 
     parseCommandLine(argc, argv, &cmdline);
 
-    srand(cmdline.randomseedSpec ? cmdline.randomseed : pm_randseed());
-
     ifP = pm_openr(cmdline.inputFilespec);
 
     if (cmdline.halftone == QT_HILBERT)
@@ -956,10 +975,14 @@ main(int argc, char *argv[]) {
 
         switch (cmdline.halftone) {
         case QT_FS:
-            converter = createFsConverter(&graypam, cmdline.threshval);
+            converter = createFsConverter(&graypam, cmdline.threshval,
+                                          cmdline.randomseedSpec,
+                                          cmdline.randomseed);
             break;
         case QT_ATKINSON:
-            converter = createAtkinsonConverter(&graypam, cmdline.threshval);
+            converter = createAtkinsonConverter(&graypam, cmdline.threshval,
+                                                cmdline.randomseedSpec,
+                                                cmdline.randomseed);
             break;
         case QT_THRESH:
             converter = createThreshConverter(&graypam, cmdline.threshval);
diff --git a/editor/pamhomography.c b/editor/pamhomography.c
new file mode 100644
index 00000000..59b59ed7
--- /dev/null
+++ b/editor/pamhomography.c
@@ -0,0 +1,677 @@
+/* ----------------------------------------------------------------------
+ *
+ * Map one quadrilateral to another
+ * by Scott Pakin <scott+pbm@pakin.org>
+ *
+ * ----------------------------------------------------------------------
+ *
+ * Copyright (C) 2020 Scott Pakin <scott+pbm@pakin.org>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * ----------------------------------------------------------------------
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <math.h>
+#include "pm_c_util.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "pam.h"
+
+#define MIN4(A, B, C, D) MIN(MIN(A, B), MIN(C, D))
+#define MAX4(A, B, C, D) MAX(MAX(A, B), MAX(C, D))
+
+/* A point on the image plane.  It may or may not lie within the
+   bounds of the image itself. */
+typedef struct Point {
+    int x;
+    int y;
+} Point;
+
+/* A quadrilateral on the image plane */
+typedef struct Quad {
+    Point ul;
+    Point ur;
+    Point lr;
+    Point ll;
+} Quad;
+
+/* A user-specified mapping from one quadrilateral to another */
+typedef struct QuadMap {
+    Quad from;
+    Quad to;
+} QuadMap;
+
+struct CmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFilespec;  /* "-" if stdin */
+    QuadMap      qmap;           /* Source and target quadrilaterals */
+    Quad         bbox;           /* Bounding box for the target image */
+    const char * fillColor;      /* Fill color for unused coordinates */
+};
+
+
+
+static unsigned int
+parseCoords(const char * const str,
+            int *        const coords) {
+/*----------------------------------------------------------------------------
+  Parse a list of up to 16 integers.  The function returns the number
+  of integers encountered.
+-----------------------------------------------------------------------------*/
+
+    const char * p;
+    char * pnext;
+    unsigned int i;
+
+    for (i = 0, p = str; i < 16; ++i, p = pnext) {
+        long int val;
+
+        /* Skip punctuation, except "+" and "-", and white space. */
+        while (*p != '\0' && *p != '+' && *p != '-' &&
+               (isspace(*p) || ispunct(*p)))
+            ++p;
+
+        /* Parse the next integer. */
+        errno = 0;  /* strtol() sets errno on error. */
+        val = strtol(p, &pnext, 10);
+        if (errno == ERANGE)
+            return i;  /* Integer lies out of long int range */
+        if (errno != 0 || pnext == p)
+            return i;  /* Too few integers */
+        coords[i] = (int)val;
+        if ((long int)coords[i] != val)
+            return i;  /* Integer lies out of int range */
+    }
+    return i;
+}
+
+
+
+static void
+parseViewString(const char * const str,
+                Quad *       const quad) {
+/*----------------------------------------------------------------------------
+  Parse a list of four integers in the order {ulx, uly, lrx, lry} into a
+  quadrilateral.  The function aborts on error.
+-----------------------------------------------------------------------------*/
+
+    int coords[16];
+
+    if (parseCoords(str, coords) != 4)
+        pm_error("failed to parse \"%s\" as a list of four integers", str);
+    quad->ul.x = quad->ll.x = coords[0];
+    quad->ul.y = quad->ur.y = coords[1];
+    quad->lr.x = quad->ur.x = coords[2];
+    quad->lr.y = quad->ll.y = coords[3];
+}
+
+
+
+static void
+parseQuadString(const char * const str,
+                Quad *       const quad) {
+/*----------------------------------------------------------------------------
+  Parse a list of eight integers in the order {ulx, uly, urx, ury,
+  lrx, lry, llx, lly} into a quadrilateral.  The function aborts on
+  error.
+-----------------------------------------------------------------------------*/
+
+    int coords[16];
+
+    if (parseCoords(str, coords) != 8)
+        pm_error("failed to parse \"%s\" as a list of eight integers", str);
+    quad->ul.x = coords[0];
+    quad->ul.y = coords[1];
+    quad->ur.x = coords[2];
+    quad->ur.y = coords[3];
+    quad->lr.x = coords[4];
+    quad->lr.y = coords[5];
+    quad->ll.x = coords[6];
+    quad->ll.y = coords[7];
+}
+
+
+
+static void
+readMapFile(const char * const fname,
+            QuadMap *    const qmap) {
+/*----------------------------------------------------------------------------
+  Read from a file either 16 numbers in the order {ulx1, uly1, urx1, ury1,
+  lrx1, lry1, llx1, lly1, ulx2, uly2, urx2, ury2, lrx2, lry2, llx2, lly2}
+  or 8 numbers in the order {ulx2, uly2, urx2, ury2, lrx2, lry2, llx2,
+  lly2}.  This function aborts on error.
+-----------------------------------------------------------------------------*/
+
+    FILE * fp;
+    char * str;      /* Entire file contents */
+    int coords[16];  /* File as a list of up to 16 coordinates */
+    char * c;
+    long int nread;
+
+    /* Read the entire file. */
+    fp = pm_openr(fname);
+    str = pm_read_unknown_size(fp, &nread);
+    REALLOCARRAY_NOFAIL(str, nread + 1);
+    str[nread] = '\0';
+    pm_close(fp);
+
+    /* Replace newlines and tabs with spaces to prettify error reporting. */
+    for (c = str; *c != '\0'; ++c)
+        if (isspace(*c))
+            *c = ' ';
+
+    /* Read either {from, to} or just a {to} quadrilateral. */
+    switch (parseCoords(str, coords)) {
+    case 16:
+        /* 16 integers: assign both the "from" and the "to" quadrilateral. */
+        qmap->from.ul.x = coords[0];
+        qmap->from.ul.y = coords[1];
+        qmap->from.ur.x = coords[2];
+        qmap->from.ur.y = coords[3];
+        qmap->from.lr.x = coords[4];
+        qmap->from.lr.y = coords[5];
+        qmap->from.ll.x = coords[6];
+        qmap->from.ll.y = coords[7];
+        qmap->to.ul.x = coords[8];
+        qmap->to.ul.y = coords[9];
+        qmap->to.ur.x = coords[10];
+        qmap->to.ur.y = coords[11];
+        qmap->to.lr.x = coords[12];
+        qmap->to.lr.y = coords[13];
+        qmap->to.ll.x = coords[14];
+        qmap->to.ll.y = coords[15];
+        break;
+    case 8:
+        /* 8 integers: assign only the "to" quadrilateral. */
+        memset((void *)&qmap->from, 0, sizeof(Quad));
+        qmap->to.ul.x = coords[0];
+        qmap->to.ul.y = coords[1];
+        qmap->to.ur.x = coords[2];
+        qmap->to.ur.y = coords[3];
+        qmap->to.lr.x = coords[4];
+        qmap->to.lr.y = coords[5];
+        qmap->to.ll.x = coords[6];
+        qmap->to.ll.y = coords[7];
+        break;
+    default:
+        /* Any other number of integers: issue an error message. */
+        pm_error("failed to parse \"%s\" as a list of either 8 or 16 integers",
+                 str);
+        break;
+    }
+
+    free(str);
+}
+
+
+
+static void
+parseCommandLine(int                  argc,
+                 const char **        const argv,
+                 struct CmdlineInfo * const cmdlineP ) {
+/*----------------------------------------------------------------------------
+   Parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+-----------------------------------------------------------------------------*/
+
+    optEntry *option_def;
+        /* Instructions to pm_optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int mapFileSpec = 0, fromSpec = 0, toSpec = 0;
+    unsigned int viewSpec = 0, fillColorSpec = 0;
+    char *mapFile, *from, *to, *view;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "mapfile",         OPT_STRING, &mapFile,
+            &mapFileSpec,             0);
+    OPTENT3(0, "from",            OPT_STRING, &from,
+            &fromSpec,                0);
+    OPTENT3(0, "to",              OPT_STRING, &to,
+            &toSpec,                  0);
+    OPTENT3(0, "view",            OPT_STRING, &view,
+            &viewSpec,                0);
+    OPTENT3(0, "fill",            OPT_STRING, &cmdlineP->fillColor,
+            &fillColorSpec,           0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and local variables. */
+
+    if (!fillColorSpec)
+        cmdlineP->fillColor = NULL;
+
+    memset((void *)&cmdlineP->qmap, 0, sizeof(QuadMap));
+    if (mapFileSpec)
+        readMapFile(mapFile, &cmdlineP->qmap);
+    if (fromSpec)
+        parseQuadString(from, &cmdlineP->qmap.from);
+    if (toSpec)
+        parseQuadString(to, &cmdlineP->qmap.to);
+    if (!mapFileSpec && !fromSpec && !toSpec && !viewSpec)
+        pm_error("You must specify at least one of "
+                 "-mapfile, -qin, -qout, and -view");
+    if (viewSpec)
+        parseViewString(view, &cmdlineP->bbox);
+    else
+        memset((void *)&cmdlineP->bbox, 0, sizeof(Quad));
+
+    if (argc < 2)
+        cmdlineP->inputFilespec = "-";
+    else if (argc == 2)
+        cmdlineP->inputFilespec = argv[1];
+    else
+        pm_error("Too many non-option arguments: %u.  "
+                 "Only argument is input file name", argc - 1);
+
+    free((void *) option_def);
+}
+
+
+
+static tuple
+parseFillColor(const struct pam *         const pamP,
+               const struct CmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+  Parse the fill color into the correct format for the given PAM metadata.
+-----------------------------------------------------------------------------*/
+
+    tuple rgb;
+    tuple fillColor;
+
+    if (!cmdlineP->fillColor) {
+        pnm_createBlackTuple(pamP, &fillColor);
+        return fillColor;
+    }
+
+    rgb = pnm_parsecolor(cmdlineP->fillColor, pamP->maxval);
+    fillColor = pnm_allocpamtuple(pamP);
+    switch (pamP->depth) {
+    case 1:
+        /* Grayscale */
+        fillColor[0] = (rgb[PAM_RED_PLANE]*299 +
+                        rgb[PAM_GRN_PLANE]*587 +
+                        rgb[PAM_BLU_PLANE]*114)/1000;
+        break;
+    case 2:
+        /* Grayscale + alpha */
+        fillColor[0] = (rgb[PAM_RED_PLANE]*299 +
+                        rgb[PAM_GRN_PLANE]*587 +
+                        rgb[PAM_BLU_PLANE]*114)/1000;
+        fillColor[PAM_GRAY_TRN_PLANE] = pamP->maxval;
+        break;
+    case 3:
+        /* RGB */
+        pnm_assigntuple(pamP, fillColor, rgb);
+        break;
+    case 4:
+        /* RGB + alpha */
+        pnm_assigntuple(pamP, fillColor, rgb);
+        fillColor[PAM_TRN_PLANE] = pamP->maxval;
+        break;
+    default:
+        pm_error("unexpected image depth %d", pamP->depth);
+        break;
+    }
+
+    return fillColor;
+}
+
+
+
+static tuple **
+initOutputImage(const struct pam *         const pamP,
+                const struct CmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+  Allocate and initialize the output image.
+-----------------------------------------------------------------------------*/
+
+    tuple fillColor;    /* Fill color to use for unused coordinates */
+    tuple ** outImg;    /* Output image */
+    unsigned int row;
+
+    outImg = pnm_allocpamarray(pamP);
+
+    fillColor = parseFillColor(pamP, cmdlineP);
+    for (row = 0; row < pamP->height; ++row) {
+        unsigned int col;
+
+        for (col = 0; col < pamP->width; ++col) {
+            pnm_assigntuple(pamP, outImg[row][col], fillColor);
+        }
+    }
+
+    free((void *) fillColor);
+    return outImg;
+}
+
+
+
+static void
+computeSteps(const Quad * const qfrom,
+             const Quad * const qto,
+             double *     const ustep,
+             double *     const vstep) {
+/*----------------------------------------------------------------------------
+  Compute increments for u and v as these range from 0.0 to 1.0.
+-----------------------------------------------------------------------------*/
+
+    double fx0, fx1, fxd;
+    double tx0, tx1, txd;
+    double fy0, fy1, fyd;
+    double ty0, ty1, tyd;
+
+    /* Compute ustep as the inverse of the maximum possible x delta across
+       either the "from" or "to" quadrilateral. */
+    fx0 = MIN4((double)qfrom->ur.x,
+               (double)qfrom->ul.x,
+               (double)qfrom->lr.x,
+               (double)qfrom->ll.x);
+    fx1 = MAX4((double)qfrom->ur.x,
+               (double)qfrom->ul.x,
+               (double)qfrom->lr.x,
+               (double)qfrom->ll.x);
+    fxd = fx1 - fx0;
+    tx0 = MIN4((double)qto->ur.x,
+               (double)qto->ul.x,
+               (double)qto->lr.x,
+               (double)qto->ll.x);
+    tx1 = MAX4((double)qto->ur.x,
+               (double)qto->ul.x,
+               (double)qto->lr.x,
+               (double)qto->ll.x);
+    txd = tx1 - tx0;
+    if (fxd == 0.0 && txd == 0.0)
+        *ustep = 1.0;  /* Arbitrary nonzero step */
+    *ustep = 0.5/MAX(fxd, txd);
+        /* Divide into 0.5 instead of 1.0 for additional smoothing. */
+
+    /* Compute vstep as the inverse of the maximum possible y delta across
+       either the "from" or "to" quadrilateral
+  . */
+    fy0 = MIN4((double)qfrom->ur.y,
+               (double)qfrom->ul.y,
+               (double)qfrom->lr.y,
+               (double)qfrom->ll.y);
+    fy1 = MAX4((double)qfrom->ur.y,
+               (double)qfrom->ul.y,
+               (double)qfrom->lr.y,
+               (double)qfrom->ll.y);
+    fyd = fy1 - fy0;
+    ty0 = MIN4((double)qto->ur.y,
+               (double)qto->ul.y,
+               (double)qto->lr.y,
+               (double)qto->ll.y);
+    ty1 = MAX4((double)qto->ur.y,
+               (double)qto->ul.y,
+               (double)qto->lr.y,
+               (double)qto->ll.y);
+    tyd = ty1 - ty0;
+    if (fyd == 0.0 && tyd == 0.0)
+        *vstep = 1.0;  /* Arbitrary nonzero step */
+    *vstep = 0.5/MAX(fyd, tyd);
+        /* Divide into 0.5 instead of 1.0 for additional smoothing. */
+}
+
+
+
+static Quad *
+prepareQuadrilateral(const struct pam * const pamP,
+                     const Quad *       const qdata) {
+/*----------------------------------------------------------------------------
+  If a quadrilateral has all zero points, replace it with a quadrilateral
+  of the full size of the image.  The caller should free the result.
+-----------------------------------------------------------------------------*/
+
+    Quad * qcopy;
+
+    MALLOCVAR_NOFAIL(qcopy);
+
+    if (qdata->ul.x == 0 && qdata->ul.y == 0 &&
+        qdata->ur.x == 0 && qdata->ur.y == 0 &&
+        qdata->ll.x == 0 && qdata->ll.y == 0 &&
+        qdata->lr.x == 0 && qdata->lr.y == 0) {
+        /* Set the quadrilateral to the image's bounding box. */
+        memset((void *)qcopy, 0, sizeof(Quad));
+        qcopy->ur.x = pamP->width - 1;
+        qcopy->lr.x = pamP->width - 1;
+        qcopy->lr.y = pamP->height - 1;
+        qcopy->ll.y = pamP->height - 1;
+    } else {
+        /* Use the quadrilateral as specified. */
+        memcpy(qcopy, qdata, sizeof(Quad));
+    }
+
+    return qcopy;
+}
+
+
+static void
+coordsAtPercent(const Quad * const quad,
+                double       const u,
+                double       const v,
+                int *        const x,
+                int *        const y) {
+/*----------------------------------------------------------------------------
+  Return the (x, y) coordinates that lie at (u%, v%) from the upper left to
+  the lower right of a given quadrilateral.
+-----------------------------------------------------------------------------*/
+
+    *x = (int) nearbyint((1.0 - u)*(1.0 - v)*quad->ul.x +
+                         u*(1.0 - v)*quad->ur.x +
+                         u*v*quad->lr.x +
+                         (1.0 - u)*v*quad->ll.x);
+    *y = (int) nearbyint((1.0 - u)*(1.0 - v)*quad->ul.y +
+                         u*(1.0 - v)*quad->ur.y +
+                         u*v*quad->lr.y +
+                         (1.0 - u)*v*quad->ll.y);
+}
+
+
+
+static void
+computeBoundingBox(const Quad * const q,
+                   Quad *       const bbox) {
+/*----------------------------------------------------------------------------
+  Compute the bounding box of a given quadrilateral.
+-----------------------------------------------------------------------------*/
+
+    bbox->ul.x = bbox->ll.x = MIN4(q->ul.x, q->ur.x, q->lr.x, q->ll.x);
+    bbox->ul.y = bbox->ur.y = MIN4(q->ul.y, q->ur.y, q->lr.y, q->ll.y);
+    bbox->ur.x = bbox->lr.x = MAX4(q->ul.x, q->ur.x, q->lr.x, q->ll.x);
+    bbox->ll.y = bbox->lr.y = MAX4(q->ul.y, q->ur.y, q->lr.y, q->ll.y);
+}
+
+
+
+static void
+mapQuadrilaterals(const struct pam * const inPamP,
+                  const struct pam * const outPamP,
+                  const Quad *       const qfrom,
+                  const Quad *       const qto,
+                  tuple **           const inImg,
+                  tuple **           const outImg,
+                  int                const xofs,
+                  int                const yofs) {
+/*----------------------------------------------------------------------------
+  Map the quadrilateral in the source image to the quadrilateral in the
+  target image.  This is the function that implemens pamhomography's
+  primary functionality.
+-----------------------------------------------------------------------------*/
+
+    sample ** channel;
+        /* Aggregated values for a single channel */
+    unsigned long ** tally;
+        /* Number of values at each coordinate in the above */
+    double ustep, vstep;
+        /* Steps to use when iterating from 0.0 to 1.0 */
+    double u, v;
+    unsigned int plane, row, col;
+
+    MALLOCARRAY2_NOFAIL(channel, outPamP->height, outPamP->width);
+    MALLOCARRAY2_NOFAIL(tally, outPamP->height, outPamP->width);
+
+    computeSteps(qfrom, qto, &ustep, &vstep);
+
+    for (plane = 0; plane < outPamP->depth; ++plane) {
+        /* Reset the channel colors and tally for each plane, */
+        for (row = 0; row < outPamP->height; ++row)
+            for (col = 0; col < outPamP->width; ++col) {
+                channel[row][col] = 0;
+                tally[row][col] = 0;
+            }
+
+        /* Iterate from 0% to 100% in the y dimension. */
+        for (v = 0.0; v <= 1.0; v += vstep) {
+            /* Iterate from 0% to 100% in the x dimension. */
+            for (u = 0.0; u <= 1.0; u += ustep) {
+                int x0, y0;  /* "From" coordinate */
+                int x1, y1;  /* "To" coordinate */
+
+                /* Map (u%, v%) of one quadrilateral to (u%, v%) of the
+                   other quadrilateral. */
+                coordsAtPercent(qfrom, u, v, &x0, &y0);
+                coordsAtPercent(qto, u, v, &x1, &y1);
+
+                /* Copy the source image's (x0, y0) to the destination
+                   image's (x1, y1) in the current plane. */
+                x1 += xofs;
+                y1 += yofs;
+                if (x0 >= 0 && y0 >= 0 &&
+                    x0 < inPamP->width && y0 < inPamP->height &&
+                    x1 >= 0 && y1 >= 0 &&
+                    x1 < outPamP->width && y1 < outPamP->height) {
+                    channel[y1][x1] += inImg[y0][x0][plane];
+                    tally[y1][x1]++;
+                }
+            }
+        }
+
+        /* Assign the current plane in the output image the average color
+           at each point. */
+        for (row = 0; row < outPamP->height; ++row)
+            for (col = 0; col < outPamP->width; ++col)
+                if (tally[row][col] != 0)
+                    outImg[row][col][plane] =
+                        (channel[row][col] + tally[row][col]/2) /
+                        tally[row][col];
+    }
+
+    pm_freearray2((void ** const)tally);
+    pm_freearray2((void ** const)channel);
+    free((void *)qto);
+    free((void *)qfrom);
+}
+
+
+
+static void
+processFile(FILE *                     const ifP,
+            const struct CmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+  Read the input image, create the output image, and map a quadrilateral in
+  the former to a quadrilateral in the latter.
+-----------------------------------------------------------------------------*/
+
+    struct pam inPam;    /* PAM metadata for the input file */
+    struct pam outPam;   /* PAM metadata for the output file */
+    tuple ** inImg;      /* Input image */
+    tuple ** outImg;     /* Output image */
+    Quad *qfrom, *qto;   /* Source and target quadrilaterals */
+    Quad bbox;           /* Bounding box around the transformed input image */
+
+    inImg = pnm_readpam(ifP, &inPam, PAM_STRUCT_SIZE(tuple_type));
+
+    /* Extract quadrilaterals and populate them with the image bounds
+       if necessary. */
+    qfrom = prepareQuadrilateral(&inPam, &cmdlineP->qmap.from);
+    qto = prepareQuadrilateral(&inPam, &cmdlineP->qmap.to);
+
+    /* Allocate storage for the target image. */
+    if (cmdlineP->bbox.ul.x == 0 && cmdlineP->bbox.ul.y == 0 &&
+        cmdlineP->bbox.lr.x == 0 && cmdlineP->bbox.lr.y == 0)
+        /* User did not specify a target bounding box.  Compute optimal
+           dimensions. */
+        computeBoundingBox(qto, &bbox);
+    else
+        /* User specified a target bounding box.  Use it. */
+        bbox = cmdlineP->bbox;
+    outPam = inPam;
+    outPam.file = stdout;
+    outPam.width = bbox.lr.x - bbox.ul.x + 1;
+    outPam.height = bbox.lr.y - bbox.ul.y + 1;
+    outImg = initOutputImage(&outPam, cmdlineP);
+
+    mapQuadrilaterals(&inPam, &outPam,
+                      qfrom, qto,
+                      inImg, outImg,
+                      -bbox.ul.x, -bbox.ul.y);
+
+    pnm_writepam(&outPam, outImg);
+
+    pnm_freepamarray(outImg, &outPam);
+    pnm_freepamarray(inImg, &inPam);
+}
+
+
+
+int
+main(int argc, const char *argv[]) {
+
+    struct CmdlineInfo cmdline;      /* Parsed command line */
+    FILE * ifP;
+
+    pm_proginit(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+    processFile(ifP, &cmdline);
+
+    pm_close(ifP);
+
+    return 0;
+}
diff --git a/editor/pammixmulti.c b/editor/pammixmulti.c
index f5012d7a..2b45d807 100644
--- a/editor/pammixmulti.c
+++ b/editor/pammixmulti.c
@@ -13,6 +13,7 @@
 #include "shhopt.h"
 #include "mallocvar.h"
 #include "nstring.h"
+#include "rand.h"
 
 typedef enum {
     BLEND_AVERAGE,   /* Take the average color of all pixels */
@@ -36,6 +37,8 @@ struct ProgramState {
         /* Standard deviation when selecting images via a mask */
     unsigned long ** imageWeights;
         /* Per-image weights as a function of grayscale level */
+    struct pm_randSt randSt;
+        /* Random number generator parameters and internal state */
 };
 
 
@@ -134,9 +137,9 @@ parseCommandLine(int argc, const char ** argv,
 }
 
 static void
-openInputFiles(unsigned int          const inFileCt,
-               const char **         const inFileName,
-               struct ProgramState * const stateP) {
+initInput(unsigned int          const inFileCt,
+          const char **         const inFileName,
+          struct ProgramState * const stateP) {
 /*----------------------------------------------------------------------------
   Open all of the input files.
 
@@ -180,6 +183,25 @@ openInputFiles(unsigned int          const inFileCt,
 }
 
 
+
+static void
+termInput(struct ProgramState * const stateP) {
+/*----------------------------------------------------------------------------
+  Deallocate all of the resources we allocated.
+-----------------------------------------------------------------------------*/
+    unsigned int i;
+
+    for (i = 0; i < stateP->inFileCt; ++i) {
+        pnm_freepamrow(stateP->inTupleRows[i]);
+        pm_close(stateP->inPam[i].file);
+    }
+
+    free(stateP->inTupleRows);
+    free(stateP->inPam);
+}
+
+
+
 static void
 initMask(const char *          const maskFileName,
          struct ProgramState * const stateP) {
@@ -233,6 +255,15 @@ initOutput(FILE *                const ofP,
 }
 
 
+static void
+termOutput(struct ProgramState * const stateP) {
+
+    free(stateP->outTupleRow);
+
+    pm_close(stateP->outPam.file);
+}
+
+
 
 static void
 blendTuplesRandom(struct ProgramState * const stateP,
@@ -243,8 +274,8 @@ blendTuplesRandom(struct ProgramState * const stateP,
   from a random input image.
 -----------------------------------------------------------------------------*/
     unsigned int const depth = stateP->inPam[0].depth;
-    unsigned int const img = (unsigned int) (rand() % stateP->inFileCt);
-
+    unsigned int const img = (unsigned int) (pm_rand(&stateP->randSt) %
+                                             stateP->inFileCt);
     unsigned int samp;
 
     for (samp = 0; samp < depth; ++samp)
@@ -276,23 +307,26 @@ blendTuplesAverage(struct ProgramState * const stateP,
 
 
 
+#if 0
 static void
-randomNormal2(double * const r1P,
-              double * const r2P) {
+randomNormal2(double *           const r1P,
+              double *           const r2P,
+              struct pm_randSt * const randStP) {
 /*----------------------------------------------------------------------------
   Return two normally distributed random numbers.
 -----------------------------------------------------------------------------*/
     double u1, u2;
 
     do {
-        u1 = drand48();
-        u2 = drand48();
+        u1 = drand48(randStP);
+        u2 = drand48(randStP);
     }
     while (u1 <= DBL_EPSILON);
 
     *r1P = sqrt(-2.0*log(u1)) * cos(2.0*M_PI*u2);
     *r2P = sqrt(-2.0*log(u1)) * sin(2.0*M_PI*u2);
 }
+#endif
 
 
 
@@ -332,7 +366,8 @@ precomputeImageWeights(struct ProgramState * const stateP,
             double r[2];
             unsigned int k;
 
-            randomNormal2(&r[0], &r[1]);
+            pm_gaussrand2(&stateP->randSt, &r[0], &r[1]);
+
             for (k = 0; k < 2; ++k) {
                 int const img =
                     r[k] * sigma + pctGray * stateP->inFileCt * 0.999999;
@@ -453,26 +488,6 @@ blendImages(BlendType             const blend,
 
 
 
-static void
-termState(struct ProgramState * const stateP) {
-/*----------------------------------------------------------------------------
-  Deallocate all of the resources we allocated.
------------------------------------------------------------------------------*/
-    unsigned int i;
-
-    for (i = 0; i < stateP->inFileCt; ++i) {
-        pnm_freepamrow(stateP->inTupleRows[i]);
-        pm_close(stateP->inPam[i].file);
-    }
-
-    free(stateP->outTupleRow);
-    free(stateP->inTupleRows);
-    free(stateP->inPam);
-    pm_close(stateP->outPam.file);
-}
-
-
-
 int
 main(int argc, const char * argv[]) {
 
@@ -483,13 +498,14 @@ main(int argc, const char * argv[]) {
 
     parseCommandLine(argc, argv, &cmdline);
 
-    srand(cmdline.randomseedSpec ? cmdline.randomseed : pm_randseed());
-
-    openInputFiles(cmdline.inFileNameCt, cmdline.inFileName, &state);
+    initInput(cmdline.inFileNameCt, cmdline.inFileName, &state);
 
     if (cmdline.blend == BLEND_MASK)
         initMask(cmdline.maskfile, &state);
 
+    pm_randinit(&state.randSt);
+    pm_srand2(&state.randSt, cmdline.randomseedSpec, cmdline.randomseed);
+
     initOutput(stdout, &state);
 
     if (cmdline.blend == BLEND_MASK)
@@ -497,10 +513,14 @@ main(int argc, const char * argv[]) {
 
     blendImages(cmdline.blend, &state);
 
+    termOutput(&state);
+
+    pm_randterm(&state.randSt);
+
     if (cmdline.blend == BLEND_MASK)
         termMask(&state);
 
-    termState(&state);
+    termInput(&state);
 
     freeCmdline(&cmdline);
 
diff --git a/editor/pamrecolor.c b/editor/pamrecolor.c
index 8c5bce12..86c1965c 100644
--- a/editor/pamrecolor.c
+++ b/editor/pamrecolor.c
@@ -1,3 +1,4 @@
+
 /* ----------------------------------------------------------------------
  *
  * Replace every pixel in an image with one of equal luminance
@@ -32,6 +33,7 @@
 
 #include "mallocvar.h"
 #include "nstring.h"
+#include "rand.h"
 #include "shhopt.h"
 #include "pam.h"
 
@@ -42,7 +44,7 @@
 #define CLAMPxy(N, A, B) MAX(MIN((float)(N), (float)(B)), (float)(A))
 
 
-struct rgbfrac {
+struct Rgbfrac {
     /* This structure represents red, green, and blue, each expressed
        as a fraction from 0.0 to 1.0.
     */
@@ -51,19 +53,19 @@ struct rgbfrac {
     float bfrac;
 };
 
-struct cmdlineInfo {
+struct CmdlineInfo {
     /* This structure represents all of the information the user
        supplied in the command line but in a form that's easy for the
        program to use.
     */
     const char *    inputFileName;  /* '-' if stdin */
     const char *    colorfile;      /* NULL if unspecified */
-    struct rgbfrac  color2gray;
+    struct Rgbfrac  color2gray;
         /* colorspace/rmult/gmult/bmult options.  Negative numbers if
            unspecified.
         */
     unsigned int    targetcolorSpec;
-    struct rgbfrac  targetcolor;
+    struct Rgbfrac  targetcolor;
     unsigned int    randomseed;
     unsigned int    randomseedSpec;
 };
@@ -71,7 +73,7 @@ struct cmdlineInfo {
 
 
 static float
-rgb2gray(struct rgbfrac * const color2grayP,
+rgb2gray(struct Rgbfrac * const color2grayP,
          float            const red,
          float            const grn,
          float            const blu) {
@@ -122,7 +124,7 @@ getColorRow(struct pam  * const pamP,
 
 static void
 convertRowToGray(struct pam     * const pamP,
-                 struct rgbfrac * const color2gray,
+                 struct Rgbfrac * const color2gray,
                  tuplen         * const tupleRow,
                  samplen        * const grayRow) {
 /*----------------------------------------------------------------------
@@ -160,7 +162,7 @@ convertRowToGray(struct pam     * const pamP,
 static void
 explicitlyColorRow(struct pam *   const pamP,
                    tuplen *       const rowData,
-                   struct rgbfrac const tint) {
+                   struct Rgbfrac const tint) {
 
     unsigned int col;
 
@@ -175,17 +177,25 @@ explicitlyColorRow(struct pam *   const pamP,
 
 static void
 randomlyColorRow(struct pam *   const pamP,
-                 tuplen *       const rowData) {
+                 tuplen *       const rowData,
+                 bool           const randomseedSpec,
+                 unsigned int   const randomseed) {
 /*----------------------------------------------------------------------
   Assign each tuple in a row a random color.
 ------------------------------------------------------------------------*/
     unsigned int col;
+    struct pm_randSt randSt;
+
+    pm_randinit(&randSt);
+    pm_srand2(&randSt, randomseedSpec, randomseed);
 
     for (col = 0; col < pamP->width; ++col) {
-        rowData[col][PAM_RED_PLANE] = rand() / (float)RAND_MAX;
-        rowData[col][PAM_GRN_PLANE] = rand() / (float)RAND_MAX;
-        rowData[col][PAM_BLU_PLANE] = rand() / (float)RAND_MAX;
+        rowData[col][PAM_RED_PLANE] = pm_drand(&randSt);
+        rowData[col][PAM_GRN_PLANE] = pm_drand(&randSt);
+        rowData[col][PAM_BLU_PLANE] = pm_drand(&randSt);
     }
+
+    pm_randterm(&randSt);
 }
 
 
@@ -193,7 +203,7 @@ randomlyColorRow(struct pam *   const pamP,
 static void
 recolorRow(struct pam     * const inPamP,
            tuplen         * const inRow,
-           struct rgbfrac * const color2grayP,
+           struct Rgbfrac * const color2grayP,
            tuplen         * const colorRow,
            struct pam     * const outPamP,
            tuplen         * const outRow) {
@@ -293,10 +303,10 @@ recolorRow(struct pam     * const inPamP,
 
 
 
-static struct rgbfrac
+static struct Rgbfrac
 color2GrayFromCsName(const char * const csName) {
 
-    struct rgbfrac retval;
+    struct Rgbfrac retval;
 
     /* Thanks to
        http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
@@ -352,7 +362,7 @@ color2GrayFromCsName(const char * const csName) {
 
 static void
 parseCommandLine(int argc, const char ** const argv,
-                 struct cmdlineInfo * const cmdlineP ) {
+                 struct CmdlineInfo * const cmdlineP ) {
 
     optEntry     * option_def;
         /* Instructions to OptParseOptions3 on how to parse our options */
@@ -445,7 +455,7 @@ parseCommandLine(int argc, const char ** const argv,
 
 int
 main(int argc, const char *argv[]) {
-    struct cmdlineInfo cmdline;          /* Command-line parameters */
+    struct CmdlineInfo cmdline;          /* Command-line parameters */
     struct pam         inPam;
     struct pam         outPam;
     struct pam         colorPam;
@@ -462,8 +472,6 @@ main(int argc, const char *argv[]) {
 
     parseCommandLine(argc, argv, &cmdline);
 
-    srand(cmdline.randomseedSpec ? cmdline.randomseed : pm_randseed());
-
     ifP = pm_openr(cmdline.inputFileName);
     inPam.comment_p = &comments;
     pnm_readpaminit(ifP, &inPam, PAM_STRUCT_SIZE(comment_p));
@@ -474,7 +482,6 @@ main(int argc, const char *argv[]) {
     outPam.depth = 4 - (inPam.depth % 2);
     outPam.allocation_depth = outPam.depth;
     strcpy(outPam.tuple_type, PAM_PPM_TUPLETYPE);
-    pnm_writepaminit(&outPam);
 
     if (cmdline.colorfile) {
         colorfP = pm_openr(cmdline.colorfile);
@@ -492,6 +499,8 @@ main(int argc, const char *argv[]) {
 
     colorRowBuffer = pnm_allocpamrown(&outPam);
 
+    pnm_writepaminit(&outPam);
+
     for (row = 0; row < inPam.height; ++row) {
         tuplen * colorRow;
 
@@ -505,7 +514,8 @@ main(int argc, const char *argv[]) {
             if (cmdline.targetcolorSpec)
                 explicitlyColorRow(&colorPam, colorRow, cmdline.targetcolor);
             else
-                randomlyColorRow(&colorPam, colorRow);
+                randomlyColorRow(&colorPam, colorRow,
+                                 cmdline.randomseedSpec, cmdline.randomseed);
         }
         recolorRow(&inPam, inRow,
                    &cmdline.color2gray, colorRow,
diff --git a/editor/pamrubber.c b/editor/pamrubber.c
index 602701ec..fda31203 100644
--- a/editor/pamrubber.c
+++ b/editor/pamrubber.c
@@ -1,20 +1,18 @@
-/*----------------------------------------------------------------------------*/
-
-/* pamrubber.c - transform images using Rubber Sheeting algorithm
-**               see: http://www.schaik.com/netpbm/rubber/
-**
-** Copyright (C) 2011 by Willem van Schaik (willem@schaik.com)
-**
-** Permission to use, copy, modify, and distribute this software and its
-** documentation for any purpose and without fee is hereby granted, provided
-** that the above copyright notice appear in all copies and that both that
-** copyright notice and this permission notice appear in supporting
-** documentation.  This software is provided "as is" without express or
-** implied warranty.
-*/
-
-/*----------------------------------------------------------------------------*/
-
+/*=============================================================================
+                              pamrubber
+===============================================================================
+  Transform images using Rubber Sheeting algorithm
+  See: http://www.schaik.com/netpbm/rubber/
+
+  Copyright (C) 2011 by Willem van Schaik (willem@schaik.com)
+
+  Permission to use, copy, modify, and distribute this software and its
+  documentation for any purpose and without fee is hereby granted, provided
+  that the above copyright notice appear in all copies and that both that
+  copyright notice and this permission notice appear in supporting
+  documentation.  This software is provided "as is" without express or
+  implied warranty.
+=============================================================================*/
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
@@ -25,12 +23,12 @@
 
 #include "pm_c_util.h"
 #include "mallocvar.h"
+#include "rand.h"
 #include "shhopt.h"
 #include "pam.h"
 #include "pamdraw.h"
 
 
-
 typedef struct {
   double x;
   double y;
@@ -54,7 +52,7 @@ typedef struct {
     point br;  /* bottom right */
 } quadrilateral;
 
-struct cmdlineInfo {
+struct CmdlineInfo {
     unsigned int nCP;
     point        oldCP[4];
     point        newCP[4];
@@ -64,14 +62,14 @@ struct cmdlineInfo {
     unsigned int frame;
     unsigned int linear;
     unsigned int verbose;
-    unsigned int randseedSpec;
-    unsigned int randseed;
+    unsigned int randomseedSpec;
+    unsigned int randomseed;
 };
 
 
 static void
 parseCmdline(int argc, const char ** argv,
-             struct cmdlineInfo * const cmdlineP) {
+             struct CmdlineInfo * const cmdlineP) {
 
 /* parse all parameters from the command line */
 
@@ -92,10 +90,10 @@ parseCmdline(int argc, const char ** argv,
     OPTENT3(0, "frame",    OPT_FLAG, NULL, &cmdlineP->frame,    0);
     OPTENT3(0, "linear",   OPT_FLAG, NULL, &cmdlineP->linear,   0);
     OPTENT3(0, "verbose",  OPT_FLAG, NULL, &cmdlineP->verbose,  0);
-    OPTENT3(0, "randseed", OPT_UINT, &cmdlineP->randseed,
-            &cmdlineP->randseedSpec, 0);
-    OPTENT3(0, "randomseed", OPT_UINT, &cmdlineP->randseed,
-            &cmdlineP->randseedSpec, 0);
+    OPTENT3(0, "randseed", OPT_UINT, &cmdlineP->randomseed,
+            &cmdlineP->randomseedSpec, 0);
+    OPTENT3(0, "randomseed", OPT_UINT, &cmdlineP->randomseed,
+            &cmdlineP->randomseedSpec, 0);
 
     opt.opt_table = option_def;
     opt.short_allowed = FALSE;  /* we have no short (old-fashioned) options */
@@ -339,28 +337,29 @@ windtriangle(triangle * const tP,
 
 
 static double
-tiny(void) {
+tiny(struct pm_randSt * const randStP) {
 
-    if (rand() % 2)
-        return +1E-6 * (double) ((rand() % 90) + 9);
+    if (pm_rand(randStP) % 2)
+        return +1E-6 * (double) ((pm_rand(randStP) % 90) + 9);
     else
-        return -1E-6 * (double) ((rand() % 90) + 9);
+        return -1E-6 * (double) ((pm_rand(randStP) % 90) + 9);
 }
 
 
 
 static void
 angle(point * const p1P,
-      point * const p2P) {
+      point * const p2P,
+      struct pm_randSt * const randStP) {
 /*----------------------------------------------------------------------------
    Move *p2P slightly if necessary to make sure the line (*p1P, *p2P)
    is not horizontal or vertical.
 -----------------------------------------------------------------------------*/
     if (p1P->x == p2P->x) { /* vertical line */
-        p2P->x += tiny();
+        p2P->x += tiny(randStP);
     }
     if (p1P->y == p2P->y) { /* horizontal line */
-        p2P->y += tiny();
+        p2P->y += tiny(randStP);
     }
 }
 
@@ -720,8 +719,10 @@ static void drawClippedTriangle(const struct pam * const pamP,
 
 
 static void
-prepTrig(int const wd,
-         int const ht) {
+prepTrig(int          const wd,
+         int          const ht,
+         bool         const randomseedSpec,
+         unsigned int const randomseed) {
 
 /* create triangles using control points */
 
@@ -731,16 +732,20 @@ prepTrig(int const wd,
     point c2p1, c2p2, c2p3, c2p4;
     line l1, l2;
     point p0;
+    struct pm_randSt randSt;
+
+    pm_randinit(&randSt);
+    pm_srand2(&randSt, randomseedSpec, randomseed);
 
-    rtl1 = makepoint(0.0 + tiny(),               0.0 + tiny());
-    rtr1 = makepoint((double) wd - 1.0 + tiny(), 0.0 + tiny());
-    rbl1 = makepoint(0.0 + tiny(),               (double) ht - 1.0 + tiny());
-    rbr1 = makepoint((double) wd - 1.0 + tiny(), (double) ht - 1.0 + tiny());
+    rtl1 = makepoint(0.0 + tiny(&randSt),               0.0 + tiny(&randSt));
+    rtr1 = makepoint((double) wd - 1.0 + tiny(&randSt), 0.0 + tiny(&randSt));
+    rbl1 = makepoint(0.0 + tiny(&randSt),               (double) ht - 1.0 + tiny(&randSt));
+    rbr1 = makepoint((double) wd - 1.0 + tiny(&randSt), (double) ht - 1.0 + tiny(&randSt));
 
-    rtl2 = makepoint(0.0 + tiny(),               0.0 + tiny());
-    rtr2 = makepoint((double) wd - 1.0 + tiny(), 0.0 + tiny());
-    rbl2 = makepoint(0.0 + tiny(),               (double) ht - 1.0 + tiny());
-    rbr2 = makepoint((double) wd - 1.0 + tiny(), (double) ht - 1.0 + tiny());
+    rtl2 = makepoint(0.0 + tiny(&randSt),               0.0 + tiny(&randSt));
+    rtr2 = makepoint((double) wd - 1.0 + tiny(&randSt), 0.0 + tiny(&randSt));
+    rbl2 = makepoint(0.0 + tiny(&randSt),               (double) ht - 1.0 + tiny(&randSt));
+    rbr2 = makepoint((double) wd - 1.0 + tiny(&randSt), (double) ht - 1.0 + tiny(&randSt));
 
     if (nCP == 1) {
         c1p1 = oldCP[0];
@@ -772,8 +777,8 @@ prepTrig(int const wd,
         c2p2 = newCP[1];
 
         /* check for hor/ver edges */
-        angle (&c1p1, &c1p2);
-        angle (&c2p1, &c2p2);
+        angle (&c1p1, &c1p2, &randSt);
+        angle (&c2p1, &c2p2, &randSt);
 
         /* connect two control points to corners to get 6 triangles */
         /* left side */
@@ -811,13 +816,13 @@ prepTrig(int const wd,
         /* Move vertices slightly if necessary to make sure no edge is
            horizontal or vertical.
         */
-        angle(&c1p1, &c1p2);
-        angle(&c1p2, &c1p3);
-        angle(&c1p3, &c1p1);
+        angle(&c1p1, &c1p2, &randSt);
+        angle(&c1p2, &c1p3, &randSt);
+        angle(&c1p3, &c1p1, &randSt);
 
-        angle(&c2p1, &c2p2);
-        angle(&c2p2, &c2p3);
-        angle(&c2p3, &c2p1);
+        angle(&c2p1, &c2p2, &randSt);
+        angle(&c2p2, &c2p3, &randSt);
+        angle(&c2p3, &c2p1, &randSt);
 
         if (windtriangle(&tri1s[0], c1p1, c1p2, c1p3)) {
             tri2s[0] = maketriangle(c2p1, c2p2, c2p3);
@@ -871,19 +876,19 @@ prepTrig(int const wd,
         c2p4 = newCP[3];
 
         /* check for hor/ver edges */
-        angle (&c1p1, &c1p2);
-        angle (&c1p2, &c1p3);
-        angle (&c1p3, &c1p4);
-        angle (&c1p4, &c1p1);
-        angle (&c1p1, &c1p3);
-        angle (&c1p2, &c1p4);
-
-        angle (&c2p1, &c2p2);
-        angle (&c2p2, &c2p3);
-        angle (&c2p3, &c2p4);
-        angle (&c2p4, &c2p1);
-        angle (&c2p1, &c2p3);
-        angle (&c2p2, &c2p4);
+        angle (&c1p1, &c1p2, &randSt);
+        angle (&c1p2, &c1p3, &randSt);
+        angle (&c1p3, &c1p4, &randSt);
+        angle (&c1p4, &c1p1, &randSt);
+        angle (&c1p1, &c1p3, &randSt);
+        angle (&c1p2, &c1p4, &randSt);
+
+        angle (&c2p1, &c2p2, &randSt);
+        angle (&c2p2, &c2p3, &randSt);
+        angle (&c2p3, &c2p4, &randSt);
+        angle (&c2p4, &c2p1, &randSt);
+        angle (&c2p1, &c2p3, &randSt);
+        angle (&c2p2, &c2p4, &randSt);
 
         /*-------------------------------------------------------------------*/
         /*        -1-      -2-        -3-      -4-        -5-      -6-       */
@@ -979,6 +984,8 @@ prepTrig(int const wd,
                      &tri2s[9], c2p3, c2p1, rtl2, rtr2, rbl2, rbr2);
         nTri = 10;
     }
+
+    pm_randterm(&randSt);
 }
 
 
@@ -1277,7 +1284,7 @@ warpQuad(point   const p2,
 
 
 static void
-setGlobalCP(struct cmdlineInfo const cmdline) {
+setGlobalCP(struct CmdlineInfo const cmdline) {
 
     unsigned int i;
 
@@ -1392,7 +1399,7 @@ pix(tuple **     const tuples,
 int
 main(int argc, const char ** const argv) {
 
-    struct cmdlineInfo cmdline;
+    struct CmdlineInfo cmdline;
     FILE * ifP;
     struct pam inpam, outpam;
     tuple ** inTuples;
@@ -1405,8 +1412,6 @@ main(int argc, const char ** const argv) {
 
     setGlobalCP(cmdline);
 
-    srand(cmdline.randseedSpec ? cmdline.randseed : pm_randseed());
-
     ifP = pm_openr(cmdline.fileName);
 
     inTuples = pnm_readpam(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
@@ -1421,7 +1426,8 @@ main(int argc, const char ** const argv) {
     makeAllWhite(&outpam, outTuples);
 
     if (cmdline.tri)
-        prepTrig(inpam.width, inpam.height);
+        prepTrig(inpam.width, inpam.height,
+                 cmdline.randomseedSpec, cmdline.randomseed);
     if (cmdline.quad)
         prepQuad();
 
diff --git a/editor/pbmreduce.c b/editor/pbmreduce.c
index 3a0968fe..70caa581 100644
--- a/editor/pbmreduce.c
+++ b/editor/pbmreduce.c
@@ -11,9 +11,11 @@
 */
 
 #include "pm_c_util.h"
-#include "pbm.h"
 #include "mallocvar.h"
+#include "rand.h"
 #include "shhopt.h"
+#include "pbm.h"
+
 #include <assert.h>
 
 #define SCALE 1024
@@ -105,7 +107,7 @@ parseCommandLine(int argc, const char ** argv,
         unsigned int scale;
 
         scale = strtol(argv[1], &endptr, 10);
-        if (*argv[1] == '\0') 
+        if (*argv[1] == '\0')
             pm_error("Scale argument is a null string.  Must be a number.");
         else if (*endptr != '\0')
             pm_error("Scale argument contains non-numeric character '%c'.",
@@ -115,7 +117,7 @@ parseCommandLine(int argc, const char ** argv,
                      "You specified %d", scale);
         else if (scale > INT_MAX / scale)
             pm_error("Scale argument too large.  You specified %d", scale);
-        else 
+        else
             cmdlineP->scale = scale;
 
         if (argc-1 > 1) {
@@ -145,10 +147,11 @@ struct FS {
 static void
 initializeFloydSteinberg(struct FS  * const fsP,
                          int          const newcols,
-                         unsigned int const seed,
-                         bool         const seedSpec) {
+                         bool         const seedSpec,
+                         unsigned int const seed) {
 
     unsigned int col;
+    struct pm_randSt randSt;
 
     MALLOCARRAY(fsP->thiserr, newcols + 2);
     MALLOCARRAY(fsP->nexterr, newcols + 2);
@@ -156,33 +159,36 @@ initializeFloydSteinberg(struct FS  * const fsP,
     if (fsP->thiserr == NULL || fsP->nexterr == NULL)
         pm_error("out of memory");
 
-    srand(seedSpec ? seed : pm_randseed());
+    pm_randinit(&randSt);
+    pm_srand2(&randSt, seedSpec, seed);
 
     for (col = 0; col < newcols + 2; ++col)
-        fsP->thiserr[col] = (rand() % SCALE - HALFSCALE) / 4;
+        fsP->thiserr[col] = ((int) (pm_rand(&randSt) % SCALE) - HALFSCALE) / 4;
         /* (random errors in [-SCALE/8 .. SCALE/8]) */
+
+    pm_randterm(&randSt);
 }
 
 
 
 /*
     Scanning method
-    
+
     In Floyd-Steinberg dithering mode horizontal direction of scan alternates
     between rows; this is called "serpentine scanning".
-    
+
     Example input (14 x 7), N=3:
-    
+
     111222333444xx    Fractional pixels on the right edge and bottom edge (x)
-    111222333444xx    are ignored; their values do not influence output. 
+    111222333444xx    are ignored; their values do not influence output.
     111222333444xx
     888777666555xx
     888777666555xx
     888777666555xx
     xxxxxxxxxxxxxx
-    
+
     Output (4 x 2):
-    
+
     1234
     8765
 
@@ -196,11 +202,14 @@ enum Direction { RIGHT_TO_LEFT, LEFT_TO_RIGHT };
 static enum Direction
 oppositeDir(enum Direction const arg) {
 
+    enum Direction retval;
+
     switch (arg) {
-    case LEFT_TO_RIGHT: return RIGHT_TO_LEFT;
-    case RIGHT_TO_LEFT: return LEFT_TO_RIGHT;
+    case LEFT_TO_RIGHT: retval = RIGHT_TO_LEFT; break;
+    case RIGHT_TO_LEFT: retval = LEFT_TO_RIGHT; break;
     }
-    assert(false);  /* All cases handled above */
+
+    return retval;
 }
 
 
@@ -240,7 +249,7 @@ main(int argc, const char * argv[]) {
 
     if (cmdline.halftone == QT_FS)
         initializeFloydSteinberg(&fs, newcols,
-                                 cmdline.randomseed, cmdline.randomseedSpec);
+                                 cmdline.randomseedSpec, cmdline.randomseed);
     else {
         /* These variables are meaningless in this case, and the values
            should never be used.
@@ -258,10 +267,10 @@ main(int argc, const char * argv[]) {
         int limitCol;
         int startCol;
         int step;
-   
+
         for (colChar = 0; colChar < colChars; ++colChar)
             newbitrow[colChar] = 0x00;  /* Clear to white */
- 
+
         for (subrow = 0; subrow < cmdline.scale; ++subrow)
             pbm_readpbmrow(ifP, bitslice[subrow], cols, format);
 
@@ -274,7 +283,7 @@ main(int argc, const char * argv[]) {
         case LEFT_TO_RIGHT: {
             startCol = 0;
             limitCol = newcols;
-            step = +1;  
+            step = +1;
         } break;
         case RIGHT_TO_LEFT: {
             startCol = newcols - 1;
diff --git a/editor/pnmflip b/editor/pnmflip
index 07d4ddb9..962198a2 100755
--- a/editor/pnmflip
+++ b/editor/pnmflip
@@ -46,6 +46,13 @@ exec perl -w -x -S -- "$0" "$@"
 use strict;
 use File::Basename;
 use Cwd 'abs_path';
+use IO::Handle;
+
+sub pm_message($) {
+    STDERR->print("pnmflip: $_[0]\n");
+}
+
+
 
 my $xformOpt;
 my @miscOptions;
@@ -78,8 +85,7 @@ foreach (@ARGV) {
     } else {
         # It's a parameter
         if (defined($infile)) {
-            print(STDERR
-                  "You may specify at most one non-option parameter.\n");
+            pm_message("You may specify at most one non-option parameter.");
             exit(10);
         } else {
             $infile = $_;
diff --git a/editor/pnmquant b/editor/pnmquant
index f7af9e7a..0bebce69 100755
--- a/editor/pnmquant
+++ b/editor/pnmquant
@@ -37,9 +37,16 @@ use Getopt::Long;
 use File::Spec;
 #use Fcntl ":seek";  # not available in Perl 5.00503
 use Fcntl;  # gets open flags
+use IO::Handle;
 
 my ($TRUE, $FALSE) = (1,0);
 
+sub pm_message($) {
+    STDERR->print("pnmquant: $_[0]\n");
+}
+
+
+
 my ($SEEK_SET, $SEEK_CUR, $SEEK_END) = (0, 1, 2);
 
 
@@ -102,17 +109,16 @@ sub parseCommandLine(@) {
                                   "plain");
 
     if (!$optsAreValid) {
-        print(STDERR "Invalid option syntax.\n");
+        pm_message("Invalid option syntax");
         exit(1);
     }
     if (@ARGV > 2) {
-        print(STDERR "This program takes at most 2 arguments.  You specified ",
-              scalar(@ARGV), "\n");
+        pm_message("This program takes at most 2 arguments.  You specified " .
+                   scalar(@ARGV));
         exit(1);
     } 
     elsif (@ARGV < 1) {
-        print(STDERR 
-              "You must specify the number of colors as an argument.\n");
+        pm_message("You must specify the number of colors as an argument.");
         exit(1);
     }
     my $infile;
@@ -120,9 +126,8 @@ sub parseCommandLine(@) {
     
     if (!($cmdline{ncolors} =~ m{ ^[[:digit:]]+$ }x ) || 
         $cmdline{ncolors} == 0) {
-        print(STDERR 
-              "Number of colors argument '$cmdline{ncolors}' " .
-              "is not a positive integer.\n");
+        pm_message("Number of colors argument '$cmdline{ncolors}' " .
+                   "is not a positive integer.");
         exit(1);
     }
 
@@ -210,8 +215,8 @@ sub makeColormap($$$$$$$) {
     my ($mapfileFh, $mapfileSpec) = tempFile(".pnm");
 
     if (!defined($mapfileFh)) {
-        print(STDERR "Unable to create temporary file for colormap.  " .
-              "errno = $ERRNO\n");
+        pm_message("Unable to create temporary file for colormap.  " .
+                   "errno = $ERRNO");
         exit(1);
     }
        
@@ -223,8 +228,8 @@ sub makeColormap($$$$$$$) {
         (defined($opt_center)    ? 1 : 0);
 
     if ($colorSummaryOptCt > 1) {
-        print(STDERR "You can specify only one of " .
-              "-meanpixel, -meancolor, and -center\n");
+        pm_message("You can specify only one of " .
+                   "-meanpixel, -meancolor, and -center");
         exit(1);
     }
     if (defined($opt_meanpixel)) {
@@ -240,8 +245,8 @@ sub makeColormap($$$$$$$) {
         (defined($opt_spreadbrightness) ? 1 : 0);
 
     if ($spreadOptCt > 1) {
-        print(STDERR "You can specify only one of " .
-              "-spreadluminosity and -spreadbrightness\n");
+        pm_message("You can specify only one of " .
+                   "-spreadluminosity and -spreadbrightness");
         exit(1);
     }
 
@@ -263,7 +268,7 @@ sub makeColormap($$$$$$$) {
     my $maprc = system("pnmcolormap", $ncolors, @options);
 
     if ($maprc != 0) {
-        print(STDERR "pnmcolormap failed, rc=$maprc\n");
+        pm_message("pnmcolormap failed, rc=$maprc");
         exit(1);
     } 
     return $mapfileSpec;
@@ -287,15 +292,15 @@ sub remap($$$$$$) {
     }
     if ($opt_norandom) {
         if (defined($opt_randomseed)) {
-             print(STDERR "You cannot specify -randomseed with -norandom\n");
+             pm_message("You cannot specify -randomseed with -norandom");
              exit(1);
         }
         push(@options, "-norandom");
     }
     if (defined($opt_randomseed)) {
         if ($opt_randomseed < 0) {
-             print(STDERR "-randomseed value must not be negative.  " .
-                   "You specified $opt_randomseed\n");
+             pm_message("-randomseed value must not be negative.  " .
+                        "You specified $opt_randomseed");
              exit(10);
         }
         push(@options, "-randomseed=$opt_randomseed");
@@ -310,7 +315,7 @@ sub remap($$$$$$) {
     my $remaprc = system("pnmremap", "-mapfile=$mapfileSpec", @options);
     
     if ($remaprc != 0) {
-        print(STDERR "pnmremap failed, rc=$remaprc\n");
+        pm_message("pnmremap failed, rc=$remaprc");
         exit(1);
     }
 }
diff --git a/editor/pnmquantall b/editor/pnmquantall
index aea6cc84..80d00fce 100755
--- a/editor/pnmquantall
+++ b/editor/pnmquantall
@@ -57,11 +57,18 @@ use warnings;
 use English;
 use Fcntl;  # gets open flags
 use File::Copy;
+use IO::Handle;
 
 my $TRUE=1; my $FALSE = 0;
 
 
 
+sub pm_message($) {
+    STDERR->print("pnmquantall: $_[0]\n");
+}
+
+
+
 sub doVersionHack($) {
     my ($argvR) = @_;
 
@@ -84,7 +91,7 @@ sub parseArgs($$$$) {
 
     if (@argv > 0 && $argv[0] eq "-ext") {
         if (@argv < 2) {
-            print STDERR ("-ext requires a value\n");
+            pm_message("-ext requires a value");
         exit(100);
         } else {
             $$extR = $argv[1];
@@ -96,8 +103,8 @@ sub parseArgs($$$$) {
     }
 
     if (@argv < $firstArgPos + 2) {
-        print STDERR ("Not enough arguments.  You need at least the number " .
-                      "of colors and one file name\n");
+        pm_message("Not enough arguments.  You need at least the number " .
+                      "of colors and one file name");
         exit(100);
     }
     
@@ -212,7 +219,7 @@ if (!$progError) {
 my $exitStatus;
 
 if ($progError) {
-    print STDERR ("Failed.  $progError\n");
+    pm_message("Failed.  $progError");
     $exitStatus = 1;
 } else {
     $exitStatus = 0;
diff --git a/editor/pnmremap.c b/editor/pnmremap.c
index 0c0096ba..e5b59d04 100644
--- a/editor/pnmremap.c
+++ b/editor/pnmremap.c
@@ -28,6 +28,7 @@
 #include "pm_c_util.h"
 #include "mallocvar.h"
 #include "nstring.h"
+#include "rand.h"
 #include "shhopt.h"
 #include "pam.h"
 #include "ppm.h"
@@ -399,20 +400,21 @@ randomizeError(long **       const err,
    Set a random error in the range [-1 .. 1] (normalized via FS_SCALE)
    in the error array err[][].
 -----------------------------------------------------------------------------*/
-    unsigned int const seed = (random.init == RANDOM_WITHSEED) ?
-        random.seed : pm_randseed();
-
     unsigned int col;
+    struct pm_randSt randSt;
 
     assert(random.init != RANDOM_NONE);
 
-    srand(seed);
+    pm_randinit(&randSt);
+    pm_srand2(&randSt, random.init == RANDOM_WITHSEED, random.seed);
 
     for (col = 0; col < width; ++col) {
         unsigned int plane;
         for (plane = 0; plane < depth; ++plane)
-            err[plane][col] = rand() % (FS_SCALE * 2) - FS_SCALE;
+            err[plane][col] = pm_rand(&randSt) % (FS_SCALE * 2) - FS_SCALE;
     }
+
+    pm_randterm(&randSt);
 }
 
 
diff --git a/editor/specialty/pampaintspill.c b/editor/specialty/pampaintspill.c
index c7994723..5cd482d5 100644
--- a/editor/specialty/pampaintspill.c
+++ b/editor/specialty/pampaintspill.c
@@ -45,11 +45,11 @@
 
 #include "mallocvar.h"
 #include "nstring.h"
+#include "rand.h"
 #include "shhopt.h"
 #include "pam.h"
 #include "pammap.h"
 
-
 static time_t const timeUpdateDelta = 30;
     /* Seconds between progress updates */
 static int const    minUpdates = 4;
@@ -67,6 +67,8 @@ struct cmdlineInfo {
     unsigned int all;
     float        power;
     unsigned int downsample;
+    unsigned int randomseedSpec;
+    unsigned int randomseed;
 };
 
 struct coords {
@@ -98,16 +100,18 @@ parseCommandLine(int argc, const char ** const argv,
     MALLOCARRAY_NOFAIL(option_def, 100);
     option_def_index = 0;          /* Incremented by OPTENTRY */
 
-    OPTENT3(0, "bgcolor",    OPT_STRING, &cmdlineP->bgcolor,    
+    OPTENT3(0, "bgcolor",    OPT_STRING, &cmdlineP->bgcolor,
             &bgcolorSpec, 0);
     OPTENT3(0, "wrap",       OPT_FLAG,   NULL,
             &cmdlineP->wrap,       0);
     OPTENT3(0, "all",        OPT_FLAG,   NULL,
             &cmdlineP->all,        0);
-    OPTENT3(0, "power",      OPT_FLOAT,  &cmdlineP->power,      
+    OPTENT3(0, "power",      OPT_FLOAT,  &cmdlineP->power,
             &powerSpec, 0);
-    OPTENT3(0, "downsample", OPT_UINT,   &cmdlineP->downsample, 
+    OPTENT3(0, "downsample", OPT_UINT,   &cmdlineP->downsample,
             &downsampleSpec, 0);
+    OPTENT3(0, "randomseed", OPT_UINT,   &cmdlineP->randomseed,
+            &cmdlineP->randomseedSpec, 0);
 
     opt.opt_table = option_def;
     opt.short_allowed = 0;
@@ -223,7 +227,9 @@ locatePaintSources(struct pam *            const pamP,
                    tuple **                const tuples,
                    tuple                   const bgColor,
                    unsigned int            const downsample,
-                   struct paintSourceSet * const paintSourcesP) {
+                   struct paintSourceSet * const paintSourcesP,
+                   bool                    const randomseedSpec,
+                   unsigned int            const randomseed) {
 /*--------------------------------------------------------------------
   Construct a list of all pixel coordinates in the input image that
   represent a non-background color.
@@ -248,21 +254,24 @@ locatePaintSources(struct pam *            const pamP,
     pm_message("Image contains %u background + %u non-background pixels",
                pamP->width * pamP->height - paintSources.size,
                paintSources.size);
-    
+
     /* Reduce the number of paint sources to reduce execution time. */
     if (downsample > 0 && downsample < paintSources.size) {
+        struct pm_randSt randSt;
         unsigned int i;
 
-        srand(pm_randseed());
+        pm_randinit(&randSt);
+        pm_srand2(&randSt, randomseedSpec, randomseed);
 
         for (i = 0; i < downsample; ++i) {
             unsigned int const swapIdx =
-                i + rand() % (paintSources.size - i);
+                i + pm_rand(&randSt) % (paintSources.size - i);
             struct coords const swapVal = paintSources.list[i];
 
             paintSources.list[i] = paintSources.list[swapIdx];
             paintSources.list[swapIdx] = swapVal;
         }
+        pm_randterm(&randSt);
         paintSources.size = downsample;
     }
 
@@ -426,7 +435,7 @@ produceOutputImage(struct pam *          const pamP,
     for (row = 0; row < pamP->height; ++row) {
         struct coords   target;
         double        * newColor;
-        
+
         MALLOCARRAY(newColor, pamP->depth);
 
         target.y = row;
@@ -484,7 +493,8 @@ main(int argc, const char *argv[]) {
                pnm_colorname(&inPam, bgColor, PAM_COLORNAME_HEXOK));
 
     locatePaintSources(&inPam, inTuples, bgColor, cmdline.downsample,
-                       &paintSources);
+                       &paintSources,
+                       cmdline.randomseedSpec, cmdline.randomseed);
 
     produceOutputImage(&inPam, inTuples, bgColor, paintSources, distFunc,
                        cmdline.power, cmdline.all, &outTuples);
@@ -498,3 +508,5 @@ main(int argc, const char *argv[]) {
 
     return 0;
 }
+
+
diff --git a/editor/specialty/ppmshift.c b/editor/specialty/ppmshift.c
index cdb0f173..27cbb78c3 100644
--- a/editor/specialty/ppmshift.c
+++ b/editor/specialty/ppmshift.c
@@ -12,11 +12,11 @@
 #include <stdbool.h>
 
 #include "mallocvar.h"
+#include "rand.h"
 #include "shhopt.h"
 #include "ppm.h"
 
 
-
 struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
@@ -24,6 +24,7 @@ struct CmdlineInfo {
     const char * inputFileName;
 
     unsigned int shift;
+    unsigned int seedSpec;
     unsigned int seed;
 };
 
@@ -42,8 +43,6 @@ parseCommandLine(int argc, const char ** const argv,
 
     unsigned int option_def_index;
 
-    unsigned int seedSpec;
-
     MALLOCARRAY(option_def, 100);
 
     opt.opt_table = option_def;
@@ -52,14 +51,11 @@ parseCommandLine(int argc, const char ** const argv,
 
     option_def_index = 0;   /* incremented by OPTENT3 */
     OPTENT3(0,   "seed",            OPT_UINT,     &cmdlineP->seed,
-            &seedSpec,         0);
+            &cmdlineP->seedSpec,         0);
 
     pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
 
-    if (!seedSpec)
-        cmdlineP->seed = pm_randseed();
-
     if (argc-1 < 1)
         pm_error("You must specify the shift factor as an argument");
     else {
@@ -84,6 +80,62 @@ parseCommandLine(int argc, const char ** const argv,
 
 
 
+static void
+shiftRow(pixel *            const srcrow,
+         unsigned int       const cols,
+         unsigned int       const shift,
+         pixel *            const destrow,
+         struct pm_randSt * const randStP) {
+
+    /* the range by which a line is shifted lays in the range from */
+    /* -shift/2 .. +shift/2 pixels; however, within this range it is */
+    /* randomly chosen */
+
+    pixel * pP;
+    pixel * pP2;
+    int nowshift;
+
+    if (shift != 0)
+        nowshift = (pm_rand(randStP) % (shift+1)) - ((shift+1) / 2);
+    else
+        nowshift = 0;
+
+    pP  = &srcrow[0];
+    pP2 = &destrow[0];
+
+    /* if the shift value is less than zero, we take the original
+       pixel line and copy it into the destination line translated
+       to the left by x pixels. The empty pixels on the right end
+       of the destination line are filled up with the pixel that
+       is the right-most in the original pixel line.
+    */
+    if (nowshift < 0) {
+        unsigned int col;
+        pP += abs(nowshift);
+        for (col = 0; col < cols; ++col) {
+            PPM_ASSIGN(*pP2, PPM_GETR(*pP), PPM_GETG(*pP), PPM_GETB(*pP));
+            ++pP2;
+            if (col < (cols + nowshift) - 1)
+                ++pP;
+        }
+    } else {
+        unsigned int col;
+        /* The shift value is 0 or positive, so fill the first
+           <nowshift> pixels of the destination line with the
+           first pixel from the source line, and copy the rest of
+           the source line to the dest line
+        */
+        for (col = 0; col < cols; ++col) {
+            PPM_ASSIGN(*pP2, PPM_GETR(*pP), PPM_GETG(*pP), PPM_GETB(*pP));
+            ++pP2;
+            if (col >= nowshift)
+                ++pP;
+        }
+    }
+}
+
+
+
 int
 main(int argc, const char ** argv) {
 
@@ -95,13 +147,15 @@ main(int argc, const char ** argv) {
     pixel * destrow;
     unsigned int row;
     unsigned int shift;
+    struct pm_randSt randSt;
 
     /* parse in 'default' parameters */
     pm_proginit(&argc, argv);
 
     parseCommandLine(argc, argv, &cmdline);
 
-    srand(cmdline.seed);
+    pm_randinit(&randSt);
+    pm_srand2(&randSt, cmdline.seedSpec, cmdline.seed);
 
     ifP = pm_openr(cmdline.inputFileName);
 
@@ -115,67 +169,23 @@ main(int argc, const char ** argv) {
     } else
         shift = cmdline.shift;
 
-    srcrow = ppm_allocrow(cols);
-
+    srcrow  = ppm_allocrow(cols);
     destrow = ppm_allocrow(cols);
 
     ppm_writeppminit(stdout, cols, rows, maxval, 0);
 
-    /** now do the shifting **/
-    /* the range by which a line is shifted lays in the range from */
-    /* -shift/2 .. +shift/2 pixels; however, within this range it is */
-    /* randomly chosen */
     for (row = 0; row < rows; ++row) {
-        pixel * pP;
-        pixel * pP2;
-        unsigned int nowshift;
-
-        if (shift != 0)
-            nowshift = (rand() % (shift+1)) - ((shift+1) / 2);
-        else
-            nowshift = 0;
-
         ppm_readppmrow(ifP, srcrow, cols, maxval, format);
 
-        pP  = &srcrow[0];
-        pP2 = &destrow[0];
-
-        /* if the shift value is less than zero, we take the original
-           pixel line and copy it into the destination line translated
-           to the left by x pixels. The empty pixels on the right end
-           of the destination line are filled up with the pixel that
-           is the right-most in the original pixel line.
-        */
-        if (nowshift < 0) {
-            unsigned int col;
-            pP += abs(nowshift);
-            for (col = 0; col < cols; ++col) {
-                PPM_ASSIGN(*pP2, PPM_GETR(*pP), PPM_GETG(*pP), PPM_GETB(*pP));
-                ++pP2;
-                if (col < (cols + nowshift) - 1)
-                    ++pP;
-            }
-        } else {
-            unsigned int col;
-            /* The shift value is 0 or positive, so fill the first
-               <nowshift> pixels of the destination line with the
-               first pixel from the source line, and copy the rest of
-               the source line to the dest line
-            */
-            for (col = 0; col < cols; ++col) {
-                PPM_ASSIGN(*pP2, PPM_GETR(*pP), PPM_GETG(*pP), PPM_GETB(*pP));
-                ++pP2;
-                if (col >= nowshift)
-                    ++pP;
-            }
-        }
+        shiftRow(srcrow, cols, shift, destrow, &randSt);
 
         ppm_writeppmrow(stdout, destrow, cols, maxval, 0);
     }
 
-    pm_close(ifP);
-    ppm_freerow(srcrow);
     ppm_freerow(destrow);
+    ppm_freerow(srcrow);
+    pm_close(ifP);
+    pm_randterm(&randSt);
 
     return 0;
 }
diff --git a/editor/specialty/ppmspread.c b/editor/specialty/ppmspread.c
index 6753f4fe..7b9558e3 100644
--- a/editor/specialty/ppmspread.c
+++ b/editor/specialty/ppmspread.c
@@ -10,102 +10,158 @@
 
 #include <string.h>
 
+#include "nstring.h"
+#include "rand.h"
+#include "shhopt.h"
 #include "ppm.h"
 
 
+struct CmdlineInfo {
+    /* This structure represents all of the information the user
+       supplied in the command line but in a form that's easy for the
+       program to use.
+    */
+    const char * inputFilename;  /* '-' if stdin */
+    unsigned int spread;
+    unsigned int randomseedSpec;
+    unsigned int randomseed;
+};
+
+
+
+static void
+parseCommandLine(int argc, const char ** const argv,
+                 struct CmdlineInfo * const cmdlineP ) {
+
+    optEntry     * option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options */
+    optStruct3     opt;
+    unsigned int   option_def_index;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+    option_def_index = 0;          /* Incremented by OPTENTRY */
+
+    OPTENT3(0, "randomseed", OPT_UINT,   &cmdlineP->randomseed,
+            &cmdlineP->randomseedSpec, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = 0;
+    opt.allowNegNum = 1;
+
+    pm_optParseOptions3( &argc, (char **)argv, opt, sizeof(opt), 0 );
+
+    if (argc-1 < 1)
+        pm_error("You must specify the spread factor as an argument");
+    else {
+        const char * error;
+        pm_string_to_uint(argv[1], &cmdlineP->spread, &error);
+
+        if (error)
+            pm_error("Spread factor '%s' is not an unsigned integer.  %s",
+                     argv[1], error);
+
+        if (argc-1 < 2)
+            cmdlineP->inputFilename = "-";
+        else {
+            cmdlineP->inputFilename = argv[2];
+            if (argc-1 >2)
+                pm_error("Too many arguments: %u.  "
+                         "The only possible arguments are "
+                         "the spread factor and the optional input file name",
+                         argc-1);
+        }
+    }
+}
+
+
+
+static void
+spreadRow(pixel **           const srcarray,
+          unsigned int       const cols,
+          unsigned int       const rows,
+          unsigned int       const spread,
+          unsigned int       const row,
+          pixel **           const destarray,
+          struct pm_randSt * const randStP) {
+
+    unsigned int col;
+
+    for (col = 0; col < cols; ++col) {
+        pixel const p = srcarray[row][col];
+
+        int const xdis = (pm_rand(randStP) % (spread + 1) )
+            - ((spread + 1) / 2);
+        int const ydis = (pm_rand(randStP) % (spread + 1))
+            - ((spread + 1) / 2);
+
+        int const xnew = col + xdis;
+        int const ynew = row + ydis;
+
+        /* only set the displaced pixel if it's within the bounds
+           of the image
+        */
+        if (xnew >= 0 && xnew < cols && ynew >= 0 && ynew < rows) {
+            /* Displacing a pixel is accomplished by swapping it
+               with another pixel in its vicinity.
+            */
+            pixel const p2 = srcarray[ynew][xnew];
+                /* Original value of second pixel */
+
+            /* Set second pixel to new value */
+            PPM_ASSIGN(destarray[ynew][xnew],
+                       PPM_GETR(p), PPM_GETG(p), PPM_GETB(p));
+
+            /* Set first pixel to (old) value of second */
+            PPM_ASSIGN(destarray[row][col],
+                       PPM_GETR(p2), PPM_GETG(p2), PPM_GETB(p2));
+        } else {
+            /* Displaced pixel is out of bounds; leave the old pixel there.
+            */
+            PPM_ASSIGN(destarray[row][col],
+                       PPM_GETR(p), PPM_GETG(p), PPM_GETB(p));
+        }
+    }
+}
+
+
 
 int
-main(int    argc,
-     char * argv[]) {
+main(int          argc,
+     const char * argv[]) {
 
+    struct CmdlineInfo cmdline;
     FILE * ifP;
-    int argn, rows, cols;
+    int rows, cols;
     unsigned int row;
-    pixel ** destarray, ** srcarray;
-    pixel * pP;
-    pixel * pP2;
+    pixel ** destarray;
+    pixel ** srcarray;
     pixval maxval;
-    pixval r1, g1, b1;
-    int amount;
-    const char * const usage = "amount [ppmfile]\n        amount: # of pixels to displace a pixel by at most\n";
-
-    /* parse in 'default' parameters */
-    ppm_init(&argc, argv);
-
-    argn = 1;
-
-    /* parse in amount & seed */
-    if (argn == argc)
-        pm_usage(usage);
-    if (sscanf(argv[argn], "%d", &amount) != 1)
-        pm_usage(usage);
-    if (amount < 0)
-        pm_error("amount should be a positive number");
-    ++argn;
-
-    /* parse in filename (if present, stdin otherwise) */
-    if (argn != argc)
-    {
-        ifP = pm_openr(argv[argn]);
-        ++argn;
-    }
-    else
-        ifP = stdin;
+    struct pm_randSt randSt;
 
-    if (argn != argc)
-        pm_usage(usage);
+    pm_proginit(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilename);
 
     srcarray = ppm_readppm(ifP, &cols, &rows, &maxval);
 
     destarray = ppm_allocarray(cols, rows);
 
+    pm_randinit(&randSt);
+    pm_srand2(&randSt, cmdline.randomseedSpec, cmdline.randomseed);
+
     /* clear out the buffer */
     for (row = 0; row < rows; ++row)
         memset(destarray[row], 0, cols * sizeof(pixel));
 
-    srand(pm_randseed());
-
-    /* start displacing pixels */
+    /* Displace pixels */
     for (row = 0; row < rows; ++row) {
-        unsigned int col;
-        pP = &srcarray[row][0];
-
-        for (col = 0; col < cols; ++col) {
-            int const xdis = (rand() % (amount+1)) - ((amount+1) / 2);
-            int const ydis = (rand() % (amount+1)) - ((amount+1) / 2);
+        spreadRow(srcarray, cols, rows, cmdline.spread, row,
+                  destarray, &randSt);
 
-            int const xnew = col + xdis;
-            int const ynew = row + ydis;
-
-            /* only set the displaced pixel if it's within the bounds
-               of the image
-            */
-            if (xnew >= 0 && xnew < cols && ynew >= 0 && ynew < rows) {
-                /* displacing a pixel is accomplished by swapping it
-                   with another pixel in its vicinity - so, first
-                   store other pixel's RGB
-                */
-                pP2 = &srcarray[ynew][xnew];
-                r1 = PPM_GETR(*pP2);
-                g1 = PPM_GETG(*pP2);
-                b1 = PPM_GETB(*pP2);
-                /* set second pixel to new value */
-                pP2 = &destarray[ynew][xnew];
-                PPM_ASSIGN(*pP2, PPM_GETR(*pP), PPM_GETG(*pP), PPM_GETB(*pP));
-                
-                /* now, set first pixel to (old) value of second */
-                pP2 = &destarray[row][col];
-                PPM_ASSIGN(*pP2, r1, g1, b1);
-            } else {
-                /* displaced pixel is out of bounds; leave the old
-                   pixel there
-                */
-                pP2 = &destarray[row][col];
-                PPM_ASSIGN(*pP2, PPM_GETR(*pP), PPM_GETG(*pP), PPM_GETB(*pP));
-            }
-            ++pP;
-        }
     }
+    pm_randterm(&randSt);
 
     ppm_writeppm(stdout, destarray, cols, rows, maxval, 0);
 
@@ -115,3 +171,5 @@ main(int    argc,
 
     return 0;
 }
+
+
diff --git a/generator/pamcrater.c b/generator/pamcrater.c
index 43c27dbc..b8ceafa5 100644
--- a/generator/pamcrater.c
+++ b/generator/pamcrater.c
@@ -48,6 +48,7 @@
 
 #include "pm_c_util.h"
 #include "mallocvar.h"
+#include "rand.h"
 #include "shhopt.h"
 #include "nstring.h"
 #include "pam.h"
@@ -169,11 +170,12 @@ static double const DepthBias2  = 0.5;      /* Square of depth bias */
 
 
 static double const
-cast(double const high) {
+cast(double             const high,
+     struct pm_randSt * const randStP) {
 /*----------------------------------------------------------------------------
    A random number in the range [0, 'high'].
 -----------------------------------------------------------------------------*/
-    return high * ((rand() & 0x7FFF) / arand);
+    return high * ((pm_rand(randStP) & 0x7FFF) / arand);
 }
 
 
@@ -252,11 +254,12 @@ setElev(struct pam * const pamP,
 
 
 static void
-smallCrater(struct pam * const pamP,
-            tuple **     const terrain,
-            int          const cx,
-            int          const cy,
-            double       const radius) {
+smallCrater(struct pam *       const pamP,
+            tuple **           const terrain,
+            int                const cx,
+            int                const cy,
+            double             const radius,
+            struct pm_randSt * const randStP) {
 /*----------------------------------------------------------------------------
    Generate a crater with a special method for tiny craters.
 
@@ -283,10 +286,10 @@ smallCrater(struct pam * const pamP,
             /* The mean elevation of the Moore neighborhood (9 pixels
                centered on the crater location).
             */
-        
+
         /* Perturb the mean elevation by a small random factor. */
 
-        int const x = radius >= 1 ? ((rand() >> 8) & 0x3) - 1 : 0;
+        int const x = radius >= 1 ? ((pm_rand(randStP) >> 8) & 0x3) - 1 : 0;
 
         assert(axelev > 0);
 
@@ -374,7 +377,7 @@ normalCrater(struct pam * const pamP,
                 av = (axelev + cz) * (1 - roll) +
                     (terrainMod(pamP, terrain, x, y) + cz) * roll;
                 av = MAX(1000, MIN(64000, av));
-                
+
                 setElev(pamP, terrain, x, y, av);
             }
         }
@@ -388,19 +391,20 @@ normalCrater(struct pam * const pamP,
 
 
 static void
-plopCrater(struct pam * const pamP,
-           tuple **     const terrain,
-           int          const cx,
-           int          const cy,
-           double       const radius,
-           bool         const verbose) {
+plopCrater(struct pam *       const pamP,
+           tuple **           const terrain,
+           int                const cx,
+           int                const cy,
+           double             const radius,
+           bool               const verbose,
+           struct pm_randSt * const randStP) {
 
     if (verbose && pm_have_float_format())
         pm_message("Plopping crater at (%4d, %4d) with radius %g",
                    cx, cy, radius);
 
     if (radius < 3)
-        smallCrater (pamP, terrain, cx, cy, radius);
+        smallCrater (pamP, terrain, cx, cy, radius, randStP);
     else
         normalCrater(pamP, terrain, cx, cy, radius);
 }
@@ -448,6 +452,7 @@ genCraters(struct CmdlineInfo const cmdline) {
 -----------------------------------------------------------------------------*/
     tuple ** terrain;    /* elevation array */
     struct pam pam;
+    struct pm_randSt randSt;
 
     /* Allocate the elevation array and initialize it to mean surface
        elevation.
@@ -455,18 +460,22 @@ genCraters(struct CmdlineInfo const cmdline) {
 
     initCanvas(cmdline.width, cmdline.height, &pam, &terrain);
 
+    pm_randinit(&randSt);
+    pm_srand2(&randSt, cmdline.randomseedSpec, cmdline.randomseed);
+
     if (cmdline.test)
         plopCrater(&pam, terrain,
                    pam.width/2 + cmdline.offset,
                    pam.height/2 + cmdline.offset,
-                   (double) cmdline.radius, cmdline.verbose);
+                   (double) cmdline.radius, cmdline.verbose,
+                   &randSt);
     else {
         unsigned int const ncraters = cmdline.number; /* num of craters */
         unsigned int l;
 
         for (l = 0; l < ncraters; ++l) {
-            int const cx = cast((double) pam.width  - 1);
-            int const cy = cast((double) pam.height - 1);
+            int const cx = cast((double) pam.width  - 1, &randSt);
+            int const cy = cast((double) pam.height - 1, &randSt);
 
             /* Thanks, Rudy, for this equation that maps the uniformly
                distributed numbers from cast() into an area-law distribution
@@ -475,9 +484,11 @@ genCraters(struct CmdlineInfo const cmdline) {
                Produces values within the interval:
                0.56419 <= radius <= 56.419
             */
-            double const radius = sqrt(1 / (M_PI * (1 - cast(0.9999))));
+            double const radius =
+                sqrt(1 / (M_PI * (1 - cast(0.9999, &randSt))));
 
-            plopCrater(&pam, terrain, cx, cy, radius, cmdline.verbose);
+            plopCrater(&pam, terrain, cx, cy, radius,
+                       cmdline.verbose, &randSt);
 
             if (((l + 1) % 100000) == 0)
                 pm_message("%u craters generated of %u (%u%% done)",
@@ -485,6 +496,8 @@ genCraters(struct CmdlineInfo const cmdline) {
         }
     }
 
+    pm_randterm(&randSt);
+
     pnm_writepam(&pam, terrain);
 
     pnm_freepamarray(terrain, &pam);
@@ -503,12 +516,9 @@ main(int argc, const char ** argv) {
 
     parseCommandLine(argc, argv, &cmdline);
 
-    srand(cmdline.randomseedSpec ? cmdline.randomseed : pm_randseed());
-
     genCraters(cmdline);
 
     return 0;
 }
 
 
-
diff --git a/generator/pamstereogram.c b/generator/pamstereogram.c
index 9b861b51..6e64b127 100644
--- a/generator/pamstereogram.c
+++ b/generator/pamstereogram.c
@@ -16,7 +16,7 @@
  *
  * ----------------------------------------------------------------------
  *
- * Copyright (C) 2006-2020 Scott Pakin <scott+pbm@pakin.org>
+ * Copyright (C) 2006-2021 Scott Pakin <scott+pbm@pakin.org>
  *
  * All rights reserved.
  *
@@ -58,11 +58,12 @@
 #include "pm_c_util.h"
 #include "mallocvar.h"
 #include "nstring.h"
+#include "rand.h"
 #include "shhopt.h"
 #include "pam.h"
 
 
-enum outputType {OUTPUT_BW, OUTPUT_GRAYSCALE, OUTPUT_COLOR};
+enum OutputType {OUTPUT_BW, OUTPUT_GRAYSCALE, OUTPUT_COLOR};
 
 /* ---------------------------------------------------------------------- */
 
@@ -85,13 +86,14 @@ struct cmdlineInfo {
     unsigned int magnifypat;     /* -magnifypat option */
     int xshift;                  /* -xshift option */
     int yshift;                  /* -yshift option */
+    int yfillshift;              /* -yfillshift option */
     const char * patfile;        /* -patfile option.  Null if none */
     const char * texfile;        /* -texfile option.  Null if none */
     const char * bgcolor;        /* -bgcolor option */
     unsigned int smoothing;      /* -smoothing option */
     unsigned int randomseed;     /* -randomseed option */
     unsigned int randomseedSpec; /* -randomseed option count */
-    enum outputType outputType;  /* Type of output file */
+    enum OutputType outputType;  /* Type of output file */
     unsigned int xbegin;         /* -xbegin option */
     unsigned int xbeginSpec;     /* -xbegin option count */
     unsigned int tileable;       /* -tileable option */
@@ -153,8 +155,8 @@ parseCommandLine(int                  argc,
     unsigned int option_def_index;
 
     unsigned int patfileSpec, texfileSpec, dpiSpec, eyesepSpec, depthSpec,
-        guidesizeSpec, magnifypatSpec, xshiftSpec, yshiftSpec,
-      bgcolorSpec, smoothingSpec, planesSpec;
+        guidesizeSpec, magnifypatSpec, xshiftSpec, yshiftSpec, yfillshiftSpec,
+        bgcolorSpec, smoothingSpec, planesSpec;
 
     unsigned int blackandwhite, grayscale, color;
     const char ** planes;
@@ -194,6 +196,8 @@ parseCommandLine(int                  argc,
             &xshiftSpec,              0);
     OPTENT3(0, "yshift",          OPT_INT,    &cmdlineP->yshift,
             &yshiftSpec,              0);
+    OPTENT3(0, "yfillshift",      OPT_INT,    &cmdlineP->yfillshift,
+            &yfillshiftSpec,          0);
     OPTENT3(0, "patfile",         OPT_STRING, &cmdlineP->patfile,
             &patfileSpec,             0);
     OPTENT3(0, "texfile",         OPT_STRING, &cmdlineP->texfile,
@@ -282,9 +286,10 @@ parseCommandLine(int                  argc,
 
     if (!xshiftSpec)
         cmdlineP->xshift = 0;
-
     if (!yshiftSpec)
         cmdlineP->yshift = 0;
+    if (!yfillshiftSpec)
+        cmdlineP->yfillshift = 0;
 
     if (xshiftSpec && !cmdlineP->patfile)
         pm_error("-xshift is valid only with -patfile");
@@ -402,11 +407,12 @@ typedef struct outGenerator {
 
 
 
-struct randomState {
+struct RandomState {
     /* The state of a randomColor generator. */
     unsigned int magnifypat;
     tuple *      currentRow;
     unsigned int prevy;
+    struct pm_randSt * randStP;
 };
 
 
@@ -421,7 +427,7 @@ randomColor(outGenerator * const outGenP,
 /*----------------------------------------------------------------------------
    Return a random RGB value.
 -----------------------------------------------------------------------------*/
-    struct randomState * const stateP = outGenP->stateP;
+    struct RandomState * const stateP = outGenP->stateP;
 
     /* Every time we start a new row, we select a new sequence of random
        colors.
@@ -436,7 +442,7 @@ randomColor(outGenerator * const outGenP,
             unsigned int plane;
 
             for (plane = 0; plane < outGenP->pam.depth; ++plane) {
-                unsigned int const randval = rand();
+                unsigned int const randval = pm_rand(stateP->randStP);
                 thisTuple[plane] = randval % modulus;
             }
         }
@@ -456,9 +462,13 @@ static outGenStateTerm termRandomColor;
 static void
 termRandomColor(outGenerator * const outGenP) {
 
-    struct randomState * const stateP = outGenP->stateP;
+    struct RandomState * const stateP = outGenP->stateP;
 
     pnm_freepamrow(stateP->currentRow);
+
+    pm_randterm(stateP->randStP);
+
+    free(stateP->randStP);
 }
 
 
@@ -468,7 +478,7 @@ initRandomColor(outGenerator *     const outGenP,
                 const struct pam * const inPamP,
                 struct cmdlineInfo const cmdline) {
 
-    struct randomState * stateP;
+    struct RandomState * stateP;
 
     outGenP->pam.format      = PAM_FORMAT;
     outGenP->pam.plainformat = 0;
@@ -499,6 +509,10 @@ initRandomColor(outGenerator *     const outGenP,
     stateP->magnifypat = cmdline.magnifypat;
     stateP->prevy      = (unsigned int)(-cmdline.magnifypat);
 
+    MALLOCVAR_NOFAIL(stateP->randStP);
+    pm_randinit(stateP->randStP);
+    pm_srand2(stateP->randStP, cmdline.randomseedSpec, cmdline.randomseed);
+
     outGenP->stateP         = stateP;
     outGenP->getTuple       = &randomColor;
     outGenP->terminateState = &termRandomColor;
@@ -1178,6 +1192,8 @@ static void
 makeImageRow(outGenerator *       const outGenP,
              unsigned int         const row,
              unsigned int         const xbegin,
+             unsigned int         const farWidth,
+             int                  const yfillshift,
              const unsigned int * const sameL,
              const unsigned int * const sameR,
              const tuple *        const outRow) {
@@ -1200,20 +1216,26 @@ makeImageRow(outGenerator *       const outGenP,
 
   sameL[N] > N is not allowed.
 -----------------------------------------------------------------------------*/
-    int col;
+    unsigned int const width  = outGenP->pam.width;
+    unsigned int const height = outGenP->pam.height;
+
+    unsigned int col;
     int lastLinked;
 
-    for (col = (int)xbegin, lastLinked = INT_MIN;
-         col < outGenP->pam.width;
-         ++col) {
+    for (col = xbegin, lastLinked = -1; col < width; ++col) {
 
         tuple newtuple;
 
-        if (sameL[col] == col || sameL[col] < (int)xbegin) {
+        if (sameL[col] == col || sameL[col] < xbegin) {
             if (lastLinked == col - 1)
                 newtuple = outRow[col - 1];
-            else
-                newtuple = outGenP->getTuple(outGenP, col, row);
+            else {
+                if (col < xbegin + farWidth)
+                    newtuple = outGenP->getTuple(outGenP, col, row);
+                else
+                    newtuple = outGenP->getTuple(
+                        outGenP, col, (row + height - yfillshift) % height);
+            }
         } else {
           newtuple = outRow[sameL[col]];
           lastLinked = col;
@@ -1222,20 +1244,25 @@ makeImageRow(outGenerator *       const outGenP,
         pnm_assigntuple(&outGenP->pam, outRow[col], newtuple);
     }
 
-    for (col = (int)xbegin - 1, lastLinked = INT_MIN; col >= 0; --col) {
+    for (col = xbegin, lastLinked = -1; col > 0; --col) {
         tuple newtuple;
 
-        if (sameR[col] == col) {
-            if (lastLinked == col + 1)
-                newtuple = outRow[col + 1];
-            else
-                newtuple = outGenP->getTuple(outGenP, col, row);
+        if (sameR[col-1] == col-1) {
+            if (lastLinked == col)
+                newtuple = outRow[col];
+            else {
+                if (col > xbegin - farWidth)
+                    newtuple = outGenP->getTuple(outGenP, col-1, row);
+                else
+                    newtuple = outGenP->getTuple(
+                        outGenP, col-1, (row + height - yfillshift) % height);
+            }
         } else {
-            newtuple = outRow[sameR[col]];
-            lastLinked = col;
+            newtuple = outRow[sameR[col-1]];
+            lastLinked = col-1;
                 /* Keep track of the last pixel to be constrained. */
         }
-        pnm_assigntuple(&outGenP->pam, outRow[col], newtuple);
+        pnm_assigntuple(&outGenP->pam, outRow[col-1], newtuple);
     }
 }
 
@@ -1261,6 +1288,8 @@ makeOneImageRow(unsigned int         const row,
                 const unsigned int * const sameR,
                 unsigned int *       const colNumBuffer,
                 unsigned int         const xbegin,
+                unsigned int         const farWidth,
+                int                  const yfillshift,
                 tuple *              const rowBuffer,
                 tuple *              const outRow) {
 
@@ -1272,7 +1301,7 @@ makeOneImageRow(unsigned int         const row,
                             rowBuffer, outRow);
         else
             makeImageRow(outputGeneratorP, row,
-                         xbegin, sameL, sameR, outRow);
+                         xbegin, farWidth, yfillshift, sameL, sameR, outRow);
     }
 }
 
@@ -1284,6 +1313,8 @@ constructRowTileable(const struct pam *   const inPamP,
                      bool                 const makeMask,
                      const unsigned int * const sameL,
                      const unsigned int * const sameR,
+                     unsigned int         const farWidth,
+                     int                  const yfillshift,
                      unsigned int         const row,
                      tuple *              const outRow,
                      tuple *              const outRowBuf1,
@@ -1300,10 +1331,10 @@ constructRowTileable(const struct pam *   const inPamP,
        xbegin=maximum case.
     */
     makeOneImageRow(row, outputGeneratorP, makeMask, sameL, sameR, colNumBuf2,
-                    0, outRowBuf2, outRow);
+                    farWidth, yfillshift, 0, outRowBuf2, outRow);
 
     makeOneImageRow(row, outputGeneratorP, makeMask, sameL, sameR, colNumBuf2,
-                    inPamP->width - 1, outRowBuf2, outRowMax);
+                    farWidth, yfillshift, inPamP->width - 1, outRowBuf2, outRowMax);
 
     for (col = 0; col < inPamP->width; ++col) {
         unsigned int plane;
@@ -1362,6 +1393,8 @@ doRow(const struct pam * const inPamP,
       unsigned int       const magnifypat,
       unsigned int       const smoothing,
       unsigned int       const xbegin,
+      unsigned int       const farWidth,
+      int                const yfillshift,
       unsigned int       const row,
       tuple *            const inRow,
       tuple *            const outRowBuf0,
@@ -1397,13 +1430,16 @@ doRow(const struct pam * const inPamP,
     /* Construct a single row. */
     if (tileable) {
         constructRowTileable(inPamP, outputGeneratorP, makeMask,
-                             sameL, sameR, row, outRow,
+                             sameL, sameR,
+                             farWidth, yfillshift,
+                             row, outRow,
                              outRowBuf1, outRowBuf2, colNumBuf2);
 
     } else {
         makeOneImageRow(row, outputGeneratorP, makeMask,
                         sameL, sameR, colNumBuf2,
-                        xbegin, outRowBuf1, outRow);
+                        xbegin, farWidth, yfillshift,
+                        outRowBuf1, outRow);
     }
 
     /* Write the resulting row. */
@@ -1423,7 +1459,8 @@ makeImageRows(const struct pam * const inPamP,
               bool               const tileable,
               unsigned int       const magnifypat,
               unsigned int       const smoothing,
-              unsigned int       const xbegin) {
+              unsigned int       const xbegin,
+              int                const yfillshift) {
 
     tuple * inRow;    /* Buffer for use in reading from the height-map image */
     tuple * outRowBuf0; /* Buffer for use in generating output rows */
@@ -1433,6 +1470,8 @@ makeImageRows(const struct pam * const inPamP,
     unsigned int * colNumBuf1;
     unsigned int * colNumBuf2;
     unsigned int row;      /* Current row in the input and output files */
+    unsigned int pixelEyesep;
+    unsigned int farWidth;
 
     inRow = pnm_allocpamrow(inPamP);
     outRowBuf0 = pnm_allocpamrow(&outputGeneratorP->pam);
@@ -1451,10 +1490,13 @@ makeImageRows(const struct pam * const inPamP,
         pm_error("Unable to allocate space for %u column buffer",
                  inPamP->width);
 
+    pixelEyesep = ROUNDU(eyesep * dpi);
+    farWidth = pixelEyesep/(magnifypat * 2);
+
     for (row = 0; row < inPamP->height; ++row) {
         doRow(inPamP, outputGeneratorP,  depthOfField, eyesep, dpi,
               crossEyed, makeMask, tileable, magnifypat, smoothing, xbegin,
-              row,
+              farWidth, yfillshift, row,
               inRow, outRowBuf0, outRowBuf1, outRowBuf2,
               colNumBuf0, colNumBuf1, colNumBuf2);
     }
@@ -1510,7 +1552,8 @@ produceStereogram(FILE *             const ifP,
     makeImageRows(&inPam, outputGeneratorP,
                   cmdline.depth, cmdline.eyesep, cmdline.dpi,
                   cmdline.crosseyed, cmdline.makemask, cmdline.tileable,
-                  cmdline.magnifypat, cmdline.smoothing, xbegin);
+                  cmdline.magnifypat, cmdline.smoothing, xbegin,
+                  cmdline.yfillshift);
 
     if (cmdline.guidebottom)
         drawguides(cmdline.guidesize, &outputGeneratorP->pam,
@@ -1567,8 +1610,6 @@ main(int argc, const char *argv[]) {
     if (cmdline.verbose)
         reportParameters(cmdline);
 
-    srand(cmdline.randomseedSpec ? cmdline.randomseed : pm_randseed());
-
     ifP = pm_openr(cmdline.inputFilespec);
 
     /* Produce a stereogram. */
@@ -1580,4 +1621,3 @@ main(int argc, const char *argv[]) {
 }
 
 
-
diff --git a/generator/pgmcrater b/generator/pgmcrater
index c66f5576..6d81df39 100755
--- a/generator/pgmcrater
+++ b/generator/pgmcrater
@@ -36,6 +36,16 @@ exec perl -w -x -S -- "$0" "$@"
 use strict;
 
 use Getopt::Long;
+use IO::Handle;
+
+
+
+sub pm_message($) {
+    STDERR->print("pgmcrater: $_[0]\n");
+}
+
+
+
 
 sub doVersionHack($) {
     my ($argvR) = @_;
@@ -69,7 +79,7 @@ my $validOptions = GetOptions(
     'randomseed=i' => \my $randomseedOpt);
 
 if (!$validOptions) {
-    print STDERR "Invalid syntax\n";
+    pm_message("Invalid syntax");
     exit(100);
 }
 
diff --git a/generator/pgmnoise.c b/generator/pgmnoise.c
index e3e4eca6..ea35956b 100644
--- a/generator/pgmnoise.c
+++ b/generator/pgmnoise.c
@@ -7,12 +7,17 @@
 #include "pm_c_util.h"
 #include "mallocvar.h"
 #include "nstring.h"
+#include "rand.h"
 #include "shhopt.h"
 #include "pgm.h"
 
+/* constants */
+static unsigned long int const ceil31bits = 0x7fffffffUL;
+static unsigned long int const ceil32bits = 0xffffffffUL;
 
 
-struct cmdlineInfo {
+
+struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
@@ -21,13 +26,15 @@ struct cmdlineInfo {
     unsigned int maxval;
     unsigned int randomseed;
     unsigned int randomseedSpec;
+    unsigned int verbose;
 };
 
 
 
 static void
-parseCommandLine(int argc, const char ** const argv,
-                 struct cmdlineInfo * const cmdlineP) {
+parseCommandLine(int argc,
+                 const char **        const argv,
+                 struct CmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
    Note that the file spec array we return is stored in the storage that
    was passed to us as the argv array.
@@ -46,6 +53,8 @@ parseCommandLine(int argc, const char ** const argv,
             &cmdlineP->randomseedSpec,      0);
     OPTENT3(0,   "maxval",       OPT_UINT,    &cmdlineP->maxval,
             &maxvalSpec,                    0);
+    OPTENT3(0,   "verbose",      OPT_FLAG,    NULL,
+            &cmdlineP->verbose,             0);
 
     opt.opt_table = option_def;
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
@@ -57,7 +66,7 @@ parseCommandLine(int argc, const char ** const argv,
 
     if (maxvalSpec) {
         if (cmdlineP->maxval > PGM_OVERALLMAXVAL)
-            pm_error("Maxval too large: %u.  Maximu is %u",
+            pm_error("Maxval too large: %u.  Maximum is %u",
                      cmdlineP->maxval, PGM_OVERALLMAXVAL);
         else if (cmdlineP->maxval == 0)
             pm_error("Maxval must not be zero");
@@ -95,38 +104,43 @@ parseCommandLine(int argc, const char ** const argv,
 
 
 static unsigned int
-randPool(unsigned int const digits) {
+randPool(unsigned int       const nDigits,
+         struct pm_randSt * const randStP) {
 /*----------------------------------------------------------------------------
-  Draw 'digits' bits from pool of random bits.  If the number of random bits
-  in pool is insufficient, call rand() and add 31 bits to it.
+  Draw 'nDigits' bits from pool of random bits.  If the number of random bits
+  in pool is insufficient, call pm_rand() and add N bits to it.
+
+  N is 31 or 32.  In raw mode we use N = 32 regardless of the actual number of
+  available bits.  If there are only 31 available, we use zero for the MSB.
 
-  'digits' must be at most 16.
+  'nDigits' must be at most 16.
 
-  We assume that each call to rand() generates 31 bits, or RAND_MAX ==
-  2147483647.
+  We assume that each call to pm_rand() generates 31 or 32 bits, or
+  randStP->max == 2147483647 or 4294967295.
 
-  The underlying logic is flexible and endian-free.  The above conditions
-  can be relaxed.
+  The underlying logic is flexible and endian-free.  The above conditions can
+  be relaxed.
 -----------------------------------------------------------------------------*/
     static unsigned long int hold=0;  /* entropy pool */
     static unsigned int len=0;        /* number of valid bits in pool */
 
-    unsigned int const mask = (1 << digits) - 1;
-
+    unsigned int const mask = (1 << nDigits) - 1;
+    unsigned int const randbits = (randStP->max == ceil31bits) ? 31 : 32;
     unsigned int retval;
 
-    assert(RAND_MAX == 2147483647 && digits <= 16);
+    assert(randStP->max == ceil31bits || randStP->max == ceil32bits);
+    assert(nDigits <= 16);
 
     retval = hold;  /* initial value */
 
-    if (len > digits) { /* Enough bits in hold to satisfy request */
-        hold >>= digits;
-        len   -= digits;
-    } else {              /* Load another 31 bits into hold */
-        hold    = rand();
+    if (len > nDigits) { /* Enough bits in hold to satisfy request */
+        hold >>= nDigits;
+        len   -= nDigits;
+    } else {            /* Load another 31 or 32 bits into hold */
+        hold    = pm_rand(randStP);
         retval |= (hold << len);
-        hold >>=  (digits - len);
-        len = 31 - digits + len;
+        hold >>=  (nDigits - len);
+        len = randbits - nDigits + len;
     }
     return (retval & mask);
 }
@@ -134,36 +148,61 @@ randPool(unsigned int const digits) {
 
 
 static void
-pgmnoise(FILE *       const ofP,
-         unsigned int const cols,
-         unsigned int const rows,
-         gray         const maxval) {
+reportVerbose(struct pm_randSt * const randStP,
+              gray               const maxval,
+              bool               const usingPool)  {
+
+    pm_message("random seed: %u", randStP->seed);
+    pm_message("random max: %u maxval: %u", randStP->max, maxval);
+    pm_message("method: %s", usingPool ? "pool" : "modulo");
+}
+
+
 
-    bool const usingPool = !(RAND_MAX==2147483647 && (maxval & (maxval+1)));
+static void
+pgmnoise(FILE *             const ofP,
+         unsigned int       const cols,
+         unsigned int       const rows,
+         gray               const maxval,
+         bool               const verbose,
+         struct pm_randSt * const randStP) {
+
+    bool const usingPool =
+        (randStP->max==ceil31bits || randStP->max==ceil32bits) &&
+        !(maxval & (maxval+1));
     unsigned int const bitLen = pm_maxvaltobits(maxval);
 
     unsigned int row;
     gray * destrow;
 
     /* If maxval is 2^n-1, we draw exactly n bits from the pool.
-       Otherwise call rand() and determine gray value by modulo.
+       Otherwise call pm_rand() and determine gray value by modulo.
 
        In the latter case, there is a minuscule skew toward 0 (=black)
        because smaller numbers are produced more frequently by modulo.
        Thus we employ the pool method only when it is certain that no
-       skew will ensue.
+       skew will result.
 
        To illustrate the point, consider converting the outcome of one
        roll of a fair, six-sided die to 5 values (0 to 4) by N % 5.  The
-       probability for values 1, 2, 3, 4 are 1/6, but 0 alone is 2/6.
+       probability for values 1, 2, 3, 4 is 1/6, but 0 alone is 2/6.
        Average is 10/6 or 1.6667, compared to 2.0 from an ideal
        generator which produces exactly 5 values.  With two dice
        average improves to 70/36 or 1.9444.
 
        The more (distinct) dice we roll, or the more binary digits we
        draw, the smaller the skew.
+
+       The pool method is economical.  But there is an additional merit:
+       No bits are lost this way.  This gives us a means to check the
+       integrity of the random number generator.
+
+       - Akira Urushibata, March 2021
     */
 
+    if (verbose)
+        reportVerbose(randStP, maxval, usingPool);
+
     destrow = pgm_allocrow(cols);
 
     pgm_writepgminit(ofP, cols, rows, maxval, 0);
@@ -172,12 +211,11 @@ pgmnoise(FILE *       const ofP,
         if (usingPool) {
             unsigned int col;
             for (col = 0; col < cols; ++col)
-                destrow[col] = randPool(bitLen);
-        }
-        else {
+                destrow[col] = randPool(bitLen, randStP);
+        } else {
             unsigned int col;
             for (col = 0; col < cols; ++col)
-                destrow[col] = rand() % (maxval + 1);
+                destrow[col] = pm_rand(randStP) % (maxval + 1);
         }
         pgm_writepgmrow(ofP, destrow, cols, maxval, 0);
     }
@@ -191,18 +229,22 @@ int
 main(int          argc,
      const char * argv[]) {
 
-    struct cmdlineInfo cmdline;
+    struct CmdlineInfo cmdline;
+    struct pm_randSt randSt;
 
     pm_proginit(&argc, argv);
 
     parseCommandLine(argc, argv, &cmdline);
 
-    srand(cmdline.randomseedSpec ? cmdline.randomseed : pm_randseed());
+    pm_randinit(&randSt);
+    pm_srand2(&randSt, cmdline.randomseedSpec, cmdline.randomseed);
+
+    pgmnoise(stdout, cmdline.width, cmdline.height, cmdline.maxval,
+             cmdline.verbose, &randSt);
 
-    pgmnoise(stdout, cmdline.width, cmdline.height, cmdline.maxval);
+    pm_randterm(&randSt);
 
     return 0;
 }
 
 
-
diff --git a/generator/ppmforge.c b/generator/ppmforge.c
index 114f7f18..7cef571f 100644
--- a/generator/ppmforge.c
+++ b/generator/ppmforge.c
@@ -34,12 +34,14 @@
 #define _XOPEN_SOURCE 500  /* get M_PI in math.h */
 
 #include <math.h>
+#include <float.h>
 #include <assert.h>
 
 #include "pm_c_util.h"
-#include "ppm.h"
 #include "mallocvar.h"
+#include "rand.h"
 #include "shhopt.h"
+#include "ppm.h"
 
 static double const hugeVal = 1e50;
 
@@ -59,12 +61,8 @@ typedef struct {
 
 /* Definition for obtaining random numbers. */
 
-#define nrand 4               /* Gauss() sample count */
-#define Cast(low, high) ((low)+(((high)-(low)) * ((rand() & 0x7FFF) / arand)))
-
 /*  Local variables  */
 
-static double arand, gaussadd, gaussfac; /* Gaussian random parameters */
 static double fracdim;            /* Fractal dimension */
 static double powscale;           /* Power law scaling exponent */
 static int meshsize = 256;        /* FFT mesh size */
@@ -172,6 +170,11 @@ parseCommandLine(int argc, const char **argv,
         if (cmdlineP->dimension <= 0.0)
             pm_error("-dimension must be greater than zero.  "
                      "You specified %f", cmdlineP->dimension);
+        else if (cmdlineP->dimension > 5.0 + FLT_EPSILON)
+            pm_error("-dimension must not be greater than 5.  "
+                     "Results are not interesting with higher numbers, so "
+                     "we assume it is a mistake.  "
+                     "You specified %f", cmdlineP->dimension);
     } else
         cmdlineP->dimension = cmdlineP->clouds ? 2.15 : 2.4;
 
@@ -335,45 +338,73 @@ fourn(float *     const data,
 #undef SWAP
 
 
+struct Gauss {
+    struct pm_randSt randSt;
+    unsigned int     nrand;          /* Gauss() sample count */
+    double           arand;
+    double           gaussadd;
+    double           gaussfac;
+};
+
+
 
 static void
-initgauss(unsigned int const seed) {
+initgauss(struct Gauss * const gaussP,
+          unsigned int   const seed) {
 /*----------------------------------------------------------------------------
   Initialize random number generators.
 
   As given in Peitgen & Saupe, page 77.
 -----------------------------------------------------------------------------*/
+    gaussP->nrand = 4;
+
     /* Range of random generator */
-    arand = pow(2.0, 15.0) - 1.0;
-    gaussadd = sqrt(3.0 * nrand);
-    gaussfac = 2 * gaussadd / (nrand * arand);
-    srand(seed);
+    gaussP->arand    = pow(2.0, 15.0) - 1.0;
+    gaussP->gaussadd = sqrt(3.0 * gaussP->nrand);
+    gaussP->gaussfac = 2 * gaussP->gaussadd / (gaussP->nrand * gaussP->arand);
+
+    pm_randinit(&gaussP->randSt);
+    pm_srand(&gaussP->randSt, seed);
 }
 
 
 
 static double
-gauss() {
+gauss(struct Gauss * const gaussP) {
 /*----------------------------------------------------------------------------
   A Gaussian random number.
 
   As given in Peitgen & Saupe, page 77.
 -----------------------------------------------------------------------------*/
-    int i;
+    unsigned int i;
     double sum;
 
-    for (i = 1, sum = 0.0; i <= nrand; ++i) {
-        sum += (rand() & 0x7FFF);
+    for (i = 1, sum = 0.0; i <= gaussP->nrand; ++i) {
+        sum += (pm_rand(&gaussP->randSt) & 0x7FFF);
     }
-    return gaussfac * sum - gaussadd;
+    return gaussP->gaussfac * sum - gaussP->gaussadd;
+}
+
+
+
+static double
+cast(double         const low,
+     double         const high,
+     struct Gauss * const gaussP) {
+
+    return
+        low +
+        ((high-low) * ((pm_rand(&gaussP->randSt) & 0x7FFF) / gaussP->arand));
+
 }
 
 
 
 static void
-spectralsynth(float **     const x,
-              unsigned int const n,
-              double        const h) {
+spectralsynth(float **       const x,
+              unsigned int   const n,
+              double         const h,
+              struct Gauss * const gaussP) {
 /*----------------------------------------------------------------------------
   Spectrally synthesized fractal motion in two dimensions.
 
@@ -395,9 +426,10 @@ spectralsynth(float **     const x,
 
     for (i = 0; i <= n / 2; i++) {
         for (j = 0; j <= n / 2; j++) {
-            phase = 2 * M_PI * ((rand() & 0x7FFF) / arand);
+            phase = 2 * M_PI * ((pm_rand(&gaussP->randSt) & 0x7FFF) /
+                                gaussP->arand);
             if (i != 0 || j != 0) {
-                rad = pow((double) (i * i + j * j), -(h + 1) / 2) * gauss();
+                rad = pow((double) (i*i + j*j), -(h + 1) / 2) * gauss(gaussP);
             } else {
                 rad = 0;
             }
@@ -416,8 +448,9 @@ spectralsynth(float **     const x,
     Imag(a, n / 2, n / 2) = 0;
     for (i = 1; i <= n / 2 - 1; i++) {
         for (j = 1; j <= n / 2 - 1; j++) {
-            phase = 2 * M_PI * ((rand() & 0x7FFF) / arand);
-            rad = pow((double) (i * i + j * j), -(h + 1) / 2) * gauss();
+            phase = 2 * M_PI * ((pm_rand(&gaussP->randSt) & 0x7FFF) /
+                                gaussP->arand);
+            rad = pow((double) (i * i + j * j), -(h + 1) / 2) * gauss(gaussP);
             rcos = rad * cos(phase);
             rsin = rad * sin(phase);
             Real(a, i, n - j) = rcos;
@@ -469,22 +502,22 @@ temprgb(double   const temp,
 
 
 static void
-etoile(pixel * const pix) {
+etoile(pixel *        const pix,
+       struct Gauss * const gaussP) {
 /*----------------------------------------------------------------------------
     Set a pixel in the starry sky.
 -----------------------------------------------------------------------------*/
-    if ((rand() % 1000) < starfraction) {
-#define StarQuality 0.5       /* Brightness distribution exponent */
-#define StarIntensity   8         /* Brightness scale factor */
-#define StarTintExp 0.5       /* Tint distribution exponent */
-        double v = StarIntensity * pow(1 / (1 - Cast(0, 0.9999)),
-                                       (double) StarQuality),
-            temp,
-            r, g, b;
-
-        if (v > 255) {
-            v = 255;
-        }
+    if ((pm_rand(&gaussP->randSt) % 1000) < starfraction) {
+        double const starQuality   = 0.5;
+            /* Brightness distribution exponent */
+        double const starIntensity = 8;
+            /* Brightness scale factor */
+        double const starTintExp = 0.5;
+            /* Tint distribution exponent */
+        double const v =
+            MIN(255.0,
+                starIntensity * pow(1 / (1 - cast(0, 0.9999, gaussP)),
+                                    (double) starQuality));
 
         /* We make a special case for star color  of zero in order to
            prevent  floating  point  roundoff  which  would  otherwise
@@ -493,13 +526,17 @@ etoile(pixel * const pix) {
            256 shades in the image. */
 
         if (starcolor == 0) {
-            int vi = v;
+            pixval const vi = v;
 
             PPM_ASSIGN(*pix, vi, vi, vi);
         } else {
+            double temp;
+            double r, g, b;
+
             temp = 5500 + starcolor *
-                pow(1 / (1 - Cast(0, 0.9999)), StarTintExp) *
-                ((rand() & 7) ? -1 : 1);
+                pow(1 / (1 - cast(0, 0.9999, gaussP)), starTintExp) *
+                ((pm_rand(&gaussP->randSt) & 7) ? -1 : 1);
+
             /* Constrain temperature to a reasonable value: >= 2600K
                (S Cephei/R Andromedae), <= 28,000 (Spica). */
             temp = MAX(2600, MIN(28000, temp));
@@ -575,7 +612,8 @@ createPlanetStuff(bool             const clouds,
                   unsigned char ** const cpP,
                   Vector *         const sunvecP,
                   unsigned int     const cols,
-                  pixval           const maxval) {
+                  pixval           const maxval,
+                  struct Gauss *   const gaussP) {
 
     double *u, *u1;
     unsigned int *bxf, *bxc;
@@ -585,8 +623,8 @@ createPlanetStuff(bool             const clouds,
 
     /* Compute incident light direction vector. */
 
-    shang = hourspec ? hourangle : Cast(0, 2 * M_PI);
-    siang = inclspec ? inclangle : Cast(-M_PI * 0.12, M_PI * 0.12);
+    shang = hourspec ? hourangle : cast(0, 2 * M_PI, gaussP);
+    siang = inclspec ? inclangle : cast(-M_PI * 0.12, M_PI * 0.12, gaussP);
 
     sunvecP->x = sin(shang) * cos(siang);
     sunvecP->y = sin(siang);
@@ -594,7 +632,7 @@ createPlanetStuff(bool             const clouds,
 
     /* Allow only 25% of random pictures to be crescents */
 
-    if (!hourspec && ((rand() % 100) < 75)) {
+    if (!hourspec && ((pm_rand(&gaussP->randSt) % 100) < 75)) {
         flipped = (sunvecP->z < 0);
         sunvecP->z = fabs(sunvecP->z);
     } else
@@ -634,7 +672,7 @@ createPlanetStuff(bool             const clouds,
         pm_error("Cannot allocate %u element interpolation tables.", cols);
     {
         unsigned int j;
-        for (j = 0; j < cols; j++) {
+        for (j = 0; j < cols; ++j) {
             double const bx = (n - 1) * uprj(j, cols);
 
             bxf[j] = floor(bx);
@@ -651,8 +689,9 @@ createPlanetStuff(bool             const clouds,
 
 
 static void
-generateStarrySkyRow(pixel *      const pixels,
-                     unsigned int const cols) {
+generateStarrySkyRow(pixel *        const pixels,
+                     unsigned int   const cols,
+                     struct Gauss * const gaussP) {
 /*----------------------------------------------------------------------------
   Generate a starry sky.  Note  that no FFT is performed;
   the output is  generated  directly  from  a  power  law
@@ -660,8 +699,8 @@ generateStarrySkyRow(pixel *      const pixels,
 -----------------------------------------------------------------------------*/
     unsigned int j;
 
-    for (j = 0; j < cols; j++)
-        etoile(pixels + j);
+    for (j = 0; j < cols; ++j)
+        etoile(pixels + j, gaussP);
 }
 
 
@@ -881,7 +920,8 @@ generatePlanetRow(pixel *         const pixelrow,
                   int             const byc,
                   int             const byf,
                   Vector          const sunvec,
-                  pixval          const maxval) {
+                  pixval          const maxval,
+                  struct Gauss *  const gaussP) {
 
     unsigned int const StarClose = 2;
 
@@ -924,24 +964,25 @@ generatePlanetRow(pixel *         const pixelrow,
     /* Left stars */
 
     for (col = 0; (int)col < (int)(cols/2 - (lcos + StarClose)); ++col)
-        etoile(&pixelrow[col]);
+        etoile(&pixelrow[col], gaussP);
 
     /* Right stars */
 
     for (col = cols/2 + (lcos + StarClose); col < cols; ++col)
-        etoile(&pixelrow[col]);
+        etoile(&pixelrow[col], gaussP);
 }
 
 
 
 static void
-genplanet(bool         const stars,
-          bool         const clouds,
-          float *      const a,
-          unsigned int const cols,
-          unsigned int const rows,
-          unsigned int const n,
-          unsigned int const rseed) {
+genplanet(bool           const stars,
+          bool           const clouds,
+          float *        const a,
+          unsigned int   const cols,
+          unsigned int   const rows,
+          unsigned int   const n,
+          unsigned int   const rseed,
+          struct Gauss * const gaussP) {
 /*----------------------------------------------------------------------------
   Generate planet from elevation array.
 
@@ -971,13 +1012,13 @@ genplanet(bool         const stars,
                    clouds ? "clouds" : "planet",
                    rseed, fracdim, powscale, meshsize);
         createPlanetStuff(clouds, a, n, &u, &u1, &bxf, &bxc, &cp, &sunvec,
-                          cols, maxval);
+                          cols, maxval, gaussP);
     }
 
     pixelrow = ppm_allocrow(cols);
     for (row = 0; row < rows; ++row) {
         if (stars)
-            generateStarrySkyRow(pixelrow, cols);
+            generateStarrySkyRow(pixelrow, cols, gaussP);
         else {
             double const by = (n - 1) * uprj(row, rows);
             int    const byf = floor(by) * n;
@@ -993,7 +1034,8 @@ genplanet(bool         const stars,
                 generatePlanetRow(pixelrow, row, rows, cols,
                                   t, t1, u, u1, cp, bxc, bxf, byc, byf,
                                   sunvec,
-                                  maxval);
+                                  maxval,
+                                  gaussP);
         }
         ppm_writeppmrow(stdout, pixelrow, cols, maxval, FALSE);
     }
@@ -1098,14 +1140,15 @@ planet(unsigned int const cols,
 -----------------------------------------------------------------------------*/
     float * a;
     bool error;
+    struct Gauss gauss;
 
-    initgauss(rseed);
+    initgauss(&gauss, rseed);
 
     if (stars) {
         a = NULL;
         error = FALSE;
     } else {
-        spectralsynth(&a, meshsize, 3.0 - fracdim);
+        spectralsynth(&a, meshsize, 3.0 - fracdim, &gauss);
         if (a == NULL) {
             error = TRUE;
         } else {
@@ -1117,7 +1160,7 @@ planet(unsigned int const cols,
         }
     }
     if (!error)
-        genplanet(stars, clouds, a, cols, rows, meshsize, rseed);
+        genplanet(stars, clouds, a, cols, rows, meshsize, rseed, &gauss);
 
     if (a != NULL)
         free(a);
@@ -1159,10 +1202,10 @@ main(int argc, const char ** argv) {
     cols = (MAX(cmdline.height, cmdline.width) + 1) & (~1);
     rows = cmdline.height;
 
-    success = planet(cols, rows, cmdline.night, cmdline.clouds, cmdline.seed);
+    success = planet(cols, rows, cmdline.night,
+                     cmdline.clouds, cmdline.seed);
 
     exit(success ? 0 : 1);
 }
 
 
-
diff --git a/generator/ppmpat.c b/generator/ppmpat.c
index 908c200f..9473afeb 100644
--- a/generator/ppmpat.c
+++ b/generator/ppmpat.c
@@ -14,7 +14,6 @@
 #define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
                            /* get M_PI in math.h */
 #define _BSD_SOURCE  /* Make sure strdup() is in <string.h> */
-#define SPIROGRAPHS 0   /* Spirograph to be added soon */
 
 #include <assert.h>
 #include <math.h>
@@ -23,8 +22,9 @@
 
 #include "pm_c_util.h"
 #include "mallocvar.h"
-#include "shhopt.h"
 #include "nstring.h"
+#include "rand.h"
+#include "shhopt.h"
 #include "ppm.h"
 #include "ppmdraw.h"
 
@@ -40,9 +40,6 @@ typedef enum {
     PAT_SQUIG,
     PAT_CAMO,
     PAT_ANTICAMO,
-    PAT_SPIRO1,
-    PAT_SPIRO2,
-    PAT_SPIRO3
 } Pattern;
 
 typedef struct {
@@ -80,7 +77,6 @@ validateColorCount(Pattern      const basePattern,
     switch (basePattern) {
     case PAT_GINGHAM2:
     case PAT_ARGYLE1:
-    case PAT_SPIRO1:
         if (colorCount != 2)
             pm_error("Wrong number of colors: %u. "
                      "2 colors are required for the specified pattern.",
@@ -112,8 +108,6 @@ validateColorCount(Pattern      const basePattern,
                      colorCount);
         break;
 
-    case PAT_SPIRO2:
-    case PAT_SPIRO3:
     default:
         pm_error("INTERNAL ERROR.");
     }
@@ -188,9 +182,6 @@ parseCommandLine(int argc, const char ** argv,
     unsigned int squig;
     unsigned int camo;
     unsigned int anticamo;
-    unsigned int spiro1;
-    unsigned int spiro2;
-    unsigned int spiro3;
 
     MALLOCARRAY_NOFAIL(option_def, 100);
 
@@ -219,16 +210,6 @@ parseCommandLine(int argc, const char ** argv,
             &camo,       0);
     OPTENT3(0, "anticamo",      OPT_FLAG,   NULL,
             &anticamo,   0);
-#if SPIROGRAPHS != 0
-    OPTENT3(0, "spiro1",        OPT_FLAG,   NULL,
-            &spiro1,     0);
-    OPTENT3(0, "spiro2",        OPT_FLAG,   NULL,
-            &spiro1,     0);
-    OPTENT3(0, "spiro3",        OPT_FLAG,   NULL,
-            &spiro1,     0);
-#else
-    spiro1 = spiro2 = spiro3 = 0;
-#endif
     OPTENT3(0, "color",         OPT_STRINGLIST, &colorText,
             &cmdlineP->colorSpec,           0);
     OPTENT3(0, "randomseed",    OPT_UINT,       &cmdlineP->randomseed,
@@ -246,8 +227,7 @@ parseCommandLine(int argc, const char ** argv,
         gingham2 + gingham3 + madras + tartan + argyle1 + argyle2 +
         poles +
         squig +
-        camo + anticamo +
-        spiro1 + spiro2 + spiro3;
+        camo + anticamo;
 
     if (basePatternCount < 1)
         pm_error("You must specify a base pattern option such as -gingham2");
@@ -275,12 +255,6 @@ parseCommandLine(int argc, const char ** argv,
             cmdlineP->basePattern = PAT_CAMO;
         else if (anticamo)
             cmdlineP->basePattern = PAT_ANTICAMO;
-        else if (spiro1)
-            cmdlineP->basePattern = PAT_SPIRO1;
-        else if (spiro2)
-            cmdlineP->basePattern = PAT_SPIRO2;
-        else if (spiro3)
-            cmdlineP->basePattern = PAT_SPIRO3;
         else
             assert(false);  /* Every possibility is accounted for */
     }
@@ -339,14 +313,15 @@ validateComputableDimensions(unsigned int const cols,
 
 
 static pixel
-randomColor(pixval const maxval) {
+randomColor(struct pm_randSt * const randStP,
+            pixval             const maxval) {
 
     pixel p;
 
     PPM_ASSIGN(p,
-               rand() % (maxval + 1),
-               rand() % (maxval + 1),
-               rand() % (maxval + 1)
+               pm_rand(randStP) % (maxval + 1),
+               pm_rand(randStP) % (maxval + 1),
+               pm_rand(randStP) % (maxval + 1)
         );
 
     return p;
@@ -354,15 +329,18 @@ randomColor(pixval const maxval) {
 
 
 
-#define DARK_THRESH 0.25
+static double const DARK_THRESH = 0.25;
+
+
 
 static pixel
-randomBrightColor(pixval const maxval) {
+randomBrightColor(struct pm_randSt * const randStP,
+                  pixval             const maxval) {
 
     pixel p;
 
     do {
-        p = randomColor(maxval);
+        p = randomColor(randStP, maxval);
     } while (PPM_LUMIN(p) <= maxval * DARK_THRESH);
 
     return p;
@@ -371,12 +349,13 @@ randomBrightColor(pixval const maxval) {
 
 
 static pixel
-randomDarkColor(pixval const maxval) {
+randomDarkColor(struct pm_randSt * const randStP,
+                pixval             const maxval) {
 
     pixel p;
 
     do {
-        p = randomColor(maxval);
+        p = randomColor(randStP, maxval);
     } while (PPM_LUMIN(p) > maxval * DARK_THRESH);
 
     return p;
@@ -448,7 +427,8 @@ nextColorBg(ColorTable * const colorTableP) {
 
 
 static pixel
-randomAnticamoColor(pixval const maxval) {
+randomAnticamoColor(struct pm_randSt * const randStP,
+                    pixval             const maxval) {
 
     int v1, v2, v3;
     pixel p;
@@ -457,37 +437,49 @@ randomAnticamoColor(pixval const maxval) {
     v2 = (maxval + 1) / 2;
     v3 = 3 * v1;
 
-    switch (rand() % 15) {
+    switch (pm_rand(randStP) % 15) {
     case 0: case 1:
-        PPM_ASSIGN(p, rand() % v1 + v3, rand() % v2, rand() % v2);
+        PPM_ASSIGN(p, pm_rand(randStP) % v1 + v3,
+                      pm_rand(randStP) % v2,
+                      pm_rand(randStP) % v2);
         break;
 
     case 2:
     case 3:
-        PPM_ASSIGN(p, rand() % v2, rand() % v1 + v3, rand() % v2);
+        PPM_ASSIGN(p, pm_rand(randStP) % v2,
+                      pm_rand(randStP) % v1 + v3,
+                      pm_rand(randStP) % v2);
         break;
 
     case 4:
     case 5:
-        PPM_ASSIGN(p, rand() % v2, rand() % v2, rand() % v1 + v3);
+        PPM_ASSIGN(p, pm_rand(randStP) % v2,
+                      pm_rand(randStP) % v2,
+                      pm_rand(randStP) % v1 + v3);
         break;
 
     case 6:
     case 7:
     case 8:
-        PPM_ASSIGN(p, rand() % v2, rand() % v1 + v3, rand() % v1 + v3);
+        PPM_ASSIGN(p, pm_rand(randStP) % v2,
+                      pm_rand(randStP) % v1 + v3,
+                      pm_rand(randStP) % v1 + v3);
         break;
 
     case 9:
     case 10:
     case 11:
-        PPM_ASSIGN(p, rand() % v1 + v3, rand() % v2, rand() % v1 + v3);
+        PPM_ASSIGN(p, pm_rand(randStP) % v1 + v3,
+                      pm_rand(randStP) % v2,
+                      pm_rand(randStP) % v1 + v3);
         break;
 
     case 12:
     case 13:
     case 14:
-        PPM_ASSIGN(p, rand() % v1 + v3, rand() % v1 + v3, rand() % v2);
+        PPM_ASSIGN(p, pm_rand(randStP) % v1 + v3,
+                      pm_rand(randStP) % v1 + v3,
+                      pm_rand(randStP) % v2);
         break;
     }
 
@@ -497,7 +489,8 @@ randomAnticamoColor(pixval const maxval) {
 
 
 static pixel
-randomCamoColor(pixval const maxval) {
+randomCamoColor(struct pm_randSt * const randStP,
+                pixval             const maxval) {
 
     int const v1 = (maxval + 1 ) / 8;
     int const v2 = (maxval + 1 ) / 4;
@@ -505,31 +498,39 @@ randomCamoColor(pixval const maxval) {
 
     pixel p;
 
-    switch (rand() % 10) {
+    switch (pm_rand(randStP) % 10) {
     case 0:
     case 1:
     case 2:
         /* light brown */
-        PPM_ASSIGN(p, rand() % v3 + v3, rand() % v3 + v2, rand() % v3 + v2);
+        PPM_ASSIGN(p, pm_rand(randStP) % v3 + v3,
+                      pm_rand(randStP) % v3 + v2,
+                      pm_rand(randStP) % v3 + v2);
         break;
 
     case 3:
     case 4:
     case 5:
         /* dark green */
-        PPM_ASSIGN(p, rand() % v2, rand() % v2 + 3 * v1, rand() % v2);
+        PPM_ASSIGN(p, pm_rand(randStP) % v2,
+                      pm_rand(randStP) % v2 + 3 * v1,
+                      pm_rand(randStP) % v2);
         break;
 
     case 6:
     case 7:
         /* brown */
-        PPM_ASSIGN(p, rand() % v2 + v2, rand() % v2, rand() % v2);
+        PPM_ASSIGN(p, pm_rand(randStP) % v2 + v2,
+                      pm_rand(randStP) % v2,
+                      pm_rand(randStP) % v2);
         break;
 
     case 8:
     case 9:
         /* dark brown */
-        PPM_ASSIGN(p, rand() % v1 + v1, rand() % v1, rand() % v1);
+        PPM_ASSIGN(p, pm_rand(randStP) % v1 + v1,
+                      pm_rand(randStP) % v1,
+                      pm_rand(randStP) % v1);
         break;
     }
 
@@ -539,28 +540,30 @@ randomCamoColor(pixval const maxval) {
 
 
 static float
-rnduni(void) {
-    return rand() % 32767 / 32767.0;
+rnduni(struct pm_randSt * const randStP) {
+
+    return pm_rand(randStP) % 32767 / 32767.0;
 }
 
 
 
 static void
-clearBackgroundCamo(pixel **     const pixels,
-                    unsigned int const cols,
-                    unsigned int const rows,
-                    pixval       const maxval,
-                    ColorTable * const colorTableP,
-                    bool         const antiflag) {
+clearBackgroundCamo(pixel **           const pixels,
+                    unsigned int       const cols,
+                    unsigned int       const rows,
+                    pixval             const maxval,
+                    ColorTable *       const colorTableP,
+                    struct pm_randSt * const randStP,
+                    bool               const antiflag) {
 
     pixel color;
 
     if (colorTableP->count > 0) {
         color = colorTableP->color[0];
     } else if (antiflag)
-        color = randomAnticamoColor(maxval);
+        color = randomAnticamoColor(randStP, maxval);
     else
-        color = randomCamoColor(maxval);
+        color = randomCamoColor(randStP,maxval);
 
     ppmd_filledrectangle(
         pixels, cols, rows, maxval, 0, 0, cols, rows, PPMD_NULLDRAWPROC,
@@ -570,13 +573,14 @@ clearBackgroundCamo(pixel **     const pixels,
 
 
 static void
-camoFill(pixel **         const pixels,
-         unsigned int     const cols,
-         unsigned int     const rows,
-         pixval           const maxval,
-         struct fillobj * const fh,
-         ColorTable     * const colorTableP,
-         bool             const antiflag) {
+camoFill(pixel **           const pixels,
+         unsigned int       const cols,
+         unsigned int       const rows,
+         pixval             const maxval,
+         struct fillobj *   const fh,
+         ColorTable     *   const colorTableP,
+         struct pm_randSt * const randStP,
+         bool               const antiflag) {
 
     pixel color;
 
@@ -585,9 +589,9 @@ camoFill(pixel **         const pixels,
         color = colorTableP->color[colorTableP->index];
         nextColorBg(colorTableP);
     } else if (antiflag)
-        color = randomAnticamoColor(maxval);
+        color = randomAnticamoColor(randStP, maxval);
     else
-        color = randomCamoColor(maxval);
+        color = randomCamoColor(randStP, maxval);
 
     ppmd_fill(pixels, cols, rows, maxval, fh, PPMD_NULLDRAWPROC, &color);
 }
@@ -608,25 +612,29 @@ camoFill(pixel **         const pixels,
 
 
 static void
-computeXsYs(int *        const xs,
-            int *        const ys,
-            unsigned int const cols,
-            unsigned int const rows,
-            unsigned int const pointCt) {
-
-    unsigned int const cx = rand() % cols;
-    unsigned int const cy = rand() % rows;
-    double const a = rnduni() * (MAX_ELLIPSE_FACTOR - MIN_ELLIPSE_FACTOR) +
-        MIN_ELLIPSE_FACTOR;
-    double const b = rnduni() * (MAX_ELLIPSE_FACTOR - MIN_ELLIPSE_FACTOR) +
-        MIN_ELLIPSE_FACTOR;
-    double const theta = rnduni() * 2.0 * M_PI;
+computeXsYs(int *              const xs,
+            int *              const ys,
+            unsigned int       const cols,
+            unsigned int       const rows,
+            unsigned int       const pointCt,
+            struct pm_randSt * const randStP) {
+
+    unsigned int const cx = pm_rand(randStP) % cols;
+    unsigned int const cy = pm_rand(randStP) % rows;
+    double const a = rnduni(randStP) *
+                       (MAX_ELLIPSE_FACTOR - MIN_ELLIPSE_FACTOR) +
+                        MIN_ELLIPSE_FACTOR;
+    double const b = rnduni(randStP) *
+                       (MAX_ELLIPSE_FACTOR - MIN_ELLIPSE_FACTOR) +
+                        MIN_ELLIPSE_FACTOR;
+    double const theta = rnduni(randStP) * 2.0 * M_PI;
 
     unsigned int p;
 
     for (p = 0; p < pointCt; ++p) {
-        double const c = rnduni() * (MAX_POINT_FACTOR - MIN_POINT_FACTOR) +
-            MIN_POINT_FACTOR;
+        double const c = rnduni(randStP) *
+                           (MAX_POINT_FACTOR - MIN_POINT_FACTOR) +
+                            MIN_POINT_FACTOR;
         double const tx   = a * sin(p * 2.0 * M_PI / pointCt);
         double const ty   = b * cos(p * 2.0 * M_PI / pointCt);
         double const tang = atan2(ty, tx) + theta;
@@ -638,18 +646,20 @@ computeXsYs(int *        const xs,
 
 
 static void
-camo(pixel **     const pixels,
-     unsigned int const cols,
-     unsigned int const rows,
-     ColorTable * const colorTableP,
-     pixval       const maxval,
-     bool         const antiflag) {
+camo(pixel **           const pixels,
+     unsigned int       const cols,
+     unsigned int       const rows,
+     ColorTable *       const colorTableP,
+     struct pm_randSt * const randStP,
+     pixval             const maxval,
+     bool               const antiflag) {
 
     unsigned int const n = (rows * cols) / SQR(BLOBRAD) * 5;
 
     unsigned int i;
 
-    clearBackgroundCamo(pixels, cols, rows, maxval, colorTableP, antiflag);
+    clearBackgroundCamo(pixels, cols, rows, maxval,
+                        colorTableP, randStP, antiflag);
 
     if (colorTableP->count > 0) {
         assert(colorTableP->count > 1);
@@ -658,13 +668,13 @@ camo(pixel **     const pixels,
 
     for (i = 0; i < n; ++i) {
         unsigned int const pointCt =
-            rand() % (MAX_POINTS - MIN_POINTS + 1) + MIN_POINTS;
+            pm_rand(randStP) % (MAX_POINTS - MIN_POINTS + 1) + MIN_POINTS;
 
         int xs[MAX_POINTS], ys[MAX_POINTS];
         int x0, y0;
         struct fillobj * fh;
 
-        computeXsYs(xs, ys, cols, rows, pointCt);
+        computeXsYs(xs, ys, cols, rows, pointCt, randStP);
 
         x0 = (xs[0] + xs[pointCt - 1]) / 2;
         y0 = (ys[0] + ys[pointCt - 1]) / 2;
@@ -675,7 +685,8 @@ camo(pixel **     const pixels,
             pixels, cols, rows, maxval, x0, y0, pointCt, xs, ys, x0, y0,
             ppmd_fill_drawproc, fh);
 
-        camoFill(pixels, cols, rows, maxval, fh, colorTableP, antiflag);
+        camoFill(pixels, cols, rows, maxval, fh,
+                 colorTableP, randStP, antiflag);
 
         ppmd_fill_destroy(fh);
     }
@@ -688,17 +699,20 @@ camo(pixel **     const pixels,
 -----------------------------------------------------------------------------*/
 
 static void
-gingham2(pixel **     const pixels,
-         unsigned int const cols,
-         unsigned int const rows,
-         ColorTable   const colorTable,
-         pixval       const maxval) {
+gingham2(pixel **           const pixels,
+         unsigned int       const cols,
+         unsigned int       const rows,
+         ColorTable         const colorTable,
+         struct pm_randSt * const randStP,
+         pixval             const maxval) {
 
     bool  const colorSpec = (colorTable.count > 0);
     pixel const backcolor = colorSpec ?
-                            colorTable.color[0] : randomDarkColor(maxval);
+                              colorTable.color[0] :
+                              randomDarkColor(randStP, maxval);
     pixel const forecolor = colorSpec ?
-                            colorTable.color[1] : randomBrightColor(maxval);
+                              colorTable.color[1] :
+                              randomBrightColor(randStP, maxval);
     unsigned int const colso2 = cols / 2;
     unsigned int const rowso2 = rows / 2;
 
@@ -722,19 +736,23 @@ gingham2(pixel **     const pixels,
 
 
 static void
-gingham3(pixel **     const pixels,
-         unsigned int const cols,
-         unsigned int const rows,
-         ColorTable   const colorTable,
-         pixval       const maxval) {
+gingham3(pixel **           const pixels,
+         unsigned int       const cols,
+         unsigned int       const rows,
+         ColorTable         const colorTable,
+         struct pm_randSt * const randStP,
+         pixval             const maxval) {
 
     bool  const colorSpec = (colorTable.count > 0);
-    pixel const backcolor = colorSpec ?
-                            colorTable.color[0] : randomDarkColor(maxval);
+    pixel const backcolor  = colorSpec ?
+                               colorTable.color[0] :
+                               randomDarkColor(randStP, maxval);
     pixel const fore1color = colorSpec ?
-                            colorTable.color[1] : randomBrightColor(maxval);
+                               colorTable.color[1] :
+                               randomBrightColor(randStP, maxval);
     pixel const fore2color = colorSpec ?
-                            colorTable.color[2] : randomBrightColor(maxval);
+                               colorTable.color[2] :
+                               randomBrightColor(randStP, maxval);
     unsigned int const colso4 = cols / 4;
     unsigned int const rowso4 = rows / 4;
 
@@ -770,19 +788,23 @@ gingham3(pixel **     const pixels,
 
 
 static void
-madras(pixel **     const pixels,
-       unsigned int const cols,
-       unsigned int const rows,
-       ColorTable   const colorTable,
-       pixval       const maxval) {
+madras(pixel **           const pixels,
+       unsigned int       const cols,
+       unsigned int       const rows,
+       ColorTable         const colorTable,
+       struct pm_randSt * const randStP,
+       pixval             const maxval) {
 
     bool  const colorSpec = (colorTable.count > 0);
-    pixel const backcolor = colorSpec ?
-                            colorTable.color[0] : randomDarkColor(maxval);
+    pixel const backcolor  = colorSpec ?
+                               colorTable.color[0] :
+                               randomDarkColor(randStP, maxval);
     pixel const fore1color = colorSpec ?
-                            colorTable.color[1] : randomBrightColor(maxval);
+                               colorTable.color[1] :
+                               randomBrightColor(randStP, maxval);
     pixel const fore2color = colorSpec ?
-                            colorTable.color[2] : randomBrightColor(maxval);
+                               colorTable.color[2] :
+                               randomBrightColor(randStP, maxval);
     unsigned int const cols2  = cols * 2 / 44;
     unsigned int const rows2  = rows * 2 / 44;
     unsigned int const cols3  = cols * 3 / 44;
@@ -899,19 +921,23 @@ madras(pixel **     const pixels,
 
 
 static void
-tartan(pixel **     const pixels,
-       unsigned int const cols,
-       unsigned int const rows,
-       ColorTable   const colorTable,
-       pixval       const maxval) {
+tartan(pixel **           const pixels,
+       unsigned int       const cols,
+       unsigned int       const rows,
+       ColorTable         const colorTable,
+       struct pm_randSt * const randStP,
+       pixval             const maxval) {
 
     bool  const colorSpec = (colorTable.count > 0);
-    pixel const backcolor = colorSpec ?
-                            colorTable.color[0] : randomDarkColor(maxval);
+    pixel const backcolor  = colorSpec ?
+                               colorTable.color[0] :
+                               randomDarkColor(randStP, maxval);
     pixel const fore1color = colorSpec ?
-                            colorTable.color[1] : randomBrightColor(maxval);
+                               colorTable.color[1] :
+                               randomBrightColor(randStP, maxval);
     pixel const fore2color = colorSpec ?
-                            colorTable.color[2] : randomBrightColor(maxval);
+                               colorTable.color[2] :
+                               randomBrightColor(randStP, maxval);
     unsigned int const cols1  = cols / 22;
     unsigned int const rows1  = rows / 22;
     unsigned int const cols3  = cols * 3 / 22;
@@ -1013,14 +1039,15 @@ argyle(pixel **     const pixels,
        unsigned int const cols,
        unsigned int const rows,
        ColorTable   const colorTable,
+       struct pm_randSt * const randStP,
        pixval       const maxval,
        bool         const stripes) {
 
     bool  const colorSpec = (colorTable.count > 0);
     pixel const backcolor = colorSpec ?
-        colorTable.color[0] : randomDarkColor(maxval);
+        colorTable.color[0] : randomDarkColor(randStP, maxval);
     pixel const forecolor = colorSpec ?
-        colorTable.color[1] : randomBrightColor(maxval);
+        colorTable.color[1] : randomBrightColor(randStP, maxval);
 
     /* Fill canvas with background to start */
     ppmd_filledrectangle(
@@ -1032,7 +1059,8 @@ argyle(pixel **     const pixels,
     if (stripes) {
          /* Connect corners with thin stripes */
          pixel const stripecolor =
-             colorSpec ? colorTable.color[2] : randomBrightColor(maxval);
+             colorSpec ? colorTable.color[2] :
+                         randomBrightColor(randStP, maxval);
 
          ppmd_line(pixels, cols, rows, maxval, 0, 0, cols-1, rows-1,
               PPMD_NULLDRAWPROC, (char *) &stripecolor);
@@ -1049,32 +1077,33 @@ argyle(pixel **     const pixels,
 
 
 
-#define MAXPOLES 500
+static unsigned int const MAXPOLES = 500;
 
 
 
 static void
-placeAndColorPolesRandomly(int *        const xs,
-                           int *        const ys,
-                           pixel *      const colors,
-                           unsigned int const cols,
-                           unsigned int const rows,
-                           pixval       const maxval,
-                           ColorTable * const colorTableP,
-                           unsigned int const poleCt) {
+placeAndColorPolesRandomly(int *              const xs,
+                           int *              const ys,
+                           pixel *            const colors,
+                           unsigned int       const cols,
+                           unsigned int       const rows,
+                           pixval             const maxval,
+                           ColorTable *       const colorTableP,
+                           struct pm_randSt * const randStP,
+                           unsigned int       const poleCt) {
 
     unsigned int i;
 
     for (i = 0; i < poleCt; ++i) {
 
-        xs[i] = rand() % cols;
-        ys[i] = rand() % rows;
+        xs[i] = pm_rand(randStP) % cols;
+        ys[i] = pm_rand(randStP) % rows;
 
         if (colorTableP->count > 0) {
             colors[i] = colorTableP->color[colorTableP->index];
             nextColor(colorTableP);
         } else
-            colors[i] = randomBrightColor(maxval);
+            colors[i] = randomBrightColor(randStP, maxval);
     }
 }
 
@@ -1104,11 +1133,12 @@ assignInterpolatedColor(pixel * const resultP,
 
 
 static void
-poles(pixel **     const pixels,
-      unsigned int const cols,
-      unsigned int const rows,
-      ColorTable * const colorTableP,
-      pixval       const maxval) {
+poles(pixel **           const pixels,
+      unsigned int       const cols,
+      unsigned int       const rows,
+      ColorTable *       const colorTableP,
+      struct pm_randSt * const randStP,
+      pixval             const maxval) {
 
     unsigned int const poleCt = MAX(2, MIN(MAXPOLES, cols * rows / 30000));
 
@@ -1117,7 +1147,7 @@ poles(pixel **     const pixels,
     unsigned int row;
 
     placeAndColorPolesRandomly(xs, ys, colors, cols, rows, maxval,
-                               colorTableP, poleCt);
+                               colorTableP, randStP, poleCt);
 
     /* Interpolate points */
 
@@ -1236,11 +1266,12 @@ sqRainbowCircleDrawproc(pixel **     const pixels,
 
 
 static void
-chooseSqPoleColors(ColorTable * const colorTableP,
-                   pixval       const maxval,
-                   pixel *      const color1P,
-                   pixel *      const color2P,
-                   pixel *      const color3P) {
+chooseSqPoleColors(ColorTable *       const colorTableP,
+                   pixval             const maxval,
+                   pixel *            const color1P,
+                   pixel *            const color2P,
+                   pixel *            const color3P,
+                   struct pm_randSt * const randStP) {
 
     if (colorTableP->count > 0) {
         *color1P = colorTableP->color[colorTableP->index];
@@ -1250,19 +1281,20 @@ chooseSqPoleColors(ColorTable * const colorTableP,
         *color3P = colorTableP->color[colorTableP->index];
         nextColor(colorTableP);
     } else {
-        *color1P = randomBrightColor(maxval);
-        *color2P = randomBrightColor(maxval);
-        *color3P = randomBrightColor(maxval);
+        *color1P = randomBrightColor(randStP, maxval);
+        *color2P = randomBrightColor(randStP, maxval);
+        *color3P = randomBrightColor(randStP, maxval);
     }
 }
 
 
 
 static void
-sqAssignColors(unsigned int const circlecount,
-               pixval       const maxval,
-               ColorTable * const colorTableP,
-               pixel *      const colors) {
+sqAssignColors(unsigned int       const circlecount,
+               pixval             const maxval,
+               ColorTable *       const colorTableP,
+               pixel *            const colors,
+               struct pm_randSt * const randStP) {
 
     float const cco3 = (circlecount - 1) / 3.0;
 
@@ -1271,7 +1303,7 @@ sqAssignColors(unsigned int const circlecount,
     pixel rc3;
     unsigned int i;
 
-    chooseSqPoleColors(colorTableP, maxval, &rc1, &rc2, &rc3);
+    chooseSqPoleColors(colorTableP, maxval, &rc1, &rc2, &rc3, randStP);
 
     for (i = 0; i < circlecount; ++i) {
         if (i < cco3) {
@@ -1333,24 +1365,25 @@ clearBackgroundSquig(pixel **     const pixels,
 
 
 static void
-chooseWrapAroundPoint(unsigned int const cols,
-                      unsigned int const rows,
-                      ppmd_point * const pFirstP,
-                      ppmd_point * const pLastP,
-                      ppmd_point * const p0P,
-                      ppmd_point * const p1P,
-                      ppmd_point * const p2P,
-                      ppmd_point * const p3P) {
-
-    switch (rand() % 4) {
+chooseWrapAroundPoint(unsigned int       const cols,
+                      unsigned int       const rows,
+                      ppmd_point *       const pFirstP,
+                      ppmd_point *       const pLastP,
+                      ppmd_point *       const p0P,
+                      ppmd_point *       const p1P,
+                      ppmd_point *       const p2P,
+                      ppmd_point *       const p3P,
+                      struct pm_randSt * const randStP) {
+
+    switch (pm_rand(randStP) % 4) {
     case 0:
-        p1P->x = rand() % cols;
+        p1P->x = pm_rand(randStP) % cols;
         p1P->y = 0;
         if (p1P->x < cols / 2)
-            pFirstP->x = rand() % (p1P->x * 2 + 1);
+            pFirstP->x = pm_rand(randStP) % (p1P->x * 2 + 1);
         else
-            pFirstP->x = cols - 1 - rand() % ((cols - p1P->x) * 2);
-        pFirstP->y = rand() % rows;
+            pFirstP->x = cols - 1 - pm_rand(randStP) % ((cols - p1P->x) * 2);
+        pFirstP->y = pm_rand(randStP) % rows;
         p2P->x = p1P->x;
         p2P->y = rows - 1;
         pLastP->x = 2 * p2P->x - pFirstP->x;
@@ -1362,13 +1395,13 @@ chooseWrapAroundPoint(unsigned int const cols,
         break;
 
     case 1:
-        p2P->x = rand() % cols;
+        p2P->x = pm_rand(randStP) % cols;
         p2P->y = 0;
         if (p2P->x < cols / 2)
-            pLastP->x = rand() % (p2P->x * 2 + 1);
+            pLastP->x = pm_rand(randStP) % (p2P->x * 2 + 1);
         else
-            pLastP->x = cols - 1 - rand() % ((cols - p2P->x) * 2);
-        pLastP->y = rand() % rows;
+            pLastP->x = cols - 1 - pm_rand(randStP) % ((cols - p2P->x) * 2);
+        pLastP->y = pm_rand(randStP) % rows;
         p1P->x = p2P->x;
         p1P->y = rows - 1;
         pFirstP->x = 2 * p1P->x - pLastP->x;
@@ -1381,12 +1414,12 @@ chooseWrapAroundPoint(unsigned int const cols,
 
     case 2:
         p1P->x = 0;
-        p1P->y = rand() % rows;
-        pFirstP->x = rand() % cols;
+        p1P->y = pm_rand(randStP) % rows;
+        pFirstP->x = pm_rand(randStP) % cols;
         if (p1P->y < rows / 2)
-            pFirstP->y = rand() % (p1P->y * 2 + 1);
+            pFirstP->y = pm_rand(randStP) % (p1P->y * 2 + 1);
         else
-            pFirstP->y = rows - 1 - rand() % ((rows - p1P->y) * 2);
+            pFirstP->y = rows - 1 - pm_rand(randStP) % ((rows - p1P->y) * 2);
         p2P->x = cols - 1;
         p2P->y = p1P->y;
         pLastP->x = p2P->x - pFirstP->x;
@@ -1399,12 +1432,12 @@ chooseWrapAroundPoint(unsigned int const cols,
 
     case 3:
         p2P->x = 0;
-        p2P->y = rand() % rows;
-        pLastP->x = rand() % cols;
+        p2P->y = pm_rand(randStP) % rows;
+        pLastP->x = pm_rand(randStP) % cols;
         if (p2P->y < rows / 2)
-            pLastP->y = rand() % (p2P->y * 2 + 1);
+            pLastP->y = pm_rand(randStP) % (p2P->y * 2 + 1);
         else
-            pLastP->y = rows - 1 - rand() % ((rows - p2P->y) * 2);
+            pLastP->y = rows - 1 - pm_rand(randStP) % ((rows - p2P->y) * 2);
         p1P->x = cols - 1;
         p1P->y = p2P->y;
         pFirstP->x = p1P->x - pLastP->x;
@@ -1420,11 +1453,12 @@ chooseWrapAroundPoint(unsigned int const cols,
 
 
 static void
-squig(pixel **     const pixels,
-      unsigned int const cols,
-      unsigned int const rows,
-      ColorTable * const colorTableP,
-      pixval       const maxval) {
+squig(pixel **           const pixels,
+      unsigned int       const cols,
+      unsigned int       const rows,
+      ColorTable *       const colorTableP,
+      struct pm_randSt * const randStP,
+      pixval             const maxval) {
 
     int i;
 
@@ -1453,10 +1487,11 @@ squig(pixel **     const pixels,
         ppmd_circlep(pixels, cols, rows, maxval,
                      ppmd_makePoint(0, 0), radius,
                      sqMeasureCircleDrawproc, &sqClientData);
-        sqAssignColors(squig.circleCt, maxval, colorTableP, squig.color);
+        sqAssignColors(squig.circleCt, maxval, colorTableP, squig.color,
+                       randStP);
 
         chooseWrapAroundPoint(cols, rows, &c[0], &c[SQ_POINTS-1],
-                              &p0, &p1, &p2, &p3);
+                              &p0, &p1, &p2, &p3, randStP);
 
         {
             /* Do the middle points */
@@ -1466,8 +1501,8 @@ squig(pixel **     const pixels,
               /* validateSquigAspect() assures that
                  cols - 2 * radius, rows -2 * radius are positive
               */
-                c[j].x = (rand() % (cols - 2 * radius)) + radius;
-                c[j].y = (rand() % (rows - 2 * radius)) + radius;
+                c[j].x = (pm_rand(randStP) % (cols - 2 * radius)) + radius;
+                c[j].y = (pm_rand(randStP) % (rows - 2 * radius)) + radius;
             }
         }
 
@@ -1490,6 +1525,7 @@ main(int argc, const char ** argv) {
 
     struct CmdlineInfo cmdline;
     pixel ** pixels;
+    struct pm_randSt randSt;
 
     pm_proginit(&argc, argv);
 
@@ -1497,65 +1533,68 @@ main(int argc, const char ** argv) {
 
     validateComputableDimensions(cmdline.width, cmdline.height);
 
-    srand(cmdline.randomseedSpec ? cmdline.randomseed : pm_randseed());
+    pm_randinit(&randSt);
+    pm_srand2(&randSt, cmdline.randomseedSpec, cmdline.randomseed);
 
     pixels = ppm_allocarray(cmdline.width, cmdline.height);
 
     switch (cmdline.basePattern) {
     case PAT_GINGHAM2:
         gingham2(pixels, cmdline.width, cmdline.height,
-                 cmdline.colorTable, PPM_MAXMAXVAL);
+                 cmdline.colorTable, &randSt, PPM_MAXMAXVAL);
         break;
 
     case PAT_GINGHAM3:
         gingham3(pixels, cmdline.width, cmdline.height,
-                 cmdline.colorTable, PPM_MAXMAXVAL);
+                 cmdline.colorTable, &randSt, PPM_MAXMAXVAL);
         break;
 
     case PAT_MADRAS:
         madras(pixels, cmdline.width, cmdline.height,
-               cmdline.colorTable, PPM_MAXMAXVAL);
+               cmdline.colorTable, &randSt, PPM_MAXMAXVAL);
         break;
 
     case PAT_TARTAN:
         tartan(pixels, cmdline.width, cmdline.height,
-               cmdline.colorTable, PPM_MAXMAXVAL);
+               cmdline.colorTable, &randSt, PPM_MAXMAXVAL);
         break;
 
     case PAT_ARGYLE1:
         argyle(pixels, cmdline.width, cmdline.height,
-               cmdline.colorTable, PPM_MAXMAXVAL, FALSE);
+               cmdline.colorTable, &randSt, PPM_MAXMAXVAL, FALSE);
         break;
 
     case PAT_ARGYLE2:
         argyle(pixels, cmdline.width, cmdline.height,
-               cmdline.colorTable, PPM_MAXMAXVAL, TRUE);
+               cmdline.colorTable, &randSt, PPM_MAXMAXVAL, TRUE);
         break;
 
     case PAT_POLES:
         poles(pixels, cmdline.width, cmdline.height,
-              &cmdline.colorTable, PPM_MAXMAXVAL);
+              &cmdline.colorTable, &randSt, PPM_MAXMAXVAL);
         break;
 
     case PAT_SQUIG:
         squig(pixels, cmdline.width, cmdline.height,
-              &cmdline.colorTable, PPM_MAXMAXVAL);
+              &cmdline.colorTable, &randSt, PPM_MAXMAXVAL);
         break;
 
     case PAT_CAMO:
         camo(pixels, cmdline.width, cmdline.height,
-             &cmdline.colorTable, PPM_MAXMAXVAL, 0);
+             &cmdline.colorTable, &randSt, PPM_MAXMAXVAL, 0);
         break;
 
     case PAT_ANTICAMO:
         camo(pixels, cmdline.width, cmdline.height,
-             &cmdline.colorTable, PPM_MAXMAXVAL, 1);
+             &cmdline.colorTable, &randSt, PPM_MAXMAXVAL, 1);
         break;
 
     default:
         pm_error("can't happen!");
     }
 
+    pm_randterm(&randSt);
+
     ppm_writeppm(stdout, pixels, cmdline.width, cmdline.height,
                  PPM_MAXMAXVAL, 0);
 
@@ -1567,4 +1606,3 @@ main(int argc, const char ** argv) {
 }
 
 
-
diff --git a/generator/ppmrainbow b/generator/ppmrainbow
index e8a329ff..a5c2689d 100755
--- a/generator/ppmrainbow
+++ b/generator/ppmrainbow
@@ -26,6 +26,19 @@ exec perl -w -x -S -- "$0" "$@"
 use strict;
 use Getopt::Long;
 use File::Temp;
+use IO::Handle;
+
+
+
+sub pm_message($) {
+    STDERR->print("ppmrainbow: $_[0]\n");
+}
+
+sub pm_error($) {
+    pm_message($_[0]);
+    exit(1);
+}
+
 
 my ($FALSE, $TRUE) = (0,1);
 
@@ -46,15 +59,6 @@ sub doVersionHack($) {
 
 
 
-sub fatal($) {
-    my ($msg) = @_;
-
-    print(STDERR "ppmrainbow: $msg\n");
-    exit(1);
-}
-
-
-
 ##############################################################################
 #
 #                                 MAINLINE
@@ -79,14 +83,14 @@ GetOptions("width=i"   => \$Twid,
            "verbose!"  => \$verbose);
 
 if ($Twid < 1 || $Thgt < 1) {
-    fatal("invalid width and/or height");
+    pm_error("invalid width and/or height");
 }
 my $verboseCommand = $verbose ? "set -x;" : "";
 
 if (@ARGV < 1) {
-    fatal("You must specify at least one color as an argument");
+    pm_error("You must specify at least one color as an argument");
 } elsif (@ARGV < 2 && ! $repeat) {
-    fatal("With the -norepeat option, you must specify at least two colors " .
+    pm_error("With the -norepeat option, you must specify at least two colors " .
           "as arguments.");
 }
 
@@ -115,7 +119,7 @@ while (@colorlist >= 2) {
     my $rc = system("$verboseCommand pgmramp -lr $w $Thgt | " .
                     "pgmtoppm \"$colorlist[0]-$colorlist[1]\" >$outfile");
     if ($rc != 0) {
-        fatal("pgmramp|pgmtoppm pipe failed.");
+        pm_error("pgmramp|pgmtoppm pipe failed.");
     }
     $widthRemaining -= $w;
     $n++;
diff --git a/generator/ppmrough.c b/generator/ppmrough.c
index c87a0364..a4a1f14d 100644
--- a/generator/ppmrough.c
+++ b/generator/ppmrough.c
@@ -16,11 +16,10 @@
 
 #include "pm_c_util.h"
 #include "mallocvar.h"
+#include "rand.h"
 #include "shhopt.h"
 #include "ppm.h"
 
-static pixel** PIX;
-static pixval BG_RED, BG_GREEN, BG_BLUE;
 
 
 struct CmdlineInfo {
@@ -28,9 +27,12 @@ struct CmdlineInfo {
      in a form easy for the program to use.
   */
     unsigned int left, right, top, bottom;
-    unsigned int width, height, var;
-    const char * bg_rgb;
-    const char * fg_rgb;
+    unsigned int leftSpec, rightSpec, topSpec, bottomSpec;
+    unsigned int width;
+    unsigned int height;
+    unsigned int var;
+    const char * bg;  /* Null if not specified */
+    const char * fg;  /* Null if not specified */
     unsigned int randomseed;
     unsigned int randomseedSpec;
     unsigned int verbose;
@@ -42,6 +44,8 @@ static void
 parseCommandLine(int argc, const char ** argv,
                  struct CmdlineInfo * const cmdlineP) {
 
+    unsigned int widthSpec, heightSpec, bgSpec, fgSpec, varSpec;
+
     optEntry * option_def;
         /* Instructions to OptParseOptions2 on how to parse our options.    */
     optStruct3 opt;
@@ -51,28 +55,30 @@ parseCommandLine(int argc, const char ** argv,
     MALLOCARRAY_NOFAIL(option_def, 100);
 
     option_def_index = 0;   /* incremented by OPTENTRY */
-    OPTENT3(0, "width",       OPT_UINT,   &cmdlineP->width,   NULL, 0);
-    OPTENT3(0, "height",      OPT_UINT,   &cmdlineP->height,  NULL, 0);
-    OPTENT3(0, "left",        OPT_UINT,   &cmdlineP->left,    NULL, 0);
-    OPTENT3(0, "right",       OPT_UINT,   &cmdlineP->right,   NULL, 0);
-    OPTENT3(0, "top",         OPT_UINT,   &cmdlineP->top,     NULL, 0);
-    OPTENT3(0, "bottom",      OPT_UINT,   &cmdlineP->bottom,  NULL, 0);
-    OPTENT3(0, "bg",          OPT_STRING, &cmdlineP->bg_rgb,  NULL, 0);
-    OPTENT3(0, "fg",          OPT_STRING, &cmdlineP->fg_rgb,  NULL, 0);
-    OPTENT3(0, "var",         OPT_UINT,   &cmdlineP->var,     NULL, 0);
+    OPTENT3(0, "width",       OPT_UINT,   &cmdlineP->width,
+            &widthSpec, 0);
+    OPTENT3(0, "height",      OPT_UINT,   &cmdlineP->height,
+            &heightSpec, 0);
+    OPTENT3(0, "left",        OPT_UINT,   &cmdlineP->left,
+            &cmdlineP->leftSpec, 0);
+    OPTENT3(0, "right",       OPT_UINT,   &cmdlineP->right,
+            &cmdlineP->rightSpec, 0);
+    OPTENT3(0, "top",         OPT_UINT,   &cmdlineP->top,
+            &cmdlineP->topSpec, 0);
+    OPTENT3(0, "bottom",      OPT_UINT,   &cmdlineP->bottom,
+            &cmdlineP->bottomSpec, 0);
+    OPTENT3(0, "bg",          OPT_STRING, &cmdlineP->bg,
+            &bgSpec, 0);
+    OPTENT3(0, "fg",          OPT_STRING, &cmdlineP->fg,
+            &fgSpec, 0);
+    OPTENT3(0, "var",         OPT_UINT,   &cmdlineP->var,
+            &varSpec, 0);
     OPTENT3(0, "randomseed",  OPT_UINT,   &cmdlineP->randomseed,
             &cmdlineP->randomseedSpec, 0);
     OPTENT3(0, "init",        OPT_UINT,   &cmdlineP->randomseed,
             &cmdlineP->randomseedSpec, 0);
-    OPTENT3(0, "verbose",     OPT_FLAG,   NULL, &cmdlineP->verbose, 0);
-
-    /* Set the defaults */
-    cmdlineP->width = 100;
-    cmdlineP->height = 100;
-    cmdlineP->left = cmdlineP->right = cmdlineP->top = cmdlineP->bottom = -1;
-    cmdlineP->bg_rgb = NULL;
-    cmdlineP->fg_rgb = NULL;
-    cmdlineP->var = 10;
+    OPTENT3(0, "verbose",     OPT_FLAG,   NULL,
+            &cmdlineP->verbose, 0);
 
     opt.opt_table = option_def;
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
@@ -80,6 +86,17 @@ parseCommandLine(int argc, const char ** argv,
 
     pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
 
+    if (!widthSpec)
+        cmdlineP->width = 100;
+    if (!heightSpec)
+        cmdlineP->height = 100;
+    if (!bgSpec)
+        cmdlineP->bg = NULL;
+    if (!fgSpec)
+        cmdlineP->fg = NULL;
+    if (!varSpec)
+        cmdlineP->var = 10;
+
     if (argc-1 != 0)
         pm_error("There are no arguments.  You specified %d.", argc-1);
 
@@ -89,239 +106,340 @@ parseCommandLine(int argc, const char ** argv,
 
 
 static void
-procLeft(int          const r1,
-         int          const r2,
-         int          const c1,
-         int          const c2,
-         unsigned int const var) {
+reportParameters(struct CmdlineInfo const cmdline,
+                 pixel              const bgcolor,
+                 pixel              const fgcolor) {
+
+    pm_message("width is %d, height is %d, variance is %d.",
+               cmdline.width, cmdline.height, cmdline.var);
+    if (cmdline.leftSpec)
+        pm_message("ragged left border is required");
+    if (cmdline.rightSpec)
+        pm_message("ragged right border is required");
+    if (cmdline.topSpec)
+        pm_message("ragged top border is required");
+    if (cmdline.bottomSpec)
+        pm_message("ragged bottom border is required");
+    pm_message("background is %s",
+               ppm_colorname(&bgcolor, PPM_MAXMAXVAL, 1));
+    pm_message("foreground is %s",
+               ppm_colorname(&fgcolor, PPM_MAXMAXVAL, 1));
+    if (cmdline.randomseedSpec)
+        pm_message("pm_rand() initialized with seed %u",
+                   cmdline.randomseed);
+}
 
-    int cm, rm, c;
 
-    if (r1 + 1 == r2)  return;
-    rm = (r1 + r2) >> 1;
-    cm = (c1 + c2) >> 1;
-    cm += (int)floor(((float)rand() / RAND_MAX - 0.5) * var + 0.5);
 
-    for (c = 0; c < cm; c++)
-        PPM_ASSIGN(PIX[rm][c], BG_RED, BG_GREEN, BG_BLUE);
+static void
+makeAllForegroundColor(pixel **     const pixels,
+                       unsigned int const rows,
+                       unsigned int const cols,
+                       pixel        const fgcolor) {
+
+    pixval const r = PPM_GETR(fgcolor);
+    pixval const g = PPM_GETG(fgcolor);
+    pixval const b = PPM_GETB(fgcolor);
+
+    unsigned int row;
+
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
 
-    procLeft(r1, rm, c1, cm, var);
-    procLeft(rm, r2, cm, c2, var);
+        for (col = 0; col < cols; ++col)
+            PPM_ASSIGN(pixels[row][col], r, g, b);
+    }
 }
 
 
 
 static void
-procRight(int          const r1,
-          int          const r2,
-          int          const c1,
-          int          const c2,
-          unsigned int const width,
-          unsigned int const var) {
+procLeft(pixel **           const pixels,
+         int                const r1,
+         int                const r2,
+         int                const c1,
+         int                const c2,
+         unsigned int       const var,
+         pixel              const bgcolor,
+         struct pm_randSt * const randStP) {
+
+    if (r1 + 1 != r2) {
+        int const rm = (r1 + r2) >> 1;
+        int const cm = ((c1 + c2) >> 1) +
+            (int)floor(((float)pm_rand(randStP) / RAND_MAX - 0.5) * var + 0.5);
+
+        int c;
+
+        for (c = 0; c < cm; c++)
+            pixels[rm][c] = bgcolor;
+
+        procLeft(pixels, r1, rm, c1, cm, var, bgcolor, randStP);
+        procLeft(pixels, rm, r2, cm, c2, var, bgcolor, randStP);
+    }
+}
+
 
-    int cm, rm, c;
 
-    if (r1 + 1 == r2)  return;
-    rm = (r1 + r2) >> 1;
-    cm = (c1 + c2) >> 1;
-    cm += (int)floor(((float)rand() / RAND_MAX - 0.5) * var + 0.5);
+static void
+procRight(pixel **           const pixels,
+          int                const r1,
+          int                const r2,
+          int                const c1,
+          int                const c2,
+          unsigned int       const width,
+          unsigned int       const var,
+          pixel              const bgcolor,
+          struct pm_randSt * const randStP) {
+
+    if (r1 + 1 != r2) {
+        int const rm = (r1 + r2) >> 1;
+        int const cm = ((c1 + c2) >> 1) +
+            (int)floor(((float)pm_rand(randStP) / RAND_MAX - 0.5) * var + 0.5);
+
+        int c;
+
+        for (c = cm; c < width; c++)
+            pixels[rm][c] = bgcolor;
+
+        procRight(pixels, r1, rm, c1, cm, width, var, bgcolor, randStP);
+        procRight(pixels, rm, r2, cm, c2, width, var, bgcolor, randStP);
+    }
+}
+
 
-    for (c = cm; c < width; c++)
-        PPM_ASSIGN(PIX[rm][c], BG_RED, BG_GREEN, BG_BLUE);
 
-    procRight(r1, rm, c1, cm, width, var);
-    procRight(rm, r2, cm, c2, width, var);
+static void
+procTop(pixel **           const pixels,
+        int                const c1,
+        int                const c2,
+        int                const r1,
+        int                const r2,
+        unsigned int       const var,
+        pixel              const bgcolor,
+        struct pm_randSt * const randStP) {
+
+    if (c1 + 1 != c2) {
+        int const cm = (c1 + c2) >> 1;
+        int const rm = ((r1 + r2) >> 1) +
+            (int)floor(((float)pm_rand(randStP) / RAND_MAX - 0.5) * var + 0.5);
+
+        int r;
+
+        for (r = 0; r < rm; r++)
+            pixels[r][cm] = bgcolor;
+
+        procTop(pixels, c1, cm, r1, rm, var, bgcolor, randStP);
+        procTop(pixels, cm, c2, rm, r2, var, bgcolor, randStP);
+    }
 }
 
 
 
 static void
-procTop(int          const c1,
-        int          const c2,
-        int          const r1,
-        int          const r2,
-        unsigned int const var) {
+procBottom(pixel **           const pixels,
+           int                const c1,
+           int                const c2,
+           int                const r1,
+           int                const r2,
+           unsigned int       const height,
+           unsigned int       const var,
+           pixel              const bgcolor,
+           struct pm_randSt * const randStP) {
+
+    if (c1 + 1 != c2) {
+        int const cm = (c1 + c2) >> 1;
+        int const rm = ((r1 + r2) >> 1) +
+            (int)floor(((float)pm_rand(randStP) / RAND_MAX - 0.5) * var + 0.5);
+
+        int r;
+
+        for (r = rm; r < height; ++r)
+            pixels[r][cm] = bgcolor;
+
+        procBottom(pixels, c1, cm, r1, rm, height, var, bgcolor, randStP);
+        procBottom(pixels, cm, c2, rm, r2, height, var, bgcolor, randStP);
+    }
+}
 
-    int rm, cm, r;
 
-    if (c1 + 1 == c2)  return;
-    cm = (c1 + c2) >> 1;
-    rm = (r1 + r2) >> 1;
-    rm += (int)floor(((float)rand() / RAND_MAX - 0.5) * var + 0.5);
 
-    for (r = 0; r < rm; r++)
-        PPM_ASSIGN(PIX[r][cm], BG_RED, BG_GREEN, BG_BLUE);
+static void
+makeRaggedLeftBorder(pixel **           const pixels,
+                     unsigned int       const rows,
+                     unsigned int       const cols,
+                     bool               const leftSpec,
+                     unsigned int       const left,
+                     unsigned int       const var,
+                     pixel              const bgcolor,
+                     struct pm_randSt * const randStP) {
+
+    if (leftSpec) {
+        int const leftC1 = left;
+        int const leftC2 = left;
+        int const leftR1 = 0;
+        int const leftR2 = rows - 1;
 
-    procTop(c1, cm, r1, rm, var);
-    procTop(cm, c2, rm, r2, var);
+        unsigned int col;
+
+        for (col = 0; col < leftC1; ++col)
+            pixels[leftR1][col] = bgcolor;
+        for (col = 0; col < leftC2; ++col)
+            pixels[leftR2][col] = bgcolor;
+
+        procLeft(pixels, leftR1, leftR2, leftC1, leftC2, var,
+                 bgcolor, randStP);
+    }
 }
 
 
 
 static void
-procBottom(int          const c1,
-           int          const c2,
-           int          const r1,
-           int          const r2,
-           unsigned int const height,
-           unsigned int const var) {
+makeRaggedRightBorder(pixel **           const pixels,
+                      unsigned int       const rows,
+                      unsigned int       const cols,
+                      bool               const rightSpec,
+                      unsigned int       const right,
+                      unsigned int       const width,
+                      unsigned int       const var,
+                      pixel              const bgcolor,
+                      struct pm_randSt * const randStP) {
+
+    if (rightSpec) {
+        int const rightC1 = cols - right - 1;
+        int const rightC2 = cols - right - 1;
+        int const rightR1 = 0;
+        int const rightR2 = rows - 1;
 
-    int rm, cm, r;
-
-    if (c1 + 1 == c2)  return;
-    cm = (c1 + c2) >> 1;
-    rm = (r1 + r2) >> 1;
-    rm += (int)floor(((float)rand() / RAND_MAX - 0.5) * var + 0.5);
+        unsigned int col;
 
-    for (r = rm; r < height; r++)
-        PPM_ASSIGN(PIX[r][cm], BG_RED, BG_GREEN, BG_BLUE);
+        for (col = rightC1; col < cols; ++col)
+            pixels[rightR1][col] = bgcolor;
+        for (col = rightC2; col < cols; ++col)
+            pixels[rightR2][col] = bgcolor;
 
-    procBottom(c1, cm, r1, rm, height, var);
-    procBottom(cm, c2, rm, r2, height, var);
+        procRight(pixels, rightR1, rightR2, rightC1, rightC2, width, var,
+                  bgcolor, randStP);
+    }
 }
 
 
 
-int
-main(int argc, const char * argv[]) {
+static void
+makeRaggedTopBorder(pixel **           const pixels,
+                    unsigned int       const rows,
+                    unsigned int       const cols,
+                    bool               const topSpec,
+                    unsigned int       const top,
+                    unsigned int       const var,
+                    pixel              const bgcolor,
+                    struct pm_randSt * const randStP) {
+
+    if (topSpec) {
+        unsigned int const topR1 = top;
+        unsigned int const topR2 = top;
+        unsigned int const topC1 = 0;
+        unsigned int const topC2 = cols - 1;
 
-    struct CmdlineInfo cmdline;
-    pixel bgcolor, fgcolor;
-    pixval fg_red, fg_green, fg_blue;
-    int rows, cols, row;
-    int left, right, top, bottom;
+        unsigned int row;
 
-    pm_proginit(&argc, argv);
+        for (row = 0; row < topR1; ++row)
+            pixels[row][topC1] = bgcolor;
+        for (row = 0; row < topR2; ++row)
+            pixels[row][topC2] = bgcolor;
 
-    parseCommandLine(argc, argv, &cmdline);
+        procTop(pixels, topC1, topC2, topR1, topR2, var, bgcolor, randStP);
+    }
+}
 
-    srand(cmdline.randomseedSpec ? cmdline.randomseed : pm_randseed());
 
-    cols = cmdline.width;
-    rows = cmdline.height;
-    left = cmdline.left;
-    right = cmdline.right;
-    top = cmdline.top;
-    bottom = cmdline.bottom;
 
-    if (cmdline.bg_rgb)
-        bgcolor = ppm_parsecolor(cmdline.bg_rgb, PPM_MAXMAXVAL);
-    else
-        PPM_ASSIGN(bgcolor, 0, 0, 0);
-    BG_RED = PPM_GETR(bgcolor);
-    BG_GREEN = PPM_GETG(bgcolor);
-    BG_BLUE = PPM_GETB(bgcolor);
+static void
+makeRaggedBottomBorder(pixel **            const pixels,
+                       unsigned int        const rows,
+                       unsigned int        const cols,
+                       bool                const bottomSpec,
+                       unsigned int        const bottom,
+                       unsigned int        const height,
+                       unsigned int        const var,
+                       pixel               const bgcolor,
+                       struct pm_randSt *  const randStP) {
+
+    if (bottomSpec) {
+        unsigned int const bottomR1 = rows - bottom - 1;
+        unsigned int const bottomR2 = rows - bottom - 1;
+        unsigned int const bottomC1 = 0;
+        unsigned int const bottomC2 = cols - 1;
 
-    if (cmdline.fg_rgb)
-        fgcolor = ppm_parsecolor(cmdline.fg_rgb, PPM_MAXMAXVAL);
-    else
-        PPM_ASSIGN(fgcolor, PPM_MAXMAXVAL, PPM_MAXMAXVAL, PPM_MAXMAXVAL);
-    fg_red = PPM_GETR(fgcolor);
-    fg_green = PPM_GETG(fgcolor);
-    fg_blue = PPM_GETB(fgcolor);
-
-    if (cmdline.verbose) {
-        pm_message("width is %d, height is %d, variance is %d.",
-                   cols, rows, cmdline.var);
-        if (left >= 0)
-            pm_message("ragged left border is required");
-        if (right >= 0)
-            pm_message("ragged right border is required");
-        if (top >= 0)
-            pm_message("ragged top border is required");
-        if (bottom >= 0)
-            pm_message("ragged bottom border is required");
-        pm_message("background is %s",
-                   ppm_colorname(&bgcolor, PPM_MAXMAXVAL, 1));
-        pm_message("foreground is %s",
-                   ppm_colorname(&fgcolor, PPM_MAXMAXVAL, 1));
-        if (cmdline.randomseedSpec)
-            pm_message("srand() initialized with seed %u", cmdline.randomseed);
-    }
+        unsigned int row;
 
-    /* Allocate memory for the whole pixmap */
-    PIX = ppm_allocarray(cols, rows);
+        for (row = bottomR1; row < rows; ++row)
+            pixels[row][bottomC1] = bgcolor;
+        for (row = bottomR2; row < rows; ++row)
+            pixels[row][bottomC2] = bgcolor;
 
-    /* First, set all pixel to foreground color */
-    for (row = 0; row < rows; row++) {
-        unsigned int col;
-        for (col = 0; col < cols; ++col)
-            PPM_ASSIGN(PIX[row][col], fg_red, fg_green, fg_blue);
+        procBottom(pixels, bottomC1, bottomC2, bottomR1, bottomR2,
+                   height, var, bgcolor, randStP);
     }
-    /* Make a ragged left border */
-    if (left >= 0) {
-        int const left_c1 = left;
-        int const left_c2 = left;
-        int const left_r1 = 0;
-        int const left_r2 = rows - 1;
+}
 
-        unsigned int col;
 
-        for (col = 0; col < left_c1; ++col)
-            PPM_ASSIGN(PIX[left_r1][col], BG_RED, BG_GREEN, BG_BLUE);
-        for (col = 0; col < left_c2; ++col)
-            PPM_ASSIGN(PIX[left_r2][col], BG_RED, BG_GREEN, BG_BLUE);
 
-        procLeft(left_r1, left_r2, left_c1, left_c2, cmdline.var);
-    }
+int
+main(int argc, const char ** const argv) {
 
-    /* Make a ragged right border */
-    if (right >= 0) {
-        int const right_c1 = cols - right - 1;
-        int const right_c2 = cols - right - 1;
-        int const right_r1 = 0;
-        int const right_r2 = rows - 1;
+    struct CmdlineInfo cmdline;
+    pixel bgcolor, fgcolor;
+    struct pm_randSt randSt;
+    static pixel** pixels;
 
-        unsigned int col;
+    pm_proginit(&argc, argv);
 
-        for (col = right_c1; col < cols; col++)
-            PPM_ASSIGN(PIX[right_r1][col], BG_RED, BG_GREEN, BG_BLUE);
-        for (col = right_c2; col < cols; col++)
-            PPM_ASSIGN(PIX[right_r2][col], BG_RED, BG_GREEN, BG_BLUE);
+    parseCommandLine(argc, argv, &cmdline);
 
-        procRight(right_r1, right_r2, right_c1, right_c2,
-                   cmdline.width, cmdline.var);
-    }
+    pm_randinit(&randSt);
+    pm_srand(&randSt,
+             cmdline.randomseedSpec ? cmdline.randomseed : pm_randseed());
 
-    /* Make a ragged top border */
-    if (top >= 0) {
-        int const top_r1 = top;
-        int const top_r2 = top;
-        int const top_c1 = 0;
-        int const top_c2 = cols - 1;
+    if (cmdline.bg)
+        bgcolor = ppm_parsecolor(cmdline.bg, PPM_MAXMAXVAL);
+    else
+        PPM_ASSIGN(bgcolor, 0, 0, 0);
 
-        unsigned int row;
+    if (cmdline.fg)
+        fgcolor = ppm_parsecolor(cmdline.fg, PPM_MAXMAXVAL);
+    else
+        PPM_ASSIGN(fgcolor, PPM_MAXMAXVAL, PPM_MAXMAXVAL, PPM_MAXMAXVAL);
 
-        for (row = 0; row < top_r1; ++row)
-            PPM_ASSIGN(PIX[row][top_c1], BG_RED, BG_GREEN, BG_BLUE);
-        for (row = 0; row < top_r2; ++row)
-            PPM_ASSIGN(PIX[row][top_c2], BG_RED, BG_GREEN, BG_BLUE);
+    if (cmdline.verbose)
+        reportParameters(cmdline, bgcolor, fgcolor);
 
-        procTop(top_c1, top_c2, top_r1, top_r2, cmdline.var);
-    }
+    pixels = ppm_allocarray(cmdline.width, cmdline.height);
 
-    /* Make a ragged bottom border */
-    if (bottom >= 0) {
-        int const bottom_r1 = rows - bottom - 1;
-        int const bottom_r2 = rows - bottom - 1;
-        int const bottom_c1 = 0;
-        int const bottom_c2 = cols - 1;
+    makeAllForegroundColor(pixels, cmdline.height, cmdline.width, fgcolor);
 
-        unsigned int row;
+    makeRaggedLeftBorder(pixels, cmdline.height, cmdline.width,
+                         cmdline.leftSpec, cmdline.left,
+                         cmdline.var, bgcolor, &randSt);
 
-        for (row = bottom_r1; row < rows; ++row)
-            PPM_ASSIGN(PIX[row][bottom_c1], BG_RED, BG_GREEN, BG_BLUE);
-        for (row = bottom_r2; row < rows; ++row)
-            PPM_ASSIGN(PIX[row][bottom_c2], BG_RED, BG_GREEN, BG_BLUE);
+    makeRaggedRightBorder(pixels, cmdline.height, cmdline.width,
+                          cmdline.rightSpec, cmdline.right,
+                          cmdline.width, cmdline.var, bgcolor, &randSt);
 
-        procBottom(bottom_c1, bottom_c2, bottom_r1, bottom_r2,
-                   cmdline.height, cmdline.var);
-    }
+    makeRaggedTopBorder(pixels, cmdline.height, cmdline.width,
+                        cmdline.topSpec, cmdline.top,
+                        cmdline.var, bgcolor, &randSt);
+
+    makeRaggedBottomBorder(pixels, cmdline.height, cmdline.width,
+                           cmdline.bottomSpec, cmdline.bottom,
+                           cmdline.height, cmdline.var, bgcolor, &randSt);
+
+    pm_randterm(&randSt);
 
     /* Write pixmap */
-    ppm_writeppm(stdout, PIX, cols, rows, PPM_MAXMAXVAL, 0);
+    ppm_writeppm(stdout, pixels, cmdline.width, cmdline.height,
+                 PPM_MAXMAXVAL, 0);
 
-    ppm_freearray(PIX, rows);
+    ppm_freearray(pixels, cmdline.height);
 
     pm_close(stdout);
 
@@ -329,4 +447,3 @@ main(int argc, const char * argv[]) {
 }
 
 
-
diff --git a/lib/Makefile b/lib/Makefile
index bc758df4..d42658a2 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -48,6 +48,10 @@ LIBOBJECTS_X = \
   util/matrix.o \
   util/nsleep.o \
   util/nstring.o \
+  util/rand.o \
+  util/randsysrand.o \
+  util/randsysrandom.o \
+  util/randmersenne.o \
   util/runlength.o \
   util/shhopt.o \
   util/token.o \
diff --git a/lib/util/Makefile b/lib/util/Makefile
index 02119edf..d8e2d135 100644
--- a/lib/util/Makefile
+++ b/lib/util/Makefile
@@ -19,6 +19,10 @@ UTILOBJECTS = \
   matrix.o \
   nsleep.o \
   nstring.o \
+  rand.o \
+  randsysrand.o \
+  randsysrandom.o \
+  randmersenne.o \
   runlength.o \
   shhopt.o \
   token.o \
diff --git a/lib/util/rand.c b/lib/util/rand.c
new file mode 100644
index 00000000..2f60de83
--- /dev/null
+++ b/lib/util/rand.c
@@ -0,0 +1,261 @@
+/*
+
+Pseudo-random number generator for Netpbm
+
+The interface provided herein should be flexible enough for anybody
+who wishes to use some other random number generator.
+
+---
+
+If you desire to implement a different generator, or writing an original
+one, first take a look at the random number generator section of the
+GNU Scientific Library package (GSL).
+
+GNU Scientific Library
+https://www.gnu.org/software/gsl/
+
+GSL Random Number Generators
+https://wnww.gnu.org/software/gsl/doc/html/rng.html
+
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <strings.h>
+#include <time.h>
+#include <float.h>
+#include <math.h>
+
+#include "netpbm/pm_c_util.h"
+#include "netpbm/mallocvar.h"
+#include "netpbm/pm.h"
+#include "netpbm/rand.h"
+
+/*-----------------------------------------------------------------------------
+                              Use
+-------------------------------------------------------------------------------
+  Typical usage:
+
+      #include "rand.h"
+
+      ...
+
+      myfunction( ... , unsigned int const seed , ... ) {
+
+          struct randSt;
+
+          ...
+
+          pm_randinit(&randSt);
+          pm_srand(&randSt, seed);  // pm_srand2() is often more useful
+
+          ...
+
+          pm_rand(&randSt);
+
+          ...
+
+          pm_randterm(&randSt);
+
+      }
+-----------------------------------------------------------------------------*/
+
+
+
+/*-----------------------------------------------------------------------------
+                            Design note
+-------------------------------------------------------------------------------
+
+  Netpbm code contains multiple random number generators.  Stock Netpbm always
+  uses an internal pseudo-random number generator that implements the Mersenne
+  Twister method and does not rely on any randomness facility of the operating
+  system, but it is easy to compile an alternative version that uses others.
+
+  The Mersenne Twister method was new to Netpbm in Netpbm 10.94
+  (March 2021).  Before that, Netpbm used standard OS-provided facilities.
+
+  Programs that use random numbers have existed in Netpbm since PBMPlus days.
+  The system rand() function was used in instances randomness was required;
+  exceptions were rare and all of them appear to be errors on the part of the
+  original author.
+
+  Although the rand() function is available in every system on which Netpbm
+  runs, differences exist in the underlying algorithm, so that Netpbm programs
+  produce different output on different systems even when the user specifies
+  the same random number seed.
+
+  This was not considered a problem in the early days.  Deterministic
+  operation was not a feature users requested and it was impossible regardless
+  of the random number generation method on most programs because they did
+  not allow a user to specify a seed for the generator.
+
+  This state of affairs changed as Netpbm got firmly established as a
+  base-level system package.  Security became critical for many users.  A
+  crucial component of quality control is automated regression tests (="make
+  check").  Unpredictable behavior gets in the way of testing.  One by one
+  programs were given the -randomseed (or -seed) option to ensure reproducible
+  results.  Often this was done as new tests cases were written.  However,
+  inconsistent output caused by system-level differences in rand()
+  implementation remained a major obstacle.
+
+  In 2020 the decision was made to replace all calls to rand() in the Netpbm
+  source code with an internal random number generator.  We decided to use the
+  Mersenne Twister, which is concise, enjoys a fine reputation and is
+  available under liberal conditions (see below.)
+-----------------------------------------------------------------------------*/
+
+
+void
+pm_srand(struct pm_randSt * const randStP,
+         unsigned int       const seed) {
+/*----------------------------------------------------------------------------
+  Initialize (or "seed") the random number generation sequence with value
+  'seed'.
+-----------------------------------------------------------------------------*/
+    pm_randinit(randStP);
+
+    randStP->vtable.srand(randStP, seed);
+
+    randStP->seed = seed;
+}
+
+
+
+void
+pm_srand2(struct pm_randSt * const randStP,
+          bool               const seedValid,
+          unsigned int       const seed) {
+/*----------------------------------------------------------------------------
+  Seed the random number generator.  If 'seedValid' is true, use 'seed"..
+  Otherwise, use pm_randseed().
+
+  For historical reasons pm_randseed() is defined in libpm.c rather than
+  this source file.
+-----------------------------------------------------------------------------*/
+    pm_srand(randStP, seedValid ? seed : pm_randseed() );
+
+}
+
+
+
+unsigned long int
+pm_rand(struct pm_randSt * const randStP) {
+/*----------------------------------------------------------------------------
+  An integer random number in the interval [0, randStP->max].
+-----------------------------------------------------------------------------*/
+    return randStP->vtable.rand(randStP);
+}
+
+
+
+double
+pm_drand(struct pm_randSt * const randStP) {
+/*----------------------------------------------------------------------------
+  A floating point random number in the interval [0, 1).
+
+  Although the return value is declared as double, the actual value will have
+  no more precision than a single call to pm_rand() provides.  This is 32 bits
+  for Mersenne Twister.
+-----------------------------------------------------------------------------*/
+    return (double) pm_rand(randStP) / randStP->max;
+}
+
+
+
+void
+pm_gaussrand2(struct pm_randSt * const randStP,
+              double *           const r1P,
+              double *           const r2P) {
+/*----------------------------------------------------------------------------
+  Generate two Gaussian (or normally) distributed random numbers *r1P and
+  *r2P.
+
+  Mean = 0, Standard deviation = 1.
+
+  This is called the Box-Muller method.
+
+  For details of this algorithm and other methods for producing
+  Gaussian random numbers see:
+
+  http://www.doc.ic.ac.uk/~wl/papers/07/csur07dt.pdf
+-----------------------------------------------------------------------------*/
+    double u1, u2;
+
+    u1 = pm_drand(randStP);
+    u2 = pm_drand(randStP);
+
+    if (u1 < DBL_EPSILON)
+        u1 = DBL_EPSILON;
+
+    *r1P = sqrt(-2.0 * log(u1)) * cos(2.0 * M_PI * u2);
+    *r2P = sqrt(-2.0 * log(u1)) * sin(2.0 * M_PI * u2);
+}
+
+
+
+double
+pm_gaussrand(struct pm_randSt * const randStP) {
+/*----------------------------------------------------------------------------
+  A Gaussian (or normally) distributed random number.
+
+  Mean = 0, Standard deviation = 1.
+
+  If a randStP->gaussCache has a value, return that value.  Otherwise call
+  pm_gaussrand2; return one generated value, remember the other.
+-----------------------------------------------------------------------------*/
+    double retval;
+
+    if (!randStP->gaussCacheValid) {
+        pm_gaussrand2(randStP, &retval, &randStP->gaussCache);
+        randStP->gaussCacheValid = true;
+    } else {
+        retval = randStP->gaussCache;
+        randStP->gaussCacheValid = false;
+    }
+
+    return retval;
+}
+
+
+
+void
+pm_randinit(struct pm_randSt * const randStP) {
+/*----------------------------------------------------------------------------
+  Initialize the random number generator.
+-----------------------------------------------------------------------------*/
+    switch (PM_RANDOM_NUMBER_GENERATOR) {
+    case PM_RAND_SYS_RAND:
+        randStP->vtable = pm_randsysrand_vtable;
+        break;
+    case PM_RAND_SYS_RANDOM:
+        randStP->vtable = pm_randsysrandom_vtable;
+        break;
+    case PM_RAND_MERSENNETWISTER:
+        randStP->vtable = pm_randmersenne_vtable;
+        break;
+    default:
+        pm_error("INTERNAL ERROR: Invalid value of "
+                 "PM_RANDOM_NUMBER_GENERATOR (random number generator "
+                 "engine type): %u", PM_RANDOM_NUMBER_GENERATOR);
+    }
+
+    randStP->vtable.init(randStP);
+
+    randStP->gaussCacheValid = false;
+}
+
+
+
+void
+pm_randterm(struct pm_randSt * const randStP) {
+/*----------------------------------------------------------------------------
+  Tear down the random number generator.
+-----------------------------------------------------------------------------*/
+     if (randStP->stateP)
+         free(randStP->stateP);
+}
+
+
+
+
diff --git a/lib/util/rand.h b/lib/util/rand.h
new file mode 100644
index 00000000..c441890a
--- /dev/null
+++ b/lib/util/rand.h
@@ -0,0 +1,107 @@
+/* Interface header file for random number generator functions in libnetpbm */
+
+#ifndef RAND_H_INCLUDED
+#define RAND_H_INCLUDED
+
+#include "netpbm/pm_c_util.h"
+#include "netpbm/mallocvar.h"
+
+/*
+  Definitions for selecting the random number generator
+
+  The default random number generator is Mersenne Twister.  Here we
+  provide a means to revert to the system rand() generator if need be.
+
+  Glibc provides generators: rand(), random() and drand48().  Each has
+  its own associated functions.  In the Glibc documentation rand() is
+  called "ISO", random() is called "BSD" and drand48() is called "SVID".
+  If your system has the glibc documentation installed "info rand" should
+  get you to the relevant page.  The documentation is available online
+  from:
+
+  https://www.gnu.org/software/libc/manual/html_node/Pseudo_002dRandom-Numbers.html
+  Pseudo-Random Numbers (The GNU C Library)
+
+  Glibc's choice of name is confusing for what it calls "ISO" rand()
+  was available in early BSD systems.
+
+  Functions by these names appear on most Unix systems, but generation
+  formulas and default initial states are known to differ.  On systems
+  which do not use glibc, what is called rand() may have no relation
+  with the formula of the ISO C standard.  Likewise random() may have
+  no relation with the BSD formula.
+*/
+
+enum PmRandEngine {PM_RAND_SYS_RAND,         /* rand()   */
+                   PM_RAND_SYS_RANDOM,       /* random() */
+                   PM_RAND_SYS_DRAND48,      /* drand48()  reserved */
+                   PM_RAND_MERSENNETWISTER   /* default */};
+
+#ifndef PM_RANDOM_NUMBER_GENERATOR
+  #define PM_RANDOM_NUMBER_GENERATOR PM_RAND_MERSENNETWISTER
+#endif
+
+
+/* Structure to hold random number generator profile and internal state */
+
+struct pm_randSt;
+
+struct pm_rand_vtable {
+    void
+    (*init)(struct pm_randSt * const randStP);
+
+    void
+    (*srand)(struct pm_randSt * const randStP,
+              unsigned int       const seed);
+
+    unsigned long int
+    (*rand)(struct pm_randSt * const randStP);
+};
+
+extern struct pm_rand_vtable const pm_randsysrand_vtable;
+extern struct pm_rand_vtable const pm_randsysrandom_vtable;
+extern struct pm_rand_vtable const pm_randmersenne_vtable;
+
+struct pm_randSt {
+    struct pm_rand_vtable vtable;
+    void *                stateP;  /* Internal state */
+    unsigned int          max;
+    unsigned int          seed;
+    bool                  gaussCacheValid;
+    double                gaussCache;
+};
+
+/* Function declarations */
+
+extern void
+pm_randinit(struct pm_randSt * const randStP);
+
+extern void
+pm_randterm(struct pm_randSt * const randStP);
+
+extern void
+pm_srand(struct pm_randSt * const randStP,
+         unsigned int       const seed);
+
+
+extern void
+pm_srand2(struct pm_randSt * const randStP,
+          bool               const withSeed,
+          unsigned int       const seedVal);
+
+extern unsigned long int
+pm_rand(struct pm_randSt * const randStP);
+
+extern double
+pm_drand(struct pm_randSt * const randStP);
+
+extern void
+pm_gaussrand2(struct pm_randSt * const randStP,
+              double *           const r1P,
+              double *           const r2P);
+
+extern double
+pm_gaussrand(struct pm_randSt * const randStP);
+
+
+#endif
diff --git a/lib/util/randmersenne.c b/lib/util/randmersenne.c
new file mode 100644
index 00000000..34355a23
--- /dev/null
+++ b/lib/util/randmersenne.c
@@ -0,0 +1,192 @@
+#include "netpbm/pm.h"
+#include "netpbm/rand.h"
+
+/* +++++ Start of Mersenne Twister pseudorandom number generator code +++++ */
+
+/*
+   Original source code from:
+   http://www.math.sci.hiroshima-u.ac.jp/m-mat/MT/VERSIONS/C-LANG/c-lang.html
+
+   A C-program for MT19937, with initialization improved 2002/1/26.
+   Coded by Takuji Nishimura and Makoto Matsumoto.
+
+   Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura,
+   All rights reserved.
+
+   Redistribution and use in source and binary forms, with or without
+   modification, are permitted provided that the following conditions
+   are met:
+
+     1. Redistributions of source code must retain the above copyright
+        notice, this list of conditions and the following disclaimer.
+
+     2. Redistributions in binary form must reproduce the above copyright
+        notice, this list of conditions and the following disclaimer in the
+        documentation and/or other materials provided with the distribution.
+
+     3. The names of its contributors may not be used to endorse or promote
+        products derived from this software without specific prior written
+        permission.
+
+   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+   A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
+   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+   Any feedback is very welcome.
+   http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html
+   email: m-mat @ math.sci.hiroshima-u.ac.jp (remove space)
+
+   Above conditions apply in the following code to the line which says:
+   +++++ End of Mersenne Twister pseudorandom number generator code +++++
+*/
+
+/* Period parameters */
+
+#define MT_N 624
+#define MT_M 397
+#define MT_MATRIX_A 0x9908b0dfUL   /* constant vector a */
+
+struct MtState {
+    uint32_t mt[MT_N]; /* the array for the state vector  */
+    unsigned int mtIndex;
+};
+
+
+
+static void
+randMtAlloc(struct MtState ** const statePP) {
+
+    struct MtState * stateP;
+
+    MALLOCVAR_NOFAIL(stateP);
+
+    *statePP = stateP;
+}
+
+
+
+/* 32 bit masks */
+
+static uint32_t const FMASK = 0xffffffffUL; /* all bits */
+static uint32_t const UMASK = 0x80000000UL; /* most significant bit */
+static uint32_t const LMASK = 0x7fffffffUL; /* least significant 31 bits */
+
+
+
+static void
+srandMt(struct MtState * const stateP,
+        unsigned int     const seed) {
+/*-----------------------------------------------------------------------------
+  Initialize state array mt[MT_N] with seed
+-----------------------------------------------------------------------------*/
+    unsigned int mtIndex;
+    uint32_t * const mt = stateP->mt;
+
+    mt[0]= seed & FMASK;
+
+    for (mtIndex = 1; mtIndex < MT_N; ++mtIndex) {
+        mt[mtIndex] = (1812433253UL * (mt[mtIndex-1]
+                       ^ (mt[mtIndex-1] >> 30)) + mtIndex);
+
+        /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */
+    }
+
+    stateP->mtIndex = mtIndex;
+}
+
+
+
+static unsigned long int
+randMt32(struct MtState * const stateP) {
+/*----------------------------------------------------------------------------
+  Generate a 32 bit random number   interval: [0, 0xffffffff]
+  ----------------------------------------------------------------------------*/
+    unsigned int mtIndex;
+    uint32_t retval;
+
+    if (stateP->mtIndex >= MT_N) {
+        /* generate N words at one time */
+        uint32_t * const mt = stateP->mt;
+        uint32_t const mag01[2]={0x0UL, MT_MATRIX_A};
+        /* mag01[x] = x * MT_MATRIX_A  for x=0, 1 */
+
+        int k;
+        uint32_t y;
+
+        if (stateP->mtIndex >= MT_N+1) {
+            pm_error("Internal error in Mersenne Twister random number"
+                     "generator");
+        }
+
+        for (k = 0; k < MT_N-MT_M; ++k) {
+            y = (mt[k] & UMASK) | (mt[k+1] & LMASK);
+            mt[k] = mt[k + MT_M] ^ (y >> 1) ^ mag01[y & 0x1UL];
+        }
+        for (; k < MT_N-1; ++k) {
+            y = (mt[k] & UMASK) | (mt[k+1] & LMASK);
+            mt[k] = mt[k+(MT_M-MT_N)] ^ (y >> 1) ^ mag01[y & 0x1UL];
+        }
+        y = (mt[MT_N - 1] & UMASK) | (mt[0] & LMASK);
+        mt[MT_N - 1] = mt[MT_M - 1] ^ (y >> 1) ^ mag01[y & 0x1UL];
+
+        mtIndex = 0;
+    } else
+        mtIndex = stateP->mtIndex;
+
+    retval = stateP->mt[mtIndex];
+
+    /* Tempering */
+    retval ^= (retval >> 11);
+    retval ^= (retval <<  7) & 0x9d2c5680UL;
+    retval ^= (retval << 15) & 0xefc60000UL;
+    retval ^= (retval >> 18);
+
+    stateP->mtIndex = mtIndex + 1;
+
+    return retval;
+}
+
+/* +++++ End of Mersenne Twister pseudorandom number generator code +++++ */
+
+
+static void
+vinit(struct pm_randSt * const randStP) {
+
+    randMtAlloc((struct MtState ** const) &randStP->stateP);
+    randStP->max    = 0xffffffffUL;
+}
+
+
+
+static void
+vsrand(struct pm_randSt * const randStP,
+       unsigned int       const seed) {
+
+    srandMt(randStP->stateP, seed);
+}
+
+
+
+static unsigned long int
+vrand(struct pm_randSt * const randStP) {
+
+    return randMt32(randStP->stateP);
+}
+
+
+
+struct pm_rand_vtable const pm_randmersenne_vtable = {
+    &vinit,
+    &vsrand,
+    &vrand
+};
+
+
diff --git a/lib/util/randsysrand.c b/lib/util/randsysrand.c
new file mode 100644
index 00000000..f97a5d3c
--- /dev/null
+++ b/lib/util/randsysrand.c
@@ -0,0 +1,39 @@
+#include <stdlib.h>
+
+#include "netpbm/rand.h"
+
+
+
+static void
+vinit(struct pm_randSt * const randStP) {
+
+    randStP->max    = RAND_MAX;
+    randStP->stateP = NULL;
+}
+
+
+
+static void
+vsrand(struct pm_randSt * const randStP,
+       unsigned int       const seed) {
+
+    srand(seed);
+}
+
+
+
+static unsigned long int
+vrand(struct pm_randSt * const randStP) {
+
+    return rand();
+}
+
+
+
+struct pm_rand_vtable const pm_randsysrand_vtable = {
+    &vinit,
+    &vsrand,
+    &vrand
+};
+
+
diff --git a/lib/util/randsysrandom.c b/lib/util/randsysrandom.c
new file mode 100644
index 00000000..3e38b0ae
--- /dev/null
+++ b/lib/util/randsysrandom.c
@@ -0,0 +1,40 @@
+#define _DEFAULT_SOURCE /* New name for SVID & BSD source defines */
+#define _XOPEN_SOURCE 500  /* Make sure random() is in stdlib.h */
+#define _BSD_SOURCE  /* Make sure random() is in stdlib.h */
+
+#include <stdlib.h>
+
+#include "netpbm/rand.h"
+
+static void
+vinit(struct pm_randSt * const randStP) {
+
+    randStP->max    = RAND_MAX;
+    randStP->stateP = NULL;
+}
+
+
+
+static void
+vsrand(struct pm_randSt * const randStP,
+       unsigned int       const seed) {
+
+    srandom(seed);
+}
+
+
+
+static unsigned long int
+vrand(struct pm_randSt * const randStP) {
+
+    return random();
+}
+
+
+struct pm_rand_vtable const pm_randsysrandom_vtable = {
+    &vinit,
+    &vsrand,
+    &vrand
+};
+
+
diff --git a/other/pamexec.c b/other/pamexec.c
index c3a1ee78..cbde81ac 100644
--- a/other/pamexec.c
+++ b/other/pamexec.c
@@ -15,8 +15,11 @@
 #define _BSD_SOURCE 1      /* Make sure strdup() is in string.h */
 #define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
 
+#include <stdbool.h>
 #include <string.h>
 #include <stdio.h>
+#include <signal.h>
+#include <setjmp.h>
 #include <errno.h>
 
 #include "pm_c_util.h"
@@ -25,7 +28,27 @@
 #include "mallocvar.h"
 #include "pam.h"
 
-struct cmdlineInfo {
+
+/* About SIGPIPE:
+
+   Unix has a strange function where by default, if you write into a pipe when
+   the reading side of the pipe has been closed, it generates a signal (of
+   class SIGPIPE), but if you tell the OS to ignore signals of class SIGPIPE,
+   then instead of generating that signal, the system call to write to the
+   pipe just fails.
+
+   Pamexec writes to a pipe when it feeds an image to the user's program's
+   Standard Input.  Should the user's program close its end of the pipe (such
+   as by exiting) before reading the whole image, that would, if we did
+   nothing to deal with it, cause Pamexec to receive a SIGPIPE signal, which
+   would make the OS terminate Pamexec.
+
+   We don't want that, so we tell the OS to ignore SIGPIPE signals, so that
+   instead our attempt to write to the pipe just fails and we fail with a
+   meaningful error message.
+*/
+
+struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
@@ -40,7 +63,7 @@ struct cmdlineInfo {
 
 static void
 parseCommandLine(int argc, const char ** argv,
-                 struct cmdlineInfo * const cmdlineP) {
+                 struct CmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
    Note that the pointers we place into *cmdlineP are sometimes to storage
    in the argv array.
@@ -59,13 +82,13 @@ parseCommandLine(int argc, const char ** argv,
     OPTENT3(0,   "check",   OPT_FLAG,   NULL,         &cmdlineP->check, 0);
 
     opt.opt_table = option_def;
-    opt.short_allowed = FALSE; /* We have no short (old-fashioned) options */
-    opt.allowNegNum = FALSE;   /* We have no parms that are negative numbers */
+    opt.short_allowed = false; /* We have no short (old-fashioned) options */
+    opt.allowNegNum = false;   /* We have no parms that are negative numbers */
 
     pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
 
-    if (argc-1 < 1) 
+    if (argc-1 < 1)
         pm_error("You must specify at least one argument: the shell command "
                  "to execute");
     else {
@@ -92,7 +115,7 @@ pipeOneImage(FILE * const infileP,
     struct pam inpam;
     struct pam outpam;
     enum pm_check_code checkRetval;
-    
+
     unsigned int row;
     tuple * tuplerow;
 
@@ -107,9 +130,25 @@ pipeOneImage(FILE * const infileP,
 
     tuplerow = pnm_allocpamrow(&inpam);
 
-    for (row = 0; row < inpam.height; ++row) {
-        pnm_readpamrow(&inpam, tuplerow);
-        pnm_writepamrow(&outpam, tuplerow);
+    {
+        jmp_buf jmpbuf;
+        int rc;
+        rc = setjmp(jmpbuf);
+        if (rc == 0) {
+            pm_setjmpbuf(&jmpbuf);
+
+            for (row = 0; row < inpam.height; ++row) {
+                pnm_readpamrow(&inpam, tuplerow);
+                pnm_writepamrow(&outpam, tuplerow);
+            }
+        } else {
+            pm_setjmpbuf(NULL);
+            pm_error("Failed to read image and pipe it to program's "
+                     "Standard Input.  If previous messages indicate "
+                     "a broken pipe error, that means the program closed "
+                     "its Standard Error (possibly by exiting) before "
+                     "it had read the entire image>");
+        }
     }
 
     pnm_freepamrow(tuplerow);
@@ -133,7 +172,7 @@ doOneImage(FILE *        const ifP,
     ofP = popen(command, "w");
 
     if (ofP == NULL)
-        pm_asprintf(errorP, 
+        pm_asprintf(errorP,
                     "Failed to start shell to run command '%s'.  "
                     "errno = %d (%s)",
                     command, errno, strerror(errno));
@@ -141,7 +180,7 @@ doOneImage(FILE *        const ifP,
         int rc;
 
         pipeOneImage(ifP, ofP);
-            
+
         rc = pclose(ofP);
 
         if (check && rc != 0)
@@ -157,7 +196,7 @@ doOneImage(FILE *        const ifP,
 int
 main(int argc, const char *argv[]) {
 
-    struct cmdlineInfo cmdline;
+    struct CmdlineInfo cmdline;
 
     FILE *       ifP;         /* Input file pointer */
     int          eof;         /* No more images in input */
@@ -169,12 +208,17 @@ main(int argc, const char *argv[]) {
     pm_proginit(&argc, argv);
 
     parseCommandLine(argc, argv, &cmdline);
-    
+
+    /* Make write to closed pipe fail rather than generate a signal.
+       See comments at top of program.
+    */
+    signal(SIGPIPE, SIG_IGN);
+
     ifP = pm_openr(cmdline.inputFileName);
 
-    for (eof = FALSE, imageSeq = 0; !eof; ++imageSeq) {
+    for (eof = false, imageSeq = 0; !eof; ++imageSeq) {
         const char * error;
-        
+
         doOneImage(ifP, cmdline.command, cmdline.check, &error);
 
         if (error) {
@@ -185,9 +229,8 @@ main(int argc, const char *argv[]) {
         pnm_nextimage(ifP, &eof);
     }
     pm_close(ifP);
-    
+
     return 0;
 }
 
 
-
diff --git a/test/Execute-Tests b/test/Execute-Tests
index 3530d978..7a02fdef 100755
--- a/test/Execute-Tests
+++ b/test/Execute-Tests
@@ -178,6 +178,7 @@ elif [ $VALGRIND_TESTS = "on" ]
   mkdir $valdir
  
   vg_command_base="valgrind --trace-children=yes";
+  # You may want to add --track-origins=yes to the above.
 
   for i in awk basename cat cksum cmp comm cp cut date dirname \
            egrep fgrep file grep gs head iconv mkdir mktemp perl rm \
@@ -185,6 +186,7 @@ elif [ $VALGRIND_TESTS = "on" ]
            testrandom Available-Testprog
 
     # Tell valgrind not to probe execution of the above programs.
+    # You may add programs in Netpbm to the above to speed up tests.
 
     do vg_skip=$vg_skip"/*/"$i","; done;
 
@@ -238,10 +240,11 @@ if [ $VALGRIND_TESTS = "on" ]
 fi
 
 # Execute a single test and test its result.
-# But first check if the .ok file exists.  (Some .ok files are
-# dynamically created.)  Then see if target programs and requirements
-# are in place.  If either of these conditions are not met, do
-# not execute the test and report "Not Testable".
+# But first check if the .ok file exists.
+# (In past versions certain .ok files were dynamically created.)
+# Then see if target programs and requirements are in place.  If
+# either of these conditions are not met, do not execute the test and
+# report "Not Testable".
 
 if [ ! -s ${srcdir}/${tname%.test}.ok ]
 then
diff --git a/test/Makefile b/test/Makefile
index c640dfff..732113f3 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -8,27 +8,14 @@ include $(BUILDDIR)/config.mk
 
 MERGE_OBJECTS =
 
-PROGS = testrandom
-
-OKSTOGENERATE = $(patsubst %.rand-ok, %.ok, $(wildcard *.rand-ok))
-
-all: $(PROGS) $(OKSTOGENERATE)
-
-testrandom.o: testrandom.c
-	$(CC_FOR_BUILD) -c -o $@ $(CFLAGS_FOR_BUILD) $<
-
-testrandom: testrandom.o
-	$(LD_FOR_BUILD) -o $@ $(LDFLAGS_FOR_BUILD) $<
-
-RAND_VARIETY ?= $(shell ./testrandom -x)
-
-$(OKSTOGENERATE): %.ok: %.rand-ok testrandom
-	sed -n "/^$(RAND_VARIETY)|/s/^$(RAND_VARIETY)|//p" $< > $@
+PROGS =
 
 OMIT_TEST_RULE = 1
 include $(SRCDIR)/common.mk
 
+all:
+
 distclean clean: cleanlocal
 .PHONY: cleanlocal
 cleanlocal:
-	rm -f $(PROGS) $(patsubst %.rand-ok,%.ok,$(wildcard *.rand-ok))
+
diff --git a/test/all-in-place.ok b/test/all-in-place.ok
index 4d840102..88626e3d 100644
--- a/test/all-in-place.ok
+++ b/test/all-in-place.ok
@@ -61,6 +61,7 @@ pamfunc: ok
 pamgauss: ok
 pamgetcolor: ok
 pamgradient: ok
+pamhomography: ok
 pamhue: ok
 pamlevels: ok
 pamlookup: ok
diff --git a/test/all-in-place.test b/test/all-in-place.test
index 78a9346a..e21c2ad8 100755
--- a/test/all-in-place.test
+++ b/test/all-in-place.test
@@ -103,6 +103,7 @@ ordinary_testprogs="\
   pamgauss \
   pamgetcolor \
   pamgradient \
+  pamhomography \
   pamhue \
   pamlevels \
   pamlookup \
diff --git a/test/pamditherbw-random.ok b/test/pamditherbw-random.ok
new file mode 100755
index 00000000..d21e3613
--- /dev/null
+++ b/test/pamditherbw-random.ok
@@ -0,0 +1,6 @@
+Test: Floyd-Steinberg
+Should print 3849876047 33894
+3849876047 33894
+Test: Atkinson
+Should print 2887295695 33894
+2887295695 33894
diff --git a/test/pamditherbw-random.test b/test/pamditherbw-random.test
new file mode 100755
index 00000000..5bff4bac
--- /dev/null
+++ b/test/pamditherbw-random.test
@@ -0,0 +1,23 @@
+#! /bin/bash
+# This script tests: pamditherbw
+# Also requires: pamchannel pamtopnm
+
+tmpdir=${tmpdir:-/tmp}
+test_red=${tmpdir}/testimg.red
+
+# Test 1.  Floyd-Steinberg
+echo "Test: Floyd-Steinberg"
+echo "Should print 3849876047 33894"
+
+pamchannel -infile=testimg.ppm -tupletype="GRAYSCALE" 0 | pamtopnm | \
+  tee ${test_red} | \
+  pamditherbw -fs -randomseed=1 | cksum
+
+
+# Test 2. Atkinson
+echo "Test: Atkinson"
+echo "Should print 2887295695 33894"
+
+pamditherbw -atkinson -randomseed=1 ${test_red} | cksum
+
+rm ${test_red}
\ No newline at end of file
diff --git a/test/pamexec.test b/test/pamexec.test
index 44c11339..ec57f4ae 100755
--- a/test/pamexec.test
+++ b/test/pamexec.test
@@ -27,7 +27,8 @@ pamexec "pbmtog3 -no | g3topbm" ${combined_pbm}  | cksum
 cat ${combined_pbm}  | cksum
 
 echo "Invalid command" 1>&2
-echo "Errors message should appear below the line." 1>&2
+echo "Executes quietly." 1>&2
+echo "Errors message should not appear below the line." 1>&2
 echo "-----------------------------------------------------------" 1>&2
 
 echo "Test Invalid: Should not print anything"
@@ -35,4 +36,4 @@ echo "Test Invalid: Should not print anything"
 pamexec "false" ${combined_pbm}
 pamexec "pamfile | false" ${combined_pbm}
 
-rm ${combined_pbm}
\ No newline at end of file
+rm ${combined_pbm}
diff --git a/test/pamrecolor.ok b/test/pamrecolor.ok
new file mode 100755
index 00000000..0a6b5413
--- /dev/null
+++ b/test/pamrecolor.ok
@@ -0,0 +1,11 @@
+Test 1. Should produce 3500040755 101532
+3500040755 101532
+Test 2. Should produce 3500040755 101532 twice
+3500040755 101532
+3500040755 101532
+Expected failure 1 1
+Expected failure 2 1
+Expected failure 3 1
+Expected failure 4 1
+Expected failure 5 1
+Expected failure 6 1
diff --git a/test/pamrecolor.test b/test/pamrecolor.test
new file mode 100755
index 00000000..cc79a124
--- /dev/null
+++ b/test/pamrecolor.test
@@ -0,0 +1,43 @@
+#! /bin/bash
+# This script tests: pamrecolor
+# Also requires: ppmtopgm pgmmake
+
+tmpdir=${tmpdir:-/tmp}
+base_pgm=${tmpdir}/base.pgm
+
+pgmmake 0.5 230 150 > ${base_pgm}
+
+echo "Test 1. Should produce 3500040755 101532"
+
+pamrecolor --colorfile=${base_pgm} testimg.ppm | cksum
+
+echo "Test 2. Should produce 3500040755 101532 twice"
+
+pamrecolor --targetcolor=rgb:80/80/80 testimg.ppm | cksum
+pamrecolor --colorspace=ntsc --targetcolor=rgb:80/80/80 testimg.ppm | cksum
+
+
+test_out=${tmpdir}/test_out
+truncated_file=${tmpdir}/truncated.txt
+echo P6 > ${truncated_file}
+
+echo 1>&2
+echo "Invalid command-line argument combinations." 1>&2
+echo "Error messages should appear below the line." 1>&2
+echo "-----------------------------------------------------------" 1>&2
+
+pamrecolor --targetcolor=rgb:00/11/22 --colorfile={base1_pgm} testimg.ppm > ${test_out} || \
+   echo -n "Expected failure 1"; test -s ${test_out}; echo " "$?
+pamrecolor --rmult=0.3  --gmult=0.3  --bmult=0.3 --colorfile={base1_pgm} testimg.ppm > ${test_out} || \
+   echo -n "Expected failure 2"; test -s ${test_out}; echo " "$?
+pamrecolor --colorspace=void --targetcolor=rgb:80/80/80 testimg.ppm > ${test_out} || \
+   echo -n "Expected failure 3"; test -s ${test_out}; echo " "$?
+pamrecolor --targetcolor=vague testimg.ppm > ${test_out} || \
+   echo -n "Expected failure 4"; test -s ${test_out}; echo " "$?
+pamrecolor --colorfile=${truncated_file} testimg.ppm > ${test_out} || \
+   echo -n "Expected failure 5"; test -s ${test_out}; echo " "$?
+pamrecolor --rmult=0.2989 --gmult=0.5866 testimg.ppm > ${test_out} || \
+   echo -n "Expected failure 6"; test -s ${test_out}; echo " "$?
+
+
+rm ${base_pgm} ${test_out} ${truncated_file}
\ No newline at end of file
diff --git a/test/pbmlife.ok b/test/pbmlife.ok
new file mode 100755
index 00000000..bd214dfb
--- /dev/null
+++ b/test/pbmlife.ok
@@ -0,0 +1,63 @@
+P1
+63 29
+000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000
+000000000000001111111000111100000000000111000111000000000000000
+000000000000000100001001100110000000001000101001100000000000000
+000000000000000100000001000010000000001000101000100000000000000
+000000000000000100010010000001000010001100101000100000000000000
+000000000000000111110010000001000010000111001100100000000000000
+000000000000000100010010000001001111101001100111100000000000000
+000000000000000100000001000010000010001000100001000000000000000
+000000000000000100000001100110000010001000100011000000000000000
+000000000000001111000000111100000000000111001100000000000000000
+000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000
+
+   tiles:	1722
+ x-edges:	1812
+ y-edges:	1842
+vertices:	1920
+    area:	1722
+perimeter:	420
+ eulerchi:	-12
+
+   tiles:	17
+ x-edges:	33
+ y-edges:	33
+vertices:	64
+    area:	17
+perimeter:	64
+ eulerchi:	15
+
+   tiles:	2
+ x-edges:	3
+ y-edges:	4
+vertices:	6
+    area:	2
+perimeter:	6
+ eulerchi:	1
+
+   tiles:	0
+ x-edges:	0
+ y-edges:	0
+vertices:	0
+    area:	0
+perimeter:	0
+ eulerchi:	0
diff --git a/test/pbmlife.test b/test/pbmlife.test
new file mode 100755
index 00000000..b4d65ec4
--- /dev/null
+++ b/test/pbmlife.test
@@ -0,0 +1,13 @@
+#! /bin/bash
+# This script tests: pbmlife pbmminkowski
+# Also requires: pbmtext
+
+pbmtext FO+89 -plain
+echo
+pbmtext FO+89 | pbmminkowski
+echo
+pbmtext FO+89 | pbmlife | pbmminkowski
+echo
+pbmtext FO+89 | pbmlife | pbmlife | pbmminkowski
+echo
+pbmtext FO+89 | pbmlife | pbmlife | pbmlife | pbmminkowski
diff --git a/test/pgmnoise.rand-ok b/test/pgmnoise.rand-ok
deleted file mode 100644
index b69f48e5..00000000
--- a/test/pgmnoise.rand-ok
+++ /dev/null
@@ -1,3 +0,0 @@
-000|0
-081|2005134911 10015
-082|3516404574 10015
diff --git a/test/pgmnoise.test b/test/pgmnoise.test
index 03301ce6..21a2729e 100755
--- a/test/pgmnoise.test
+++ b/test/pgmnoise.test
@@ -1,7 +1,89 @@
 #! /bin/bash
 # This script tests: pgmnoise
-# Also requires:
+# Also requires: pgmhist pamvalidate
 
-# Should print: 1663614689 10015 (Glibc)
-#               3516404574 10015 (MAC OS)
+echo "Test 1."
+echo "Should print: 2132901423 10015" # (Mersenne Twister)
+#                   1663614689 10015 (Glibc rand())
+#                   3516404574 10015 (MAC OS rand())
 pgmnoise --randomseed=0 100 100 | cksum
+
+
+echo "Test 2."
+# Output is similar to that of Test 2. of random-generator.test
+# The lowest four decimal digits are printed.
+
+pgmnoise --randomseed=5489 -maxval=9999 -plain 5 20
+
+
+echo "Test 3."
+for maxval in `seq 16` 255 65535
+  do
+  echo ${maxval}
+  pgmnoise -maxval=${maxval} -randomseed=1 -plain 16 1 | tr '\n' ' ' 
+  done
+echo
+
+echo "Test 4."
+# Check for maxval violation
+for maxval in `seq 16` 30 31 32 254 255 256 65534 65535
+  do
+  echo -n ${maxval} " "
+  pgmnoise -maxval=${maxval} -randomseed=1 -plain ${maxval} 10 | \
+    pamvalidate | pamfile
+  done
+
+echo "Test 5."
+echo "Should print four identical lines"
+# width height values do not affect random number sequence 
+for xysize in "1 10000" "100 100" "250 40" "1000 10"
+  do pgmnoise --randomseed=0 ${xysize} | pgmhist -mach | cksum
+  done
+
+
+tmpdir=${tmpdir:-/tmp}
+messages=${tmpdir}/messages
+
+echo "Test 6."
+echo "First column should be 2^n - 1"
+# The "pool" method of generating pixvals is used iff maxval is
+# a power of 2 minus 1: 1, 3, 7, 15, 31 ...
+for maxval in `seq 35; seq 60 69; seq 120 129; seq 250 259` 
+  do
+  pgmnoise -maxval=${maxval} -randomseed=1 -verbose 1 1 > /dev/null \
+  2> ${messages}
+  awk -v mval=${maxval} '/method/ && /pool/ { print mval, $0 }' ${messages}
+  done
+
+  rm ${messages}
+
+
+echo "Test Invalid"
+
+test_out=${tmpdir}/test_out
+
+echo 1>&2
+echo "Invalid command-line arguments." 1>&2
+echo "Error messages should appear below the line." 1>&2
+echo "-----------------------------------------------------------" 1>&2
+
+pgmnoise 0 0  > ${test_out} || \
+   echo -n "Expected failure 1"; test -s ${test_out}; echo " "$?
+pgmnoise 0 1  > ${test_out} || \
+   echo -n "Expected failure 2"; test -s ${test_out}; echo " "$?
+pgmnoise 1 0  > ${test_out} || \
+   echo -n "Expected failure 3"; test -s ${test_out}; echo " "$?
+pgmnoise      > ${test_out} || \
+   echo -n "Expected failure 4"; test -s ${test_out}; echo " "$?
+pgmnoise 1    > ${test_out} || \
+   echo -n "Expected failure 5"; test -s ${test_out}; echo " "$?
+pgmnoise 100 -1 > ${test_out} || \
+   echo -n "Expected failure 6"; test -s ${test_out}; echo " "$?
+pgmnoise -randomseed=-1 100 100  > ${test_out} || \
+   echo -n "Expected failure 7"; test -s ${test_out}; echo " "$?
+pgmnoise -maxval=-1 100 100  > ${test_out} || \
+   echo -n "Expected failure 8"; test -s ${test_out}; echo " "$?
+pgmnoise -maxval=0 100 100  > ${test_out} || \
+   echo -n "Expected failure 9"; test -s ${test_out}; echo " "$?
+pgmnoise -maxval=$((256 * 256 * 256 * 256)) 10 10 > ${test_out} || \
+   echo -n "Expected failure 10"; test -s ${test_out}; echo " "$?
diff --git a/test/ppmforge-parameters.ok b/test/ppmforge-parameters.ok
index 37d89ccd..c8edc5dd 100644
--- a/test/ppmforge-parameters.ok
+++ b/test/ppmforge-parameters.ok
@@ -1,6 +1,8 @@
-Test 1
+Test 1: Should print 256 256
 256 256
-100 90
+Test 2: Should print 40 30
+40 30
+Test 3: Should print 90 90
 90 90
 Test Invalid
 Expected failure 1 1
@@ -8,3 +10,5 @@ Expected failure 2 1
 Expected failure 3 1
 Expected failure 4 1
 Expected failure 5 1
+Expected failure 6 1
+Expected failure 7 1
diff --git a/test/ppmforge-parameters.test b/test/ppmforge-parameters.test
index 28a92916..39f199ea 100755
--- a/test/ppmforge-parameters.test
+++ b/test/ppmforge-parameters.test
@@ -2,17 +2,21 @@
 # This script tests: ppmforge
 # Also requires: pamfile
 
-echo "Test 1"
+echo "Test 1: Should print 256 256"
 
-# Should print 256 256
+# Default size is 256 256
 ppmforge -night | pamfile -size
 
+echo "Test 2: Should print 40 30"
+
 # Width is adjusted if not even
-# becomes 100 in this case
-ppmforge -night -width=99 -height=90 | pamfile -size
+# becomes 40 in this case
+ppmforge -night -width=39 -height=30 | pamfile -size
+
+echo "Test 3: Should print 90 90"
 
 # Width is adjusted if smaller than height
-# brought up to 100 in this case
+# brought up to 90 in this case
 ppmforge -night -width=80 -height=90 | pamfile -size
 
 echo "Test Invalid"
@@ -27,13 +31,17 @@ echo "-----------------------------------------------------------" 1>&2
 
 ppmforge -night  -dimension=0  > ${test_out} || \
    echo -n "Expected failure 1"; test -s ${test_out}; echo " "$?
-ppmforge -clouds -mesh=1.99    > ${test_out} || \
+ppmforge  -dimension=10  > ${test_out} || \
    echo -n "Expected failure 2"; test -s ${test_out}; echo " "$?
-ppmforge -clouds -power=0      > ${test_out} || \
+ppmforge  -dimension=-1  > ${test_out} || \
    echo -n "Expected failure 3"; test -s ${test_out}; echo " "$?
-ppmforge         -ice=0        > ${test_out} || \
+ppmforge -clouds -mesh=1.99    > ${test_out} || \
    echo -n "Expected failure 4"; test -s ${test_out}; echo " "$?
-ppmforge         -glaciers=0   > ${test_out} || \
+ppmforge -clouds -power=0      > ${test_out} || \
    echo -n "Expected failure 5"; test -s ${test_out}; echo " "$?
+ppmforge         -ice=-1       > ${test_out} || \
+   echo -n "Expected failure 6"; test -s ${test_out}; echo " "$?
+ppmforge         -glaciers=-1  > ${test_out} || \
+   echo -n "Expected failure 7"; test -s ${test_out}; echo " "$?
 
 rm ${test_out}
diff --git a/test/ppmforge.rand-ok b/test/ppmforge.rand-ok
deleted file mode 100644
index c8b3ac9f..00000000
--- a/test/ppmforge.rand-ok
+++ /dev/null
@@ -1,3 +0,0 @@
-000|0
-081|3634219838 196623
-082|3262664440 196623
diff --git a/test/ppmpat-random.rand-ok b/test/ppmpat-random.rand-ok
deleted file mode 100644
index eb8779ab..00000000
--- a/test/ppmpat-random.rand-ok
+++ /dev/null
@@ -1,7 +0,0 @@
-000|0
-081|2219119109 36015
-081|3436846137 16813
-081|908097729 16813
-082|3606254242 36015
-082|3615722579 16813
-082|1756684515 16813
diff --git a/test/ppmpat-random.test b/test/ppmpat-random.test
index a6daa982..44dd2485 100755
--- a/test/ppmpat-random.test
+++ b/test/ppmpat-random.test
@@ -7,16 +7,22 @@
 
 # These tests require random numbers.
 
-# Test 1. Should print: 2219119109 36015 (glibc)
-#                       3606254242 36015 (MAC OS)
+echo "Test 1. Should print: 1366170000 36015" # Mersenne Twister
+#                           2219119109 36015 (glibc rand())
+#                           3606254242 36015 (MAC OS rand())
+
 ppmpat --randomseed=0 -camo 100 120 | cksum
 
-# Test 2. Should print: 3436846137 16813 (glibc)
-#                       3615722579 16813 (MAC OS)
+echo "Test 2. Should print: 4073196212 16813" # Mersenne Twister
+#                       3436846137 16813 (glibc)
+#                       3615722579 16813 (MAC OS rand())
+
 ppmpat --randomseed=0 -anticamo 80 70 | cksum
 
-# Test 3. Should print: 908097729 16813 (glibc)
-#                       1756684515 16813 (MAC OS)
+echo "Test 3. Should print: 2292015301 16813" # Mersenne Twister 
+#                       908097729 16813 (glibc rand())
+#                       1756684515 16813 (MAC OS rand())
+
 ppmpat --randomseed=0 --color \
   rgb:55/c0/34,rgb:0/ff/0,rgb:0/ee/0,rgb:0/cd/0,rgb:0/8b/0,rgb:4f/4f/2f \
   -camo 80 70 | cksum
diff --git a/test/ppmrough.rand-ok b/test/ppmrough.rand-ok
deleted file mode 100644
index 216545c7..00000000
--- a/test/ppmrough.rand-ok
+++ /dev/null
@@ -1,3 +0,0 @@
-000|0
-081|378403602 30015
-082|378403602 30015
diff --git a/test/ppmshift.ok b/test/ppmshift.ok
new file mode 100755
index 00000000..e18de8f0
--- /dev/null
+++ b/test/ppmshift.ok
@@ -0,0 +1,14 @@
+Test 1. Should print: 3705777303 101484
+3705777303 101484
+Test 2. Should print: 202790723 685
+202790723 685
+Test 3. Should print: 0 : 0
+0 : 0
+Test 4. (15) Should print: 0 : 0
+0 : 0
+Test 4. (16) Should print: 0 : 0
+0 : 0
+Test 4. (20) Should print: 0 : 0
+0 : 0
+Test 4. (1000) Should print: 0 : 0
+0 : 0
diff --git a/test/ppmshift.test b/test/ppmshift.test
new file mode 100755
index 00000000..cd086d68
--- /dev/null
+++ b/test/ppmshift.test
@@ -0,0 +1,27 @@
+#! /bin/bash
+# This script tests: ppmshift
+# Also requires:
+
+echo "Test 1. Should print: 3705777303 101484"
+ppmshift -seed=1 10 testimg.ppm | cksum
+
+echo "Test 2. Should print: 202790723 685"
+ppmshift -seed=1 1 testgrid.pbm | cksum
+
+echo "Test 3. Should print: 0 : 0"
+ppmshift -seed=1 0 testimg.ppm | cmp -s - testimg.ppm | \
+  echo ${PIPESTATUS[@]} ":" $?
+
+tmpdir=${tmpdir:-/tmp}
+test_pbm=${tmpdir}/test.pbm
+
+ppmshift -seed=2 14 testgrid.pbm > ${test_pbm}
+
+for i in 15 16 20 1000
+  do
+  echo "Test 4. ("$i") Should print: 0 : 0"
+  ppmshift -seed=2 $i testgrid.pbm > ${test_pbm} | cmp -s - testgrid.ppm | \
+    echo ${PIPESTATUS[@]} ":" $?
+  done
+
+rm ${test_pbm}
\ No newline at end of file
diff --git a/test/ppmspread.ok b/test/ppmspread.ok
new file mode 100755
index 00000000..7d44ab8e
--- /dev/null
+++ b/test/ppmspread.ok
@@ -0,0 +1,6 @@
+Test 1. Should print 639729144 101484
+639729144 101484
+Test 2. Should print 3278353642 685
+3278353642 685
+Test 3. Should print 2425386270 41
+2425386270 41
diff --git a/test/ppmspread.test b/test/ppmspread.test
new file mode 100755
index 00000000..871f7438
--- /dev/null
+++ b/test/ppmspread.test
@@ -0,0 +1,12 @@
+#! /bin/bash
+# This script tests: ppmspread
+# Also requires: ppmtopgm pgmtopbm
+
+echo "Test 1. Should print 639729144 101484"
+ppmspread -randomseed=1 10 testimg.ppm | cksum
+
+echo "Test 2. Should print 3278353642 685"
+ppmspread -randomseed=1 1 testgrid.pbm | cksum
+
+echo "Test 3. Should print 2425386270 41"
+ppmspread -randomseed=1 0 testgrid.pbm | ppmtopgm | pgmtopbm | cksum
diff --git a/test/random-generator.ok b/test/random-generator.ok
new file mode 100644
index 00000000..e137d901
--- /dev/null
+++ b/test/random-generator.ok
@@ -0,0 +1,219 @@
+Test 1: Should produce:
+P2
+12 1
+1023
+720 296 192 858 101 57 298 629 804 1019 64 617
+Above output is for Mersenne Twister
+P2
+12 1
+1023
+720 296 192 858 101 57 298 629 804 1019 64 617
+
+Test 2: Mersenne Twister random number generator
+Should produce:
+3499211612  581869302 3890346734 3586334585  545404204
+4161255391 3922919429  949333985 2715962298 1323567403
+ ... 
+ 297480282 1101405687 1473439254 2634793792 1341017984
+ Total 1000 integers, 200 lines
+
+3499211612  581869302 3890346734 3586334585  545404204 
+4161255391 3922919429  949333985 2715962298 1323567403 
+ 418932835 2350294565 1196140740  809094426 2348838239 
+4264392720 4112460519 4279768804 4144164697 4156218106 
+ 676943009 3117454609 4168664243 4213834039 4111000746 
+ 471852626 2084672536 3427838553 3437178460 1275731771 
+ 609397212   20544909 1811450929  483031418 3933054126 
+2747762695 3402504553 3772830893 4120988587 2163214728 
+2816384844 3427077306  153380495 1551745920 3646982597 
+ 910208076 4011470445 2926416934 2915145307 1712568902 
+3254469058 3181055693 3191729660 2039073006 1684602222 
+1812852786 2815256116  746745227  735241234 1296707006 
+3032444839 3424291161  136721026 1359573808 1189375152 
+3747053250  198304612  640439652  417177801 4269491673 
+3536724425 3530047642 2984266209  537655879 1361931891 
+3280281326 4081172609 2107063880  147944788 2850164008 
+1884392678  540721923 1638781099  902841100 3287869586 
+ 219972873 3415357582  156513983  802611720 1755486969 
+2103522059 1967048444 1913778154 2094092595 2775893247 
+3410096536 3046698742 3955127111 3241354600 3468319344 
+1185518681 3031277329 2919300778   12105075 2813624502 
+3052449900  698412071 2765791248  511091141 1958646067 
+2140457296 3323948758 4122068897 2464257528 1461945556 
+3765644424 2513705832 3471087299  961264978   76338300 
+3226667454 3527224675 1095625157 3525484323 2173068963 
+4037587209 3002511655 1772389185 3826400342 1817480335 
+4120125281 2495189930 2350272820  678852156  595387438 
+3271610651  641212874  988512770 1105989508 3477783405 
+3610853094 4245667946 1092133642 1427854500 3497326703 
+1287767370 1045931779   58150106 3991156885  933029415 
+1503168825 3897101788  844370145 3644141418 1078396938 
+4101769245 2645891717 3345340191 2032760103 4241106803 
+1510366103  290319951 3568381791 3408475658 2513690134 
+2553373352 2361044915 3147346559 3939316793 2986002498 
+1227669233 2919803768 3252150224 1685003584 3237241796 
+2411870849 1634002467  893645500 2438775379 2265043167 
+ 325791709 1736062366  231714000 1515103006 2279758133 
+2546159170 3346497776 1530490810 4011545318 4144499009 
+ 557942923  663307952 2443079012 1696117849 2016017442 
+1663423246   51119001 3122246755 1447930741 1668894615 
+ 696567687 3983551422 3411426125 1873110678 1336658413 
+3705174600 2270032533 2664425968  711455903  513451233 
+2585492744 2027039028 1129453058 1461232481 2809248324 
+2275654012 2960153730 3075629128 3213286615 4245057188 
+1935061435 3094495853  360010077 3919490483  983448591 
+2171099548 3922754098 2397746050  654458600 2161184684 
+3546856898 1986311591 2312163142 2347594600 4278366025 
+1922360368  335761339 3669839044 1901288696 2595154464 
+ 458070173 2141230976 4131320786 4208748424   19903848 
+ 147391738 3328215103 4196191786 3510290616 1559873971 
+3731015357 2918514861  362649214 1487061100 1717053387 
+3675955720 1116134897  193529268 3436267940 2835191639 
+1852908272 3220971953 3911201640  571213604  781027019 
+4219206494 1133024903  409547355  625085180 1214072539 
+ 584409985 3445042528 3733581611  333104904 2489812253 
+2694595213 2361631596   34763086  622576118 2921810672 
+3663740744 2293225236 2671706445 1884059696 1507329019 
+ 857065948 2204390003  592711182 1725752375 1642107460 
+ 326274448 3274574484 1030432041  173822100  529650788 
+1086437636  789877945 2167974914 1030588245 3533061365 
+1792148406 4216468704  213264131 3536714075 3877136173 
+1296338417 4057830103  205919137 2108245233 1064497347 
+2101324080 2336703164 1450493809 3812754708 3865701845 
+1476779561 1585902852  142887412  477612192  699530444 
+3351157089 3768249319 1673915577  903239649 1038056164 
+1171465372 1734789440 2115022236  414269055  959581346 
+ 566820984 2105828892 4046076449 4101450561 4106566571 
+2800184123 2470502098 3253453343  256751188 1869365987 
+1008372035 2374606708 1516804538  228288551 3527001547 
+1385173098   66157275 1739381798  184785808 3901692666 
+ 725806641 3475217997 2787929747 1109372433 3142723729 
+ 557686578 2782047723 2118822689 1936702581 1625646963 
+2349385293 3085804937 1272688179 1236112995 3198431244 
+2677635414  811555596 3486972196 2949678043 1342211552 
+ 788174404 1656614077 1582629285 1477167035 2687011245 
+3503701453 3351051324 2874557775  348432514 1629591495 
+3991682351 1969229192 3331660584 1304012077 2090754125 
+3910846836 1871998370 2098597104 1918921592 3246092887 
+1315760974  464122393 2184028058 1690455542 2193747147 
+3737423698 3511684278 1549884962 3413774919 3938991454 
+2767325310 2335626851 1626114941  601913200 3485711542 
+ 858447440 2288468476 4075602213 1506361431 4252489875 
+4032981007 1031118352 3762145731   70955369 2362903502 
+1669089455 2673510137 3348740333 2521337794 2047144929 
+ 892246357 2319875070 1293843163   79245769 2022600352 
+3866257397  989939126  835351312 3626278636 3805332945 
+ 836506264 1895040349  970326679  634920763  733185481 
+1028655248  977810701 3434484235 1871311609 2031584214 
+1336174158  385787519 3965885375 2768323462 1847726660 
+2718987737  793780050 2509902580 3886434164 3120956802 
+4207987247 1523159183 1884932179 2922324286  477253416 
+3037922812 1108379444  697195677 1755438379  574393398 
+2555059183 1930828628 1126190880  180621093 2589191337 
+3424652760 3054648512  719646637  952394946 3570038180 
+ 504304985 1395707758 1274213163 2816553213 1369142370 
+1804702100 1821782344 3358274235 2181234724  486158240 
+ 367287522 4267199121 1127352639  779850007 3440331597 
+3276765484  125500149 1142120513 3989398167 1048565860 
+3136747194  432668526 2098559576 1478877150 2484746208 
+1209580219 1019125185 4160278734 1970740713  918146921 
+4136433784 2602441845 2348512686  973030509 2238261365 
+ 815637919  994690313 1724736366 2099799816 1775069742 
+2680317667  730798472 2916864943 1284417767 1698724919 
+2733611686 1578128411  651006053 4243350375 3303874296 
+ 162087183 3796616231 3801767645 4119825424 3922537059 
+  77594039 3419583692 2503306160  423966005 3293613218 
+1124728190 1407880681 1440346680  554334954 2919409323 
+1253962019  586491243 3638308238 3097648541  991125519 
+ 458538714 2155963569 2807866455    6862945 2122460897 
+  53853750 3346001678 1230879976 3071060893  423909157 
+3881450262 1652511030 3826483009 1526211009 1435219366 
+3092251623 3001090498  281084412  849586749 2207008400 
+ 131172352 1820973075 3195774605 2962673849 2147580010 
+1090677336 2061249893 1724513375 3885752424 1135918139 
+2619357288 4012575714 2652856935 2029480458 3691276589 
+2623865075 3459550738 2097670126 2477000057 2209844713 
+ 785646024 1052349661 1030500157 1430246618 3807539761 
+2157629976  123154542 2560049331 2104110449 1332109867 
+ 721241591 4136042859 4203401395  998151922 3060999432 
+3207929139 2149509272 1385268511 2023309182 1366796638 
+ 256061060 4090836236 2929047008 2296609403  182240337 
+3744374619  306855912 4014087816 2240468995 2865233169 
+ 415452309 1244206523 3513921306  281425419 3511338031 
+ 995954022 3102854413 3026765331  643667197  837979907 
+2832983005 1813414171 2227348307 4020325887 4178893912 
+ 610818241 2787397224 2762441380 3437393657 2030369078 
+1949046312 1876612561 1857107382 1049344864 3544695775 
+2172907342  358500115 3895295219  571965125  328582064 
+ 744698407 3066193991 1679065087 2650874932 3570748805 
+ 812110431 3450423805 1705023874  259721746 1192558045 
+1714799045 3685508436 2262914445 3903852862 1790140070 
+2651193482 2821191752  776610414 2697125035 2212010032 
+1254062056 3541766210 1853927671 1543286708   66516686 
+3505195914 4226521519 1260092911  717982876  739240369 
+ 456195732 2116515161 1599487648  838913496  850912042 
+3712172413 2103192411  877020153 1458113119 2646869271 
+4087221703 3771198399 3952796001 1685641891  226245966 
+4065518354 3169076409  715963611 1155859114 4174181651 
+1816065125 2422210778 2353087594 2569974907 4049024520 
+ 563593555 1794197249 2434290377 4222178191 2381045132 
+1294739153 1333544226 3011196239  518183212 2861903570 
+3168787443 2315530531 1042490149 2998340365 3534153126 
+2862715604  796613230  765073073 1342937225  549817636 
+3786981820 4291017601 2895722553  734959362 3175258828 
+ 140019477  268621172 2410334776  565052604 3787587805 
+ 386344800 2874086067   35710270  817904650 1960697289 
+1584484509 2724312018 1978802819 2275314726 4216102886 
+2138332912  671754166 1442240992 3674442465 1085868016 
+2769242611 1003628378 1616076847  743729558  820011032 
+2559719034 1839332599 3121982280 2070268989 3769147733 
+ 518022934 3037227899 2531915367 1008310588  971468687 
+2052976098 1651926578   78218926 2503907441 3209763057 
+1081499040 2812016370 1247433164  335294964 2650385171 
+2030527826 1139372809 4279827824 3540669095 2285341455 
+4220507154 3863048231 3136394663 3319584205 1476940506 
+ 875141230 2508558662 3896001866  462864388 1609807693 
+3892563868 3642514037 3778083990 1403162576 3512254868 
+1403323269 1119818229 2831288053 2552740643 2520136409 
+  96690857  210381252 1826474872 3306977352 1343117402 
+2112059492  693571694 2096734379  767794921 1843084587 
+1816280216 1695342628  404711915 3334843684 2570639553 
+4186538211 2022604264 3214805180 2989079529 2725165355 
+3005995436  310011850 2742468706 2720274646  144327376 
+2271696819  295519962 1272030376 1372670420 1397272558 
+2280044719 2710639434 2810822904 4271368265 1750711132 
+2216408539 3521792518 3111505866 3085328191 1054735512 
+4160317205 1427385632 2282061755 3215251668 1396490078 
+2933318719  453673969 2926038256 2624047458  338625410 
+3344930154 1971116345 1818716442 2998517928  390083048 
+ 291563131 1144486353  296954266  659950561 2263631666 
+1206908601 1125491020 1890151284 2076080514 2264060846 
+ 561805191 1964622705  405620012 3759692386  517035386 
+2225016848 4165419081 4052828294 3248204933 2738939733 
+1151808775 4113264137 3113447491 1033828852 1785686386 
+2903923175 2038900010 1241522880  238119113 2885394101 
+2636011022 2985605703 2107193353  292026696 3884689974 
+1094315383 4016714705  962244585 3943968050 2868319718 
+1304919603 3626636694 3393461291 1479454799  971639318 
+3352306399 1928233566 2900529135 2190901098   28842068 
+ 990556577 2586302532 3057504668 1661169605 4228191763 
+3934152427 2814119472    4943754 1171095774 1986204006 
+2014406505 1822565279   12890078 1979620724 1917376192 
+3307810835 4170173371 1385005883 1308519769 3370429606 
+ 923886311 2024463563 1063369787  153599761 3463680785 
+ 755374878 2088947962 3099927142 1750207400 2033606872 
+ 926120766  655932557 2320365045 1465119024 3105365454 
+2608716819 1218456091  823539591 2331574954 3171519129 
+3246671799 1043031086 1425831588 3940307546 3443545749 
+1155610704 3681098065 3287797558   63959365  810297004 
+3800799806 1234795257 2547289014  391329364  370300179 
+2474800443 3972311925 2935022755 3924395679 2347599539 
+4212318274 1828491430 3865565525 2767860661 4078993078 
+2781496513 4013741232 2916354756   35752471 2730683119 
+3340599926 4059491907  111492530  897368671 2524912702 
+3046341697 2790787159 1014602604 1409764839  512802978 
+ 477082227 2608350570  533747000 1933326657 4182933327 
+1970210993 2290203137 2843031053 2844558050 3308351089 
+3041943368 1504174920  295229952 2843309586  884572473 
+1787387521 1861566286 3616058184   48071792 3577350513 
+ 297480282 1101405687 1473439254 2634793792 1341017984 
diff --git a/test/random-generator.test b/test/random-generator.test
new file mode 100755
index 00000000..88c9d31d
--- /dev/null
+++ b/test/random-generator.test
@@ -0,0 +1,68 @@
+#! /bin/bash
+# This script tests: pgmnoise
+# Also requires:
+
+echo "Test 1: Should produce:"
+
+echo "P2"
+echo "12 1"
+echo "1023"
+echo "720 296 192 858 101 57 298 629 804 1019 64 617"
+echo "Above output is for Mersenne Twister"
+
+# GNU libc rand(): 593 252 207 990 507 824 961 805 559 110 167 172
+# MAC OS rand():   9 782 60 418 364 654 670 172 1022 515 593 903 
+
+pgmnoise -maxval=1023 -randomseed=3791 -plain 12 1
+
+echo
+echo "Test 2: Mersenne Twister random number generator"
+echo "Should produce:"
+
+echo "3499211612  581869302 3890346734 3586334585  545404204"
+echo "4161255391 3922919429  949333985 2715962298 1323567403"
+echo " ... "
+echo " 297480282 1101405687 1473439254 2634793792 1341017984"
+echo " Total 1000 integers, 200 lines"
+echo
+
+# Use perl to avoid mawk limitation
+# (cannot convert 32 bit integers)
+
+perlPgmProcessorProgram='
+  if (($#F+1) == 10) {
+    for (my $i = 0; $i <= 9; $i += 2) {
+      my $r = $F[$i + 1] * 65536 + $F[$i];
+      printf "%10u ", $r;
+    }
+    print "";
+  }
+'
+
+pgmnoise -randomseed=5489 -plain -maxval=65535 10 200 | tee /tmp/z |
+  perl -walne "$perlPgmProcessorProgram"
+
+#    Method to generate output for Test 2 from original
+#    Mersenne Twister source code
+
+# Download Mersenne Twister code.  See lib/util/randmersenne.c for URL.
+# Edit mt19937ar.c:
+#   In function main() at bottom of file, replace
+#     init_by_array(init, length);
+#   with
+#     init_genrand(5489UL);
+#
+# We need only the output of genrand_int32().
+# Remove the second loop which produces double-precision floating point
+# random numbers with genrand_real2().
+#
+# Compile: gcc mt19937ar.c -o mt1000
+# Execute: ./mt1000
+
+# 1000 may seem like a large number of samples but there is a reason
+# for this.  The generator produces random integers in batches of 624.
+# The number of samples must be larger than 624 to ensure proper
+# generation in batches after the first.
+
+
+
diff --git a/test/winicon-roundtrip2.ok b/test/winicon-roundtrip2.ok
index bb8c77d8..54ba54e6 100644
--- a/test/winicon-roundtrip2.ok
+++ b/test/winicon-roundtrip2.ok
@@ -1,3 +1,4 @@
+Should print: former checksum values
 16 24 32 48 64 : 1 plane
 Should print 2588356089 8591 or 3783949470 69390 four times
 2588356089 8591
@@ -38,6 +39,7 @@ Should print 2704877198 33359 or 1699833476 276750 four times
 Should print 2567279592 41655 or 4154838752 345902 twice
 2567279592 41655
 2567279592 41655
+Should print: latter checksum values
 16 32 48 256 : 1 plane
 Should print 2588356089 8591 or 3783949470 69390 four times
 3783949470 69390
diff --git a/test/winicon-roundtrip2.test b/test/winicon-roundtrip2.test
index ec074055..9d55ebee 100755
--- a/test/winicon-roundtrip2.test
+++ b/test/winicon-roundtrip2.test
@@ -7,7 +7,7 @@ tmpdir=${tmpdir:-/tmp}
 test_pam=${tmpdir}/testimg.pam
 test1_pam=${tmpdir}/testimg1.pam
 test2_pam=${tmpdir}/testimg2.pam
-#test3_pam=${tmpdir}/testimg3.pam
+
 test4_pam=${tmpdir}/testimg4.pam
 test5_pam=${tmpdir}/testimg5.pam
 black_pam=${tmpdir}/black.pam
@@ -19,6 +19,10 @@ gray_pam=${tmpdir}/gray.pam
 
 for sizes in "16 24 32 48 64" "16 32 48 256"
   do
+  if  echo $sizes | awk '{exit !($NF==64)}' ;
+  then echo "Should print: former checksum values";
+  else echo "Should print: latter checksum values";
+  fi
 
   for size in ${sizes}
     do
@@ -65,11 +69,11 @@ for sizes in "16 24 32 48 64" "16 32 48 256"
   pamstack ${gray_pam} ${gray_pam} | \
   pamchannel -tupletype="GRAYSCALE_ALPHA" 0 1 | tee ${test2_pam} | cksum
   pamtowinicon ${test2_pam} | winicontopam -allimages | \
-    pamchannel -tupletype="GRAYSCALE_ALPHA" 0 1 | cksum
+    pamchannel -tupletype="GRAYSCALE_ALPHA" 0 1 | pamdepth 255 | cksum
   pamtowinicon -pngthreshold=300 ${test2_pam} | winicontopam -allimages | \
     pamchannel -tupletype="GRAYSCALE_ALPHA" 0 1 | cksum
   pamtowinicon -pngthreshold=1   ${test2_pam} | winicontopam -allimages | \
-    pamchannel -tupletype="GRAYSCALE_ALPHA" 0 1 | cksum
+    pamchannel -tupletype="GRAYSCALE_ALPHA" 0 1 | pamdepth 255 | cksum
 
   rm ${test2_pam}
 
diff --git a/version.mk b/version.mk
index daa09ece..21aa6e91 100644
--- a/version.mk
+++ b/version.mk
@@ -1,3 +1,3 @@
 NETPBM_MAJOR_RELEASE = 10
-NETPBM_MINOR_RELEASE = 93
-NETPBM_POINT_RELEASE = 3
+NETPBM_MINOR_RELEASE = 94
+NETPBM_POINT_RELEASE = 0