[alt.graphics.pixutils] ppmtosixel converter and man page

rpvinci@leland.Stanford.EDU (Richard Vinci) (05/12/91)

This is a no-frills converter from ppm format to color sixel format (i.e. for a
DEC LJ250 color inkjet printer). It requires code from Jef Poskanzer's PBMPlus
utilities to compile. This software is provided "as is" without express or
implied warranty. If you find a bug or have an improvement (and boy, is there
room for that!) please let me know (rpvinci@leland.stanford.edu).

-------- CUT HERE FOR README --------
This is a no-frills converter from ppm format to color sixel format (i.e. for a
DEC LJ250 color inkjet printer). It requires code from Jef Poskanzer's PBMPlus
utilities to compile. This software is provided "as is" without express or
implied warranty. If you find a bug or have an improvement (and boy, is there
room for that!) please let me know (rpvinci@leland.stanford.edu).

There are three segments of this posting that must be separated into individual
files: (1) this README, (2) ppmtosix.1, the man page for the converter, and 
(3) ppmtosix.c itself.

Compile ppmtosix.c by moving it to the pbmplus/ppm source directory and enter
the appropriately altered line from the ppm Makefile. On my DECstation 3100, 
I use: 

gcc -fcombine-regs -fpcc-struct-return -O -s -DRGB_DB=\"/usr/lib/X11/rgb.txt\"
 -o ppmtosix ppmtosix.c -lm libppm.a ../pgm/libpgm.a ../pbm/libpbm.a -I../pgm
 -I../pbm

This is set up so that ppmtosix can be easily bundled as part of the ppm
utilities.

Copy ppmtosix.1 to the appropriate man directory (e.g. /usr/man/man1).

The man page is fairly explicit about the operation and limitations of ppmtosix
and I won't repeat it here. I will point out, however, that my LJ250 printer
does not really match my screen colors very well, even with the same RGB
values. I guess I could make up my own color map, but you'd think that
following the DEC recommended color map would do the trick.... 
Suggestions are welcome.

-------- CUT HERE FOR PPMTOSIX.1 --------
.TH ppmtosix 1 "26 April 1991"
.SH NAME
ppmtosix - convert a portable pixmap into a sixel format
.SH SYNOPSIS
.B ppmtosix
.RB [ -raw ] [-margin]
.RI [ ppmfile ]
.SH DESCRIPTION
Reads a portable pixmap as input. Produces sixel commands (SIX) as output. The output is formatted for color printing (i.e. for a DEC LJ250 color inkjet printer). 
.PP
If RGB values from the PPM file do not have maxval=100, the RGB values are rescaled. A printer control header and a color assignment table begin the SIX file. Image data is written in a compressed format by default. A printer control footer ends the image file.
.SH OPTIONS
.TP
.B -raw
If specified, each pixel will be explicitly described in the image file.  If
.RB -raw
is not specified, output will default to compressed format in which identical adjacent pixels are replaced by "repeat pixel" commands. A raw file is often an order of magnitude larger than a compressed file and prints much slower. Abbreviation as
.RB -r
is allowed.
.TP
.B -margin
If
.RB -margin
is not specified, the image will be start at the left margin (of the window, paper, or whatever). If
.RB -margin
is specified, a 1.5 inch left margin will offset the image. Abbreviation as
.RB -m
is allowed.
.SH EXAMPLE
To convert the file "dot.xwd" from xwd to a sixel file called "dot.six", one could specify
.IP
xwdtopnm dot.xwd | ppmtosix > dot.six
.SH PRINTING
Generally, sixel files must reach the printer unfiltered. Use the lpr -x option or cat filename > /dev/tty0?.
.SH BUGS
Upon rescaling, truncation of the least significant bits of RGB values may result in
poor color conversion.  If the original PPM maxval was greater than 100, rescaling also reduces the image depth. While the actual RGB values from the ppm file are more or less retained, the color palette of the LJ250 may not match the colors on your screen. This seems to be a printer limitation.
.SH "SEE ALSO"
ppm(5)
.SH AUTHOR
Copyright (C) 1991 by Rick Vinci.
Uses code from Jef Poskanzer's PBMPlus utilities.
.\" 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.

-------- CUT HERE FOR PPMTOSIX.C --------
/* ppmtosix.c - read a portable pixmap and produce a color sixel file
**
** Copyright (C) 1991 by Rick Vinci.
**
** Uses code from Jef Poskanzer's PBMPlus utilities.
**
** 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 "ppm.h"
#include "ppmcmap.h"

#define MAXCOLORS 256
#define DCS '\x90'   /* Device Control String */
#define ST '\x9c'   /* String Terminator */
#define CSI '\x9b'   /* Control String Introducer */
#define ESC '\x1b'   /* Escape character */

static pixel** pixels;   /* stored ppm pixmap input */
static colorhash_table cht;
int margin;

