Path: utzoo!attcan!utgpu!jarvis.csri.toronto.edu!mailrus!wuarchive!brutus.cs.uiuc.edu!apple!well!pokey From: pokey@well.UUCP (Jef Poskanzer) Newsgroups: alt.sources Subject: PBMPLUS, part 14 of 18 Message-ID: <13620@well.UUCP> Date: 14 Sep 89 11:25:57 GMT Reply-To: Jef Poskanzer Organization: Paratheo-Anametamystikhood Of Eris Esoteric, Ada Lovelace Cabal Lines: 1400 #! /bin/sh # This is a shell archive, meaning: # 1. Remove everything above the #! /bin/sh line. # 2. Save the resulting text in a file. # 3. Execute the file with /bin/sh (not csh) to create the files: # ppm/ppmcscale.c # ppm/ppmcscale.1 # ppm/ilbmtoppm.c # ppm/ilbm.h # ppm/ilbmtoppm.1 # ppm/ppmquant.c # ppm/ppmquant.1 # ppm/ppmarith.c # ppm/ppmarith.1 # This archive created: Thu Sep 14 03:43:46 1989 # By: Jef Poskanzer (Paratheo-Anametamystikhood Of Eris Esoteric, Ada Lovelace Cabal) export PATH; PATH=/bin:$PATH if test ! -d 'ppm' then echo shar: creating directory "'ppm'" mkdir 'ppm' fi echo shar: extracting "'ppm/ppmcscale.c'" '(1631 characters)' if test -f 'ppm/ppmcscale.c' then echo shar: will not over-write existing file "'ppm/ppmcscale.c'" else sed 's/^X//' << \SHAR_EOF > 'ppm/ppmcscale.c' X/* ppmcscale.c - scale the colors in a portable pixmap X** X** Copyright (C) 1989 by Jef Poskanzer. X** X** Permission to use, copy, modify, and distribute this software and its X** documentation for any purpose and without fee is hereby granted, provided X** that the above copyright notice appear in all copies and that both that X** copyright notice and this permission notice appear in supporting X** documentation. This software is provided "as is" without express or X** implied warranty. X*/ X X#include X#include "ppm.h" X Xmain( argc, argv ) Xint argc; Xchar *argv[]; X { X FILE *ifd; X register pixel *pixelrow, *pP; X int argn, rows, cols, format, row; X register int col; X pixval maxval, newmaxval; X int i; X char *usage = "newmaxval [ppmfile]"; X X pm_progname = argv[0]; X X argn = 1; X X if ( argn == argc ) X pm_usage( usage ); X if ( sscanf( argv[argn], "%d", &i ) != 1 ) X pm_usage( usage ); X newmaxval = i; X argn++; X if ( newmaxval < 1 ) X pm_error( "newmaxval must be > 1", 0,0,0,0,0 ); X X if ( argn != argc ) X { X ifd = pm_openr( argv[argn] ); X argn++; X } X else X ifd = stdin; X X if ( argn != argc ) X pm_usage( usage ); X X ppm_readppminit( ifd, &cols, &rows, &maxval, &format ); X pixelrow = ppm_allocrow( cols ); X X ppm_writeppminit( stdout, cols, rows, newmaxval ); X X for ( row = 0; row < rows; row++ ) X { X ppm_readppmrow( ifd, pixelrow, cols, maxval, format ); X X for ( col = 0, pP = pixelrow; col < cols; col++, pP++ ) X PPM_CSCALE( *pP, *pP, maxval, newmaxval ); X X ppm_writeppmrow( stdout, pixelrow, cols, newmaxval ); X } X X pm_close( ifd ); X X exit( 0 ); X } SHAR_EOF if test 1631 -ne "`wc -c < 'ppm/ppmcscale.c'`" then echo shar: error transmitting "'ppm/ppmcscale.c'" '(should have been 1631 characters)' fi fi # end of overwriting check if test ! -d 'ppm' then echo shar: creating directory "'ppm'" mkdir 'ppm' fi echo shar: extracting "'ppm/ppmcscale.1'" '(803 characters)' if test -f 'ppm/ppmcscale.1' then echo shar: will not over-write existing file "'ppm/ppmcscale.1'" else sed 's/^X//' << \SHAR_EOF > 'ppm/ppmcscale.1' X.TH ppmcscale 1 "27 February 1989" X.SH NAME Xppmcscale - scale the colors in a portable pixmap X.SH SYNOPSIS Xppmcscale newmaxval [ppmfile] X.SH DESCRIPTION XReads a portable pixmap as input. XScales all the pixel values, and writes out the image with the new maxval. X.PP XScaling the colors down to a smaller maxval will result in some loss Xof information. X.SH "SEE ALSO" Xppmquant(1), ppm(5) X.SH AUTHOR XCopyright (C) 1989 by Jef Poskanzer. X XPermission to use, copy, modify, and distribute this software and its Xdocumentation for any purpose and without fee is hereby granted, provided Xthat the above copyright notice appear in all copies and that both that Xcopyright notice and this permission notice appear in supporting Xdocumentation. This software is provided "as is" without express or Ximplied warranty. SHAR_EOF if test 803 -ne "`wc -c < 'ppm/ppmcscale.1'`" then echo shar: error transmitting "'ppm/ppmcscale.1'" '(should have been 803 characters)' fi fi # end of overwriting check if test ! -d 'ppm' then echo shar: creating directory "'ppm'" mkdir 'ppm' fi echo shar: extracting "'ppm/ilbmtoppm.c'" '(8685 characters)' if test -f 'ppm/ilbmtoppm.c' then echo shar: will not over-write existing file "'ppm/ilbmtoppm.c'" else sed 's/^X//' << \SHAR_EOF > 'ppm/ilbmtoppm.c' X/* ilbmtoppm.c - read an Amiga IFF ILBM file and produce a portable pixmap X** X** Copyright (C) 1989 by Jef Poskanzer. X** X** Permission to use, copy, modify, and distribute this software and its X** documentation for any purpose and without fee is hereby granted, provided X** that the above copyright notice appear in all copies and that both that X** copyright notice and this permission notice appear in supporting X** documentation. This software is provided "as is" without express or X** implied warranty. X*/ X X#include X#include "ppm.h" X#include "ilbm.h" X Xmain( argc, argv ) Xint argc; Xchar *argv[]; X { X FILE *ifd; X pixel *pixelrow, *colormap = 0; X int argn, rows, cols = 0, row, col, colors, i, j, r, g, b, byte, bytes; X pixval maxval; X char iffid[5]; X unsigned char *body = 0, *bp, *ubp, *rawrow, *runbuf; X long formsize, bytesread, chunksize, viewportmodes = 0; X int nPlanes, masking, compression, xAsp, yAsp, ham, hammask, allPlanes; X unsigned char get_byte(); X short get_big_short(); X long get_big_long(); X X pm_progname = argv[0]; X X argn = 1; X X if ( argn < argc ) X { X ifd = pm_openr( argv[argn] ); X argn++; X } X else X ifd = stdin; X X if ( argn != argc ) X pm_usage( "[ilbmfile]" ); X X /* Read in the ILBM file. */ X iffid[4] = '\0'; X getfourchars( ifd, iffid ); X if ( strcmp( iffid, "FORM" ) != 0 ) X pm_error( "input is not a FORM type IFF file", 0,0,0,0,0 ); X formsize = get_big_long( ifd ); X getfourchars( ifd, iffid ); X if ( strcmp( iffid, "ILBM" ) != 0 ) X pm_error( "input is not an ILBM type FORM IFF file", 0,0,0,0,0 ); X bytesread = 12; X X /* Main loop, parsing the IFF FORM. */ X while ( bytesread < formsize ) X { X getfourchars( ifd, iffid ); X chunksize = get_big_long( ifd ); X bytesread += 8; X X if ( body != 0 ) X { X fprintf( X stderr, "%s: \"%s\" chunk found after BODY chunk -- skipping\n", X argv[0], iffid ); X for ( i = 0; i < chunksize; i++ ) X (void) get_byte( ifd ); X } X else if ( strcmp( iffid, "BMHD" ) == 0 ) X { X cols = get_big_short( ifd ); X rows = get_big_short( ifd ); X (void) get_big_short( ifd ); /* x */ X (void) get_big_short( ifd ); /* y */ X nPlanes = get_byte( ifd ); X masking = get_byte( ifd ); X compression = get_byte( ifd ); X (void) get_byte( ifd ); /* pad1 */ X (void) get_big_short( ifd ); /* transparentColor */ X xAsp = get_byte( ifd ); X yAsp = get_byte( ifd ); X (void) get_big_short( ifd ); /* pageWidth */ X (void) get_big_short( ifd ); /* pageHeight */ X } X else if ( strcmp( iffid, "CMAP" ) == 0 ) X { X colors = chunksize / 3; X if ( colors > 0 ) X { X colormap = ppm_allocrow( colors ); X for ( i = 0; i < colors; i++ ) X { X r = get_byte( ifd ); X g = get_byte( ifd ); X b = get_byte( ifd ); X PPM_ASSIGN( colormap[i], r, g, b ); X } X if ( colors * 3 != chunksize ) X (void) get_byte( ifd ); X } X } X else if ( strcmp( iffid, "CAMG" ) == 0 ) X { X viewportmodes = get_big_long( ifd ); X } X else if ( strcmp( iffid, "BODY" ) == 0 ) X { X body = (unsigned char *) malloc( chunksize ); X if ( body == 0 ) X pm_error( "out of memory", 0,0,0,0,0 ); X if ( fread( body, 1, chunksize, ifd ) != chunksize ) X pm_error( "premature EOF reading BODY chunk", 0,0,0,0,0 ); X } X else if ( strcmp( iffid, "GRAB" ) == 0 || X strcmp( iffid, "DEST" ) == 0 || X strcmp( iffid, "SPRT" ) == 0 || X strcmp( iffid, "CRNG" ) == 0 || X strcmp( iffid, "CCRT" ) == 0 || X strcmp( iffid, "DPPV" ) == 0 ) X { X for ( i = 0; i < chunksize; i++ ) X (void) get_byte( ifd ); X } X else X { X fprintf( X stderr, "%s: unknown chunk type \"%s\" -- skipping\n", X argv[0], iffid ); X for ( i = 0; i < chunksize; i++ ) X (void) get_byte( ifd ); X } X X bytesread += chunksize; X } X X pm_close( ifd ); X X /* Done reading. Now interpret what we got. */ X if ( cols == 0 ) X pm_error( "no BMHD chunk found", 0,0,0,0,0 ); X if ( body == 0 ) X pm_error( "no BODY chunk found", 0,0,0,0,0 ); X if ( xAsp != yAsp ) X fprintf( X stderr, X "(Warning: non-square pixels; to fix do a 'ppmscale -%cscale %g'.)\n", X xAsp > yAsp ? 'x' : 'y', X xAsp > yAsp ? (float) xAsp / yAsp : (float) yAsp / xAsp ); X if ( viewportmodes & vmHAM ) X { X ham = 1; X hammask = ( 1 << ( nPlanes - 2 ) ) - 1; X maxval = ( 1 << ( nPlanes - 2 ) ) - 1; X if ( colormap != 0 ) X for ( i = 0; i < colors; i++ ) X { X r = PPM_GETR( colormap[i] ) >> ( 10 - nPlanes ); X g = PPM_GETG( colormap[i] ) >> ( 10 - nPlanes ); X b = PPM_GETB( colormap[i] ) >> ( 10 - nPlanes ); X PPM_ASSIGN( colormap[i], r, g, b ); X } X } X else X { X ham = 0; X if ( colormap != 0 ) X maxval = 255; /* colormap contains bytes */ X else X maxval = ( 1 << nPlanes ) - 1; X } X if ( viewportmodes & vmEXTRA_HALFBRITE ) X { X pixel *tempcolormap; X X tempcolormap = ppm_allocrow( colors * 2 ); X for ( i = 0; i < colors; i++ ) X { X tempcolormap[i] = colormap[i]; X PPM_ASSIGN( X tempcolormap[colors + i], PPM_GETR(colormap[i]) / 2, X PPM_GETG(colormap[i]) / 2, PPM_GETB(colormap[i]) / 2 ); X } X ppm_freerow( colormap ); X colormap = tempcolormap; X colors *= 2; X } X if ( colormap == 0 ) X fprintf( X stderr, "(No colormap -- interpreting values as grayscale.)\n" ); X allPlanes = nPlanes + ( masking == mskHasMask ? 1 : 0 ); X X ppm_writeppminit( stdout, cols, rows, maxval ); X pixelrow = ppm_allocrow( cols ); X rawrow = (unsigned char *) malloc( cols ); X if ( rawrow == 0 ) X pm_error( "out of memory", 0,0,0,0,0 ); X runbuf = (unsigned char *) malloc( RowBytes( cols ) ); X if ( runbuf == 0 ) X pm_error( "out of memory", 0,0,0,0,0 ); X X bp = body; X for ( row = 0; row < rows; row++ ) X { X /* Extract rawrow from the image. */ X for ( col = 0; col < cols; col++ ) X rawrow[col] = 0; X for ( i = 0; i < allPlanes; i++ ) X { X switch ( compression ) X { X case cmpNone: X ubp = bp; X bp += RowBytes( cols ); X break; X X case cmpByteRun1: X ubp = runbuf; X bytes = RowBytes( cols ); X do X { X byte = *bp++; X if ( byte <= 127 ) X for ( j = byte, bytes -= j + 1; j >= 0; j-- ) X *ubp++ = *bp++; X else if ( byte != 128 ) X for ( j = 256 - byte, bytes -= j + 1, byte = *bp++; X j >= 0; j-- ) X *ubp++ = byte; X } X while ( bytes > 0 ); X if ( bytes < 0 ) X pm_error( "error doing ByteRun decompression", 0,0,0,0,0 ); X ubp = runbuf; X break; X X default: X pm_error( "unknown compression type", 0,0,0,0,0 ); X } X X if ( i >= nPlanes ) X continue; /* ignore mask plane */ X X for ( col = 0; col < cols; col++ ) X if ( ubp[col / 8] & ( 128 >> ( col % 8 ) ) ) X rawrow[col] |= 1 << i; X } X X /* Interpret rawrow into pixels. */ X r = g = b = 0; X for ( col = 0; col < cols; col++ ) X if ( ham ) X { /* HAM mode. */ X switch ( ( rawrow[col] >> nPlanes - 2 ) & 0x3 ) X { X case 0: X if ( colormap != 0 && colors >= maxval ) X pixelrow[col] = colormap[rawrow[col] & hammask]; X else X PPM_ASSIGN( X pixelrow[col], rawrow[col] & hammask, X rawrow[col] & hammask, rawrow[col] & hammask ); X r = PPM_GETR( pixelrow[col] ); X g = PPM_GETG( pixelrow[col] ); X b = PPM_GETB( pixelrow[col] ); X break; X X case 1: X b = rawrow[col] & hammask; X PPM_ASSIGN( pixelrow[col], r, g, b ); X break; X X case 2: X r = rawrow[col] & hammask; X PPM_ASSIGN( pixelrow[col], r, g, b ); X break; X X case 3: X g = rawrow[col] & hammask; X PPM_ASSIGN( pixelrow[col], r, g, b ); X break; X X default: X pm_error( "impossible HAM code", 0,0,0,0,0 ); X } X } X else if ( colormap != 0 ) X /* Non-HAM colormapped. */ X pixelrow[col] = colormap[rawrow[col]]; X else X /* Non-HAM direct -- weird. */ X PPM_ASSIGN( X pixelrow[col], rawrow[col], rawrow[col], rawrow[col] ); X X /* And write out the row. */ X ppm_writeppmrow( stdout, pixelrow, cols, maxval ); X } X X exit( 0 ); X } X Xunsigned char Xget_byte( f ) XFILE *f; X { X int i; X X i = getc( f ); X if ( i == EOF ) X pm_error( "premature EOF", 0,0,0,0,0 ); X X return (unsigned char) i; X } X Xgetfourchars( f, fourchars ) XFILE *f; Xchar fourchars[4]; X { X fourchars[0] = get_byte( f ); X fourchars[1] = get_byte( f ); X fourchars[2] = get_byte( f ); X fourchars[3] = get_byte( f ); X } X Xshort Xget_big_short( f ) XFILE *f; X { X short s; X X s = get_byte( f ) << 8; X s |= get_byte( f ); X X return s; X } X Xlong Xget_big_long( f ) XFILE *f; X { X long l; X X l = get_byte( f ) << 24; X l |= get_byte( f ) << 16; X l |= get_byte( f ) << 8; X l |= get_byte( f ); X X return l; X } SHAR_EOF if test 8685 -ne "`wc -c < 'ppm/ilbmtoppm.c'`" then echo shar: error transmitting "'ppm/ilbmtoppm.c'" '(should have been 8685 characters)' fi fi # end of overwriting check if test ! -d 'ppm' then echo shar: creating directory "'ppm'" mkdir 'ppm' fi echo shar: extracting "'ppm/ilbm.h'" '(702 characters)' if test -f 'ppm/ilbm.h' then echo shar: will not over-write existing file "'ppm/ilbm.h'" else sed 's/^X//' << \SHAR_EOF > 'ppm/ilbm.h' X/* ilbm.h - header file for Amiga IFF ILBM files X*/ X X#define RowBytes(cols) ( ( ( (cols) + 15 ) / 16 ) * 2 ) X X/* definitions for BMHD */ X Xtypedef struct X { X unsigned short w, h; X short x, y; X unsigned char nPlanes, masking, compression, pad1; X unsigned short transparentColor; X unsigned char xAspect, yAspect; X short pageWidth, pageHeight; X } BitMapHeader; X X#define mskNone 0 X#define mskHasMask 1 X#define mskHasTransparentColor 2 X#define mskLasso 3 X X#define cmpNone 0 X#define cmpByteRun1 1 X X/* definitions for CMAP */ X Xtypedef struct X { X unsigned char r, g, b; X } ColorRegister; X X/* definitions for CAMG */ X X#define vmEXTRA_HALFBRITE 0x80 X#define vmHAM 0x800 SHAR_EOF if test 702 -ne "`wc -c < 'ppm/ilbm.h'`" then echo shar: error transmitting "'ppm/ilbm.h'" '(should have been 702 characters)' fi fi # end of overwriting check if test ! -d 'ppm' then echo shar: creating directory "'ppm'" mkdir 'ppm' fi echo shar: extracting "'ppm/ilbmtoppm.1'" '(716 characters)' if test -f 'ppm/ilbmtoppm.1' then echo shar: will not over-write existing file "'ppm/ilbmtoppm.1'" else sed 's/^X//' << \SHAR_EOF > 'ppm/ilbmtoppm.1' X.TH ilbmtoppm 1 "30 March 1989" X.SH NAME Xilbmtoppm - convert Amiga IFF ILBM file into a portable pixmap X.SH SYNOPSIS Xilbmtoppm [ilbmfile] X.SH DESCRIPTION XReads an Amiga IFF ILBM file as input. XProduces a portable pixmap as output. XHandles HAM and EXTRA_HALFBRIGHT, no problem. X.SH "SEE ALSO" Xppm(5) X.SH AUTHOR XCopyright (C) 1989 by Jef Poskanzer. X XPermission to use, copy, modify, and distribute this software and its Xdocumentation for any purpose and without fee is hereby granted, provided Xthat the above copyright notice appear in all copies and that both that Xcopyright notice and this permission notice appear in supporting Xdocumentation. This software is provided "as is" without express or Ximplied warranty. SHAR_EOF if test 716 -ne "`wc -c < 'ppm/ilbmtoppm.1'`" then echo shar: error transmitting "'ppm/ilbmtoppm.1'" '(should have been 716 characters)' fi fi # end of overwriting check if test ! -d 'ppm' then echo shar: creating directory "'ppm'" mkdir 'ppm' fi echo shar: extracting "'ppm/ppmquant.c'" '(14526 characters)' if test -f 'ppm/ppmquant.c' then echo shar: will not over-write existing file "'ppm/ppmquant.c'" else sed 's/^X//' << \SHAR_EOF > 'ppm/ppmquant.c' X/* ppmquant.c - quantize the colors in a pixmap down to a specified number X** X** Copyright (C) 1989 by Jef Poskanzer. X** X** Permission to use, copy, modify, and distribute this software and its X** documentation for any purpose and without fee is hereby granted, provided X** that the above copyright notice appear in all copies and that both that X** copyright notice and this permission notice appear in supporting X** documentation. This software is provided "as is" without express or X** implied warranty. X*/ X X#include X#ifdef SYSV X#include X#define srandom srand X#define random rand X#else SYSV X#include X#endif SYSV X#include "ppm.h" X#include "ppmcmap.h" X X#define min(a,b) ((a) < (b) ? (a) : (b)) X#define max(a,b) ((a) > (b) ? (a) : (b)) X X#define MAXCOLORS 32768 X#define CLUSTER_MAXVAL 63 X Xmain( argc, argv ) Xint argc; Xchar *argv[]; X { X FILE *ifd; X register pixel **pixels, *pP; X int argn, rows, cols, row; X register int col, limitcol; X pixval maxval; X int newcolors, colors; X register int index; X colorhist_vector chv, colormap, mediancut(); X colorhash_table cht; X int floyd; X long *thisrerr, *nextrerr, *thisgerr, *nextgerr, *thisberr, *nextberr, X *temperr; X register long sr, sg, sb; X#define FS_SCALE 1024 X int fs_direction; X pixval err; X char *usage = "[-floyd|-fs] [ppmfile]"; X X pm_progname = argv[0]; X X argn = 1; X floyd = 0; X X if ( argn < argc && argv[argn][0] == '-' ) X { X if ( strncmp(argv[argn],"-fs",max(strlen(argv[argn]),2)) == 0 || X strncmp(argv[argn],"-floyd",max(strlen(argv[argn]),2)) == 0 ) X floyd = 1; X else X pm_usage( usage ); X argn++; X } X X if ( argn == argc ) X pm_usage( usage ); X if ( sscanf( argv[argn], "%d", &newcolors ) != 1 ) X pm_usage( usage ); X argn++; X if ( newcolors <= 1 ) X pm_error( "number of colors must be > 1", 0,0,0,0,0 ); X X if ( argn != argc ) X { X ifd = pm_openr( argv[argn] ); X argn++; X } X else X ifd = stdin; X X if ( argn != argc ) X pm_usage( usage ); X X /* X ** Step 0: read in the image. X */ X pixels = ppm_readppm( ifd, &cols, &rows, &maxval ); X pm_close( ifd ); X X X /* X ** Step 1: attempt to make a histogram of the colors, unclustered. X */ X fprintf( stderr, "(Making histogram... " ); X fflush( stderr ); X chv = ppm_computecolorhist( pixels, cols, rows, MAXCOLORS, &colors ); X if ( chv == (colorhist_vector) 0 ) X { X fprintf( stderr, "Too many colors.)\n" ); X /* X ** Step 2: try lowering maxval, to increase color coherence. X */ X if ( maxval <= CLUSTER_MAXVAL ) X { /* (This is not likely to happen.) */ X fprintf( X stderr, "(Try recompiling with a smaller CLUSTER_MAXVAL.)\n" ); X exit( 1 ); X } X fprintf( X stderr, X "(Scaling colors from maxval=%d to maxval=%d to improve clustering... ", X maxval, CLUSTER_MAXVAL ); X fflush( stderr ); X for ( row = 0; row < rows; row++ ) X for ( col = 0, pP = pixels[row]; col < cols; col++, pP++ ) X PPM_CSCALE( *pP, *pP, maxval, CLUSTER_MAXVAL ); X maxval = CLUSTER_MAXVAL; X fprintf( stderr, "Done.)\n" ); X X fprintf( stderr, "(Trying histogram again... " ); X fflush( stderr ); X chv = ppm_computecolorhist( pixels, cols, rows, MAXCOLORS, &colors ); X if ( chv == (colorhist_vector) 0 ) X { X fprintf( X stderr, "Still too many colors - try recompiling with a smaller CLUSTER_MAXVAL.)\n" ); X exit( 1 ); X } X } X fprintf( stderr, "Done. %d colors found.)\n", colors ); X X /* X ** Step 3: apply median-cut to histogram, making the new colormap. X */ X fprintf( stderr, "(Choosing %d colors... ", newcolors ); X fflush( stderr ); X colormap = mediancut( chv, colors, rows * cols, maxval, newcolors ); X ppm_freecolorhist( chv ); X fprintf( stderr, "Done.)\n" ); X X /* X ** Step 4: map the colors in the image to their closest match in the X ** new colormap, and write 'em out. X */ X fprintf( stderr, "(Mapping image to new colors... " ); X fflush( stderr ); X cht = ppm_alloccolorhash( ); X ppm_writeppminit( stdout, cols, rows, maxval ); X if ( floyd ) X { X /* Initialize Floyd-Steinberg error vectors. */ X thisrerr = (long *) pm_allocrow( cols + 2, sizeof(long) ); X nextrerr = (long *) pm_allocrow( cols + 2, sizeof(long) ); X thisgerr = (long *) pm_allocrow( cols + 2, sizeof(long) ); X nextgerr = (long *) pm_allocrow( cols + 2, sizeof(long) ); X thisberr = (long *) pm_allocrow( cols + 2, sizeof(long) ); X nextberr = (long *) pm_allocrow( cols + 2, sizeof(long) ); X srandom( (int) time( 0 ) ); X for ( col = 0; col < cols + 2; col++ ) X { X thisrerr[col] = random( ) % ( FS_SCALE * 2 ) - FS_SCALE; X thisgerr[col] = random( ) % ( FS_SCALE * 2 ) - FS_SCALE; X thisberr[col] = random( ) % ( FS_SCALE * 2 ) - FS_SCALE; X /* (random errors in [-1 .. 1]) */ X } X fs_direction = 1; X } X for ( row = 0; row < rows; row++ ) X { X if ( floyd ) X for ( col = 0; col < cols + 2; col++ ) X nextrerr[col] = nextgerr[col] = nextberr[col] = 0; X if ( ( ! floyd ) || fs_direction ) X { X col = 0; X limitcol = cols; X pP = pixels[row]; X } X else X { X col = cols - 1; X limitcol = -1; X pP = &(pixels[row][col]); X } X do X { X if ( floyd ) X { X /* Use Floyd-Steinberg errors to adjust actual color. */ X sr = PPM_GETR(*pP) * FS_SCALE + thisrerr[col + 1]; X sg = PPM_GETG(*pP) * FS_SCALE + thisgerr[col + 1]; X sb = PPM_GETB(*pP) * FS_SCALE + thisberr[col + 1]; X PPM_ASSIGN( *pP, sr / FS_SCALE, sg / FS_SCALE, sb / FS_SCALE ); X } X X /* Check hash table to see if we have already matched this color. */ X index = ppm_lookupcolor( cht, *pP ); X if ( index == -1 ) X { /* No; search colormap for closest match. */ X register int i, r1, g1, b1, r2, g2, b2; X register long dist, newdist; X r1 = PPM_GETR( *pP ); X g1 = PPM_GETG( *pP ); X b1 = PPM_GETB( *pP ); X dist = 2000000000; X for ( i = 0; i < newcolors; i++ ) X { X r2 = PPM_GETR( colormap[i].color ); X g2 = PPM_GETG( colormap[i].color ); X b2 = PPM_GETB( colormap[i].color ); X newdist = ( r1 - r2 ) * ( r1 - r2 ) + X ( g1 - g2 ) * ( g1 - g2 ) + X ( b1 - b2 ) * ( b1 - b2 ); X if ( newdist < dist ) X { X index = i; X dist = newdist; X } X } X ppm_addtocolorhash( cht, *pP, index ); X } X X if ( floyd ) X { X /* Propagate Floyd-Steinberg error terms. */ X if ( fs_direction ) X { X err = sr - PPM_GETR( colormap[index].color ) * FS_SCALE; X thisrerr[col + 2] += ( err * 7 ) / 16; X nextrerr[col ] += ( err * 3 ) / 16; X nextrerr[col + 1] += ( err * 5 ) / 16; X nextrerr[col + 2] += ( err ) / 16; X err = sg - PPM_GETG( colormap[index].color ) * FS_SCALE; X thisgerr[col + 2] += ( err * 7 ) / 16; X nextgerr[col ] += ( err * 3 ) / 16; X nextgerr[col + 1] += ( err * 5 ) / 16; X nextgerr[col + 2] += ( err ) / 16; X err = sb - PPM_GETB( colormap[index].color ) * FS_SCALE; X thisberr[col + 2] += ( err * 7 ) / 16; X nextberr[col ] += ( err * 3 ) / 16; X nextberr[col + 1] += ( err * 5 ) / 16; X nextberr[col + 2] += ( err ) / 16; X } X else X { X err = sr - PPM_GETR( colormap[index].color ) * FS_SCALE; X thisrerr[col ] += ( err * 7 ) / 16; X nextrerr[col + 2] += ( err * 3 ) / 16; X nextrerr[col + 1] += ( err * 5 ) / 16; X nextrerr[col ] += ( err ) / 16; X err = sg - PPM_GETG( colormap[index].color ) * FS_SCALE; X thisgerr[col ] += ( err * 7 ) / 16; X nextgerr[col + 2] += ( err * 3 ) / 16; X nextgerr[col + 1] += ( err * 5 ) / 16; X nextgerr[col ] += ( err ) / 16; X err = sb - PPM_GETB( colormap[index].color ) * FS_SCALE; X thisberr[col ] += ( err * 7 ) / 16; X nextberr[col + 2] += ( err * 3 ) / 16; X nextberr[col + 1] += ( err * 5 ) / 16; X nextberr[col ] += ( err ) / 16; X } X } X X *pP = colormap[index].color; X X if ( ( ! floyd ) || fs_direction ) X { X col++; X pP++; X } X else X { X col--; X pP--; X } X } X while ( col != limitcol ); X X if ( floyd ) X { X temperr = thisrerr; X thisrerr = nextrerr; X nextrerr = temperr; X temperr = thisgerr; X thisgerr = nextgerr; X nextgerr = temperr; X temperr = thisberr; X thisberr = nextberr; X nextberr = temperr; X fs_direction = ! fs_direction; X } X X ppm_writeppmrow( stdout, pixels[row], cols, maxval ); X } X fprintf( stderr, "Done.)\n" ); X X exit( 0 ); X } X X/* X** Here is the fun part, the median-cut colormap generator. This is based X** on Paul Heckbert's paper "Color Image Quantization for Frame Buffer X** Display", SIGGRAPH '82 Proceedings, page 297. X*/ X Xtypedef struct box *box_vector; Xstruct box X { X int index; X int colors; X int sum; X }; X Xcolorhist_vector Xmediancut( chv, colors, sum, maxval, newcolors ) Xcolorhist_vector chv; Xint colors, sum, newcolors; Xpixval maxval; X { X colorhist_vector colormap; X box_vector bv; X register int bi, i; X int boxes; X int redcompare(), greencompare(), bluecompare(), sumcompare(); X X bv = (box_vector) malloc( sizeof(struct box) * newcolors ); X colormap = X (colorhist_vector) malloc( sizeof(struct colorhist_item) * newcolors ); X if ( bv == (box_vector) 0 || colormap == (colorhist_vector) 0 ) X pm_error( "out of memory", 0,0,0,0,0 ); X for ( i = 0; i < newcolors; i++ ) X PPM_ASSIGN( colormap[i].color, 0, 0, 0 ); X X /* X ** Set up the initial box. X */ X bv[0].index = 0; X bv[0].colors = colors; X bv[0].sum = sum; X boxes = 1; X X /* X ** Main loop: split boxes until we have enough. X */ X while ( boxes < newcolors ) X { X register int indx, clrs; X int sm; X register int minr, maxr, ming, maxg, minb, maxb, v; X int halfsum, lowersum; X X /* X ** Find the first splittable box. X */ X for ( bi = 0; bv[bi].colors < 2 && bi < boxes; bi++ ) X ; X if ( bi == boxes ) X break; /* ran out of colors! */ X indx = bv[bi].index; X clrs = bv[bi].colors; X sm = bv[bi].sum; X X /* X ** Go through the box finding the minimum and maximum of each X ** component -- the boundaries of the box. X */ X minr = maxr = PPM_GETR( chv[indx].color ); X ming = maxg = PPM_GETG( chv[indx].color ); X minb = maxb = PPM_GETB( chv[indx].color ); X for ( i = 1; i < clrs; i++ ) X { X v = PPM_GETR( chv[indx + i].color ); X if ( v < minr ) minr = v; X if ( v > maxr ) maxr = v; X v = PPM_GETG( chv[indx + i].color ); X if ( v < ming ) ming = v; X if ( v > maxg ) maxg = v; X v = PPM_GETB( chv[indx + i].color ); X if ( v < minb ) minb = v; X if ( v > maxb ) maxb = v; X } X X /* X ** Find the largest dimension, and sort by that component. X */ X if ( maxr - minr >= maxg - ming && maxr - minr >= maxb - minb ) X qsort( X (char *) &(chv[indx]), clrs, sizeof(struct colorhist_item), X redcompare ); X else if ( maxg - ming >= maxb - minb ) X qsort( X (char *) &(chv[indx]), clrs, sizeof(struct colorhist_item), X greencompare ); X else X qsort( X (char *) &(chv[indx]), clrs, sizeof(struct colorhist_item), X bluecompare ); X X /* X ** Now find the median based on the counts, so that about half the X ** pixels (not colors, pixels) are in each subdivision. X */ X lowersum = chv[indx].value; X halfsum = sm / 2; X for ( i = 1; i < clrs - 1; i++ ) X { X if ( lowersum >= halfsum ) X break; X lowersum += chv[indx + i].value; X } X X /* X ** Split the box, and sort to bring the biggest box to the top. X */ X bv[bi].colors = i; X bv[bi].sum = lowersum; X bv[boxes].index = indx + i; X bv[boxes].colors = clrs - i; X bv[boxes].sum = sm - lowersum; X boxes++; X qsort( (char *) bv, boxes, sizeof(struct box), sumcompare ); X } X X /* X ** Ok, we've got enough boxes. Now choose a representative color for X ** each box. There are a number of possible ways to make this choice. X ** One would be to choose the center of the box; this ignores any structure X ** within the boxes. Another method would be to average all the colors in X ** the box -- this is the method specified in Heckbert's paper. A third X ** method is to average all the pixels in the box. X */ X/* #define CENTER_BOX */ X/* #define AVERAGE_COLORS */ X#define AVERAGE_PIXELS X for ( bi = 0; bi < boxes; bi++ ) X { X#ifdef CENTER_BOX X register int indx = bv[bi].index; X register int clrs = bv[bi].colors; X register int minr, maxr, ming, maxg, minb, maxb, v; X X minr = maxr = PPM_GETR( chv[indx].color ); X ming = maxg = PPM_GETG( chv[indx].color ); X minb = maxb = PPM_GETB( chv[indx].color ); X for ( i = 1; i < clrs; i++ ) X { X v = PPM_GETR( chv[indx + i].color ); X minr = min( minr, v ); X maxr = max( maxr, v ); X v = PPM_GETG( chv[indx + i].color ); X ming = min( ming, v ); X maxg = max( maxg, v ); X v = PPM_GETB( chv[indx + i].color ); X minb = min( minb, v ); X maxb = max( maxb, v ); X } X PPM_ASSIGN( X colormap[bi].color, ( minr + maxr ) / 2, ( ming + maxg ) / 2, X ( minb + maxb ) / 2 ); X#endif CENTER_BOX X#ifdef AVERAGE_COLORS X register int indx = bv[bi].index; X register int clrs = bv[bi].colors; X register long r = 0, g = 0, b = 0; X X for ( i = 0; i < clrs; i++ ) X { X r += PPM_GETR( chv[indx + i].color ); X g += PPM_GETG( chv[indx + i].color ); X b += PPM_GETB( chv[indx + i].color ); X } X r = r / clrs; X g = g / clrs; X b = b / clrs; X PPM_ASSIGN( colormap[bi].color, r, g, b ); X#endif AVERAGE_COLORS X#ifdef AVERAGE_PIXELS X register int indx = bv[bi].index; X register int clrs = bv[bi].colors; X register long r = 0, g = 0, b = 0, sum = 0; X X for ( i = 0; i < clrs; i++ ) X { X r += PPM_GETR( chv[indx + i].color ) * chv[indx + i].value; X g += PPM_GETG( chv[indx + i].color ) * chv[indx + i].value; X b += PPM_GETB( chv[indx + i].color ) * chv[indx + i].value; X sum += chv[indx + i].value; X } X r = r / sum; X if ( r > maxval ) r = maxval; /* avoid math errors */ X g = g / sum; X if ( g > maxval ) g = maxval; X b = b / sum; X if ( b > maxval ) b = maxval; X PPM_ASSIGN( colormap[bi].color, r, g, b ); X#endif AVERAGE_PIXELS X } X X /* X ** All done. X */ X return colormap; X } X Xint Xredcompare( ch1, ch2 ) Xcolorhist_vector ch1, ch2; X { X return (int) PPM_GETR( ch1->color ) - (int) PPM_GETR( ch2->color ); X } X Xint Xgreencompare( ch1, ch2 ) Xcolorhist_vector ch1, ch2; X { X return (int) PPM_GETG( ch1->color ) - (int) PPM_GETG( ch2->color ); X } X Xint Xbluecompare( ch1, ch2 ) Xcolorhist_vector ch1, ch2; X { X return (int) PPM_GETB( ch1->color ) - (int) PPM_GETB( ch2->color ); X } X Xint Xsumcompare( b1, b2 ) Xbox_vector b1, b2; X { X return b2->sum - b1->sum; X } SHAR_EOF if test 14526 -ne "`wc -c < 'ppm/ppmquant.c'`" then echo shar: error transmitting "'ppm/ppmquant.c'" '(should have been 14526 characters)' fi fi # end of overwriting check if test ! -d 'ppm' then echo shar: creating directory "'ppm'" mkdir 'ppm' fi echo shar: extracting "'ppm/ppmquant.1'" '(1258 characters)' if test -f 'ppm/ppmquant.1' then echo shar: will not over-write existing file "'ppm/ppmquant.1'" else sed 's/^X//' << \SHAR_EOF > 'ppm/ppmquant.1' X.TH ppmquant 1 "18 May 1989" X.SH NAME Xppmquant - quantize the colors in a portable pixmap down to a specified number X.SH SYNOPSIS Xppmquant [-floyd|-fs] [ppmfile] X.SH DESCRIPTION XReads a portable pixmap as input. XChooses colors to best represent the image, maps the existing colors Xto the new ones, and writes a portable pixmap as output. X.PP XThe quantization method is Heckbert's "median cut". X.PP XThe -floyd flag adds a Floyd-Steinberg error diffusion step. XThis may give better results on images where the unmodified quantization Xhas banding or other artifacts. XIt takes maybe 20% more CPU time. X.PP XAll flags can be abbreviated to their shortest unique prefix. X.SH REFERENCES X"Color Image Quantization for Frame Buffer Display" by Paul Heckbert, XSIGGRAPH '82 Proceedings, page 297. X.SH "SEE ALSO" Xppmcscale(1), ppm(5) X.SH AUTHOR XCopyright (C) 1989 by Jef Poskanzer. X XPermission to use, copy, modify, and distribute this software and its Xdocumentation for any purpose and without fee is hereby granted, provided Xthat the above copyright notice appear in all copies and that both that Xcopyright notice and this permission notice appear in supporting Xdocumentation. This software is provided "as is" without express or Ximplied warranty. SHAR_EOF if test 1258 -ne "`wc -c < 'ppm/ppmquant.1'`" then echo shar: error transmitting "'ppm/ppmquant.1'" '(should have been 1258 characters)' fi fi # end of overwriting check if test ! -d 'ppm' then echo shar: creating directory "'ppm'" mkdir 'ppm' fi echo shar: extracting "'ppm/ppmarith.c'" '(3424 characters)' if test -f 'ppm/ppmarith.c' then echo shar: will not over-write existing file "'ppm/ppmarith.c'" else sed 's/^X//' << \SHAR_EOF > 'ppm/ppmarith.c' X/* ppmarith.c - perform arithmetic on two portable pixmaps X** X** Copyright (C) 1989 by Jef Poskanzer. X** X** Permission to use, copy, modify, and distribute this software and its X** documentation for any purpose and without fee is hereby granted, provided X** that the above copyright notice appear in all copies and that both that X** copyright notice and this permission notice appear in supporting X** documentation. This software is provided "as is" without express or X** implied warranty. X*/ X X#include X#ifdef SYSV X#include X#else SYSV X#include X#endif SYSV X#include "ppm.h" X X#define max(a,b) ((a) > (b) ? (a) : (b)) X Xmain( argc, argv ) Xint argc; Xchar *argv[]; X { X FILE *ifd1, *ifd2; X register pixel *pixrow1, *pixrow2, *p1P, *p2P; X pixval maxval1, maxval2, maxval3; X int argn, rows1, cols1, format1, rows2, cols2, format2, row, col; X char function; X char *usage = "-add|-subtract|-multiply ppmfile1 ppmfile2"; X X pm_progname = argv[0]; X X argn = 1; X function = ' '; X X /* Check for flags. */ X if ( argn < argc && argv[argn][0] == '-' ) X { X if ( strncmp(argv[argn],"-add",max(strlen(argv[argn]),2)) == 0 ) X function = '+'; X else if ( strncmp(argv[argn],"-subtract",max(strlen(argv[argn]),2)) == 0 ) X function = '-'; X else if ( strncmp(argv[argn],"-multiply",max(strlen(argv[argn]),2)) == 0 ) X function = '*'; X else X pm_usage( usage ); X argn++; X } X X if ( function == ' ' ) X pm_usage( usage ); X X if ( argn == argc ) X pm_usage( usage ); X ifd1 = pm_openr( argv[argn] ); X argn++; X X if ( argn == argc ) X pm_usage( usage ); X ifd2 = pm_openr( argv[argn] ); X argn++; X X if ( argn != argc ) X pm_usage( usage ); X X ppm_readppminit( ifd1, &cols1, &rows1, &maxval1, &format1 ); X pixrow1 = ppm_allocrow( cols1 ); X ppm_readppminit( ifd2, &cols2, &rows2, &maxval2, &format2 ); X if ( cols2 != cols1 || rows2 != rows1 ) X pm_error( X "the two pixmaps must be the same width and height", 0,0,0,0,0 ); X pixrow2 = ppm_allocrow( cols1 ); X X maxval3 = max( maxval1, maxval2 ); X ppm_writeppminit( stdout, cols1, rows1, maxval3 ); X for ( row = 0; row < rows1; row++ ) X { X ppm_readppmrow( ifd1, pixrow1, cols1, maxval1, format1 ); X ppm_readppmrow( ifd2, pixrow2, cols1, maxval2, format2 ); X for ( col = 0, p1P = pixrow1, p2P = pixrow2; col < cols1; col++, p1P++, p2P++ ) X { X int r1, g1, b1, r2, g2, b2; X X if ( maxval1 != maxval3 ) X PPM_CSCALE( *p1P, *p1P, maxval1, maxval3 ); X r1 = PPM_GETR( *p1P ); X g1 = PPM_GETG( *p1P ); X b1 = PPM_GETB( *p1P ); X if ( maxval2 != maxval3 ) X PPM_CSCALE( *p2P, *p2P, maxval2, maxval3 ); X r2 = PPM_GETR( *p2P ); X g2 = PPM_GETG( *p2P ); X b2 = PPM_GETB( *p2P ); X switch ( function ) X { X case '+': X r1 += r2; X g1 += g2; X b1 += b2; X break; X X case '-': X r1 -= r2; X g1 -= g2; X b1 -= b2; X break; X X case '*': X r1 = r1 * r2 / maxval3; X g1 = g1 * g2 / maxval3; X b1 = b1 * b2 / maxval3; X break; X X default: X pm_error( "can't happen", 0,0,0,0,0 ); X } X if ( r1 < 0 ) r1 = 0; X else if ( r1 > maxval3 ) r1 = maxval3; X if ( g1 < 0 ) g1 = 0; X else if ( g1 > maxval3 ) g1 = maxval3; X if ( b1 < 0 ) b1 = 0; X else if ( b1 > maxval3 ) b1 = maxval3; X PPM_ASSIGN( *p1P, r1, g1, b1 ); X } X ppm_writeppmrow( stdout, pixrow1, cols1, maxval3 ); X } X X pm_close( ifd1 ); X pm_close( ifd2 ); X X exit( 0 ); X } SHAR_EOF if test 3424 -ne "`wc -c < 'ppm/ppmarith.c'`" then echo shar: error transmitting "'ppm/ppmarith.c'" '(should have been 3424 characters)' fi fi # end of overwriting check if test ! -d 'ppm' then echo shar: creating directory "'ppm'" mkdir 'ppm' fi echo shar: extracting "'ppm/ppmarith.1'" '(1098 characters)' if test -f 'ppm/ppmarith.1' then echo shar: will not over-write existing file "'ppm/ppmarith.1'" else sed 's/^X//' << \SHAR_EOF > 'ppm/ppmarith.1' X.TH ppmarith 1 "08 August 1989" X.SH NAME Xppmarith - perform arithmetic on two portable pixmaps X.SH SYNOPSIS Xppmarith -add|-subtract|-multiply ppmfile1 ppmfile2 X.SH DESCRIPTION XReads two portable bitmaps as input. XPerforms the specified arithmetic operation, Xand produces a portable bitmap as output. XThe two input pixmaps must be the same width and height. X.PP XThe arithmetic is performed between corresponding pixels in the two Xbitmaps, with maxval as 1.0, black as 0.0, and a linear scale in between. XResults that fall outside of [0..1) are truncated. X.PP XAll flags can be abbreviated to their shortest unique prefix. X.SH "SEE ALSO" Xpbmmask(1), pbmpaste(1), pnminvert(1), ppm(5) X.SH AUTHOR XCopyright (C) 1989 by Jef Poskanzer. X XPermission to use, copy, modify, and distribute this software and its Xdocumentation for any purpose and without fee is hereby granted, provided Xthat the above copyright notice appear in all copies and that both that Xcopyright notice and this permission notice appear in supporting Xdocumentation. This software is provided "as is" without express or Ximplied warranty. SHAR_EOF if test 1098 -ne "`wc -c < 'ppm/ppmarith.1'`" then echo shar: error transmitting "'ppm/ppmarith.1'" '(should have been 1098 characters)' fi fi # end of overwriting check # End of shell archive exit 0