/***** main function *****/
void main( argc, argv )
    int argc;
    char* argv[];
{
    FILE* ifp;   /* file name */
    int argn, rows, cols, colors;   /* from ppmfile header */
    int Red, Grn, Blue, rownum, colnum;
    int raw;   /* -raw option flag */
    pixval maxval;
    colorhist_vector chv;
    char* usage = "[-raw] [-margin] [ppmfile]"; /* usage error output */

    ppm_init( &argc, argv );

    argn = 1;   /* command line argument to start with */
    raw = 0;   /* -raw option not chosen (default) */
    margin = 0;   /* -margin argument not chosen (default) */
    
    /* check to see if -option is selected and, if so, is it -raw */
    while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
    {
        if ( pm_keymatch( argv[argn], "-raw", 2 ) )
            raw =1;
        else
            if ( pm_keymatch( argv[argn], "-margin", 2 ) )
                  margin = 1;
            else
               pm_usage( usage );
        ++argn;
    }

    if ( argn < argc ) /* if a ppmfile name is provided, open it */
    {
        ifp = pm_openr( argv[argn] );
       ++argn;
    }
    else
       ifp = stdin;

    if ( argn != argc ) /* wrong number of arguments */
       pm_usage( usage );

    /* read in the ppmfile, storing in pixels, then close ppmfile */
    pixels = ppm_readppm( ifp, &cols, &rows, &maxval );
    pm_close( ifp );

    /* Now change the ppm colormap into a RGB percentage scale. */
    if ( maxval != 100 )
    {
    pm_message( 
      "automatically rescaling colors...", 0,0,0,0,0 );
    for (rownum = 0; rownum < rows; ++rownum )
      for (colnum = 0; colnum < cols; ++colnum)
      {
        Red = (int) (PPM_GETR( pixels[rownum][colnum]) * 100 / maxval)+1;
        Grn = (int) (PPM_GETG( pixels[rownum][colnum] ) * 100 / maxval)+1;
        Blue = (int) (PPM_GETB( pixels[rownum][colnum] ) * 100 / maxval)+1;
        if ( Red > 100 ) Red = 100;
        if ( Grn > 100 ) Grn = 100;
        if ( Blue > 100 ) Blue = 100;
        PPM_ASSIGN( pixels[rownum][colnum], Red, Grn, Blue );
      }
     }
       
    /* Figure out the colormap. */
    pm_message( "computing colormap...", 0,0,0,0,0 );
    chv = ppm_computecolorhist( pixels, cols, rows, MAXCOLORS, &colors );
    if ( chv == (colorhist_vector) 0 )
       pm_error( "too many colors - try doing a 'ppmquant %d'", 
                           MAXCOLORS, 0,0,0,0 );
    pm_message( "%d colors found", colors, 0,0,0,0 );

    /* Make a hash table for fast color lookup. */
    cht = ppm_colorhisttocolorhash( chv, colors );

    pm_message( "delivering sixel image...", 0,0,0,0,0 );
    WriteHeader();
    WriteColorMap( chv, colors );
    if ( raw == 1 )
       WriteRawImage( cht, rows, cols );
   else
        WritePackedImage( cht, rows, cols ); 
    WriteEnd();

    exit( 0 );

} /***** end main *****/


int WriteHeader ( )
{
   if ( margin == 1 )
       printf( "%c%d;%ds", CSI, 14, 72 );
   printf( "%c", DCS );   /* start with Device Control String */
   printf( "0;0;8q" );   /* Horizontal Grid Size at 1/90" and graphics On */
   printf( "\"1;1\n" );   /* set aspect ratio 1:1 */
}

int WriteColorMap( chv, colors )
colorhist_vector chv;
int colors;
{
   register int colornum;
   
   for ( colornum = 0; colornum < colors ; ++colornum )
     printf( "#%d;2;%d;%d;%d", colornum, 
           (int) PPM_GETR(chv[colornum].color),
           (int) PPM_GETG(chv[colornum].color),
           (int) PPM_GETB(chv[colornum].color) );
   printf( "\n" );
}

int WriteRawImage( cht, rows, cols )
colorhash_table cht;
int rows, cols;
{
   int rownum, colnum, bit;
   char sixel[] = "@ACGO_";
   register pixel* pP;
   
   for ( rownum = 0; rownum < rows; ++rownum )
   {     
     bit = rownum % 6;
     for ( colnum = 0, pP = pixels[rownum]; colnum < cols; ++colnum, ++pP )
     {
       printf( "#%d%c", ppm_lookupcolor(cht, pP), sixel[bit] );
     }
     printf( "$\n" );   /* Carriage Return */
     if ( bit == 5 )
       printf( "-\n" );   /* Line Feed (one sixel height) */
   }
}

int WritePackedImage( cht, rows, cols )
colorhash_table cht;
int rows, cols;
{   int rownum, colnum, bit, repeat, thiscolor, nextcolor;
   char sixel[] = "@ACGO_";
   register pixel* pP;
   
   for ( rownum = 0; rownum < rows; ++rownum )
   {     
     bit = rownum % 6;
     repeat = 1;
     for ( colnum = 0, pP = pixels[rownum]; colnum < cols; ++colnum, ++pP )
     {
       thiscolor = ppm_lookupcolor(cht, pP);
       if ( colnum == cols -1 )   /* last pixel in row */
           if ( repeat == 1 )
               printf( "#%d%c", thiscolor, sixel[bit] );
           else
               printf( "#%d!%d%c", thiscolor, repeat, sixel[bit] );
       else   /* not last pixel in row */
        {
           nextcolor =  ppm_lookupcolor(cht, pP+1);
           if ( thiscolor == nextcolor )
               repeat++;
           else
               if ( repeat == 1 )
                   printf( "#%d%c", thiscolor, sixel[bit] );
               else
               {
                   printf( "#%d!%d%c", thiscolor, repeat, sixel[bit] );
                   repeat = 1;
                }
         }
     }   /* end colnum loop */
     printf( "$\n" );   /* Carriage Return */
     if ( bit == 5 )
       printf( "-\n" );   /* Line Feed (one sixel height) */
   }
}

int WriteEnd ()
{
   if ( margin == 1 )
       printf ( "%c%d;%ds", CSI, 1, 80 );
   printf( "%c\n", ST );
}

/* end ppmtosix.c */
-- 
Rick Vinci                  Department of Materials Science and Engineering
rpvinci@portia.stanford.edu    Stanford University, Stanford, CA 94305
---------------------------