about summary refs log tree commit diff
path: root/converter/ppm/ppmtosixel.c
diff options
context:
space:
mode:
Diffstat (limited to 'converter/ppm/ppmtosixel.c')
-rw-r--r--converter/ppm/ppmtosixel.c440
1 files changed, 270 insertions, 170 deletions
diff --git a/converter/ppm/ppmtosixel.c b/converter/ppm/ppmtosixel.c
index 5f361e45..24454214 100644
--- a/converter/ppm/ppmtosixel.c
+++ b/converter/ppm/ppmtosixel.c
@@ -1,4 +1,4 @@
-/* ppmtosix.c - read a portable pixmap and produce a color sixel file
+/* ppmtosix.c - read a PPM and produce a color sixel file
 **
 ** Copyright (C) 1991 by Rick Vinci.
 **
@@ -8,208 +8,308 @@
 ** copyright notice and this permission notice appear in supporting
 ** documentation.  This software is provided "as is" without express or
 ** implied warranty.
+**
+** "-7bit" option added August 2023 by Scott Pakin <scott+pbm@pakin.org>.
 */
 
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "shhopt.h"
 #include "ppm.h"
 
-static void WriteHeader ARGS((void));
-static void WriteColorMap 
-  ARGS((colorhist_vector chv, int colors, pixval maxval));
-static void WriteRawImage ARGS((colorhash_table cht, int rows, int cols));
-static void WritePackedImage ARGS((colorhash_table cht, int rows, int cols));
-static void WriteEnd ARGS((void));
-#define MAXVAL 100
-#define MAXCOLORS 256
+static unsigned int const SIXEL_MAXVAL = 100;
+static unsigned int const MAXCOLORCT = 256;
 
-#define DCS '\220'   /* Device Control String */
-#define ST  '\234'   /* String Terminator */
-#define CSI '\233'   /* Control String Introducer */
-#define ESC '\033'   /* Escape character */
+static const char * const sixel = "@ACGO_";
 
-static pixel** pixels;   /* stored ppm pixmap input */
-static colorhash_table cht;
-int margin;
+/* Named escape sequences */
+typedef struct {
+  const char * DCS;   /* Device Control String */
+  const char * ST;    /* String Terminator */
+  const char * CSI;   /* Control String Introducer */
+  const char * ESC;   /* Escape character */
+} EscapeSequenceSet;
 
 
 
-int
-main( argc, argv )
-    int argc;
-    char* argv[];
-{
-    FILE* ifp;
-    int argn, rows, cols, colors;
-    int raw;
-    pixval maxval;
-    colorhist_vector chv;
-    const char* const usage = "[-raw] [-margin] [ppmfile]";
-
-
-    ppm_init( &argc, argv );
-
-    argn = 1;
-    raw = 0;
-    margin = 0;
-
-    /* Parse args. */
-    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 )
-	{
-	ifp = pm_openr( argv[argn] );
-	++argn;
-	}
-    else
-	ifp = stdin;
-
-    if ( argn != argc )
-	pm_usage( usage );
-
-    /* Read in the whole ppmfile. */
-    pixels = ppm_readppm( ifp, &cols, &rows, &maxval );
-    pm_close( ifp );
-
-    /* Print a warning if we could lose accuracy when rescaling colors. */
-    if ( maxval > MAXVAL )
-	pm_message(
-	    "maxval is not %d - automatically rescaling colors", MAXVAL );
-
-    /* Figure out the colormap. */
-    pm_message( "computing colormap..." );
-    chv = ppm_computecolorhist( pixels, cols, rows, MAXCOLORS, &colors );
-    if ( chv == (colorhist_vector) 0 )
-	pm_error( "too many colors - try doing a 'pnmquant %d'", MAXCOLORS );
-    pm_message( "%d colors found", colors );
-
-    /* Make a hash table for fast color lookup. */
-    cht = ppm_colorhisttocolorhash( chv, colors );
-
-    WriteHeader();
-    WriteColorMap( chv, colors, maxval );
-    if ( raw == 1 )
-	WriteRawImage( cht, rows, cols );
-    else
-	WritePackedImage( cht, rows, cols );
-    WriteEnd();
+enum Charwidth {CHARWIDTH_8BIT, CHARWIDTH_7BIT};
 
-    /* If the program failed, it previously aborted with nonzero completion
-       code, via various function calls.
+struct CmdlineInfo {
+    /* All the information the user supplied in the command line, in a form
+       easy for the program to use.
     */
-    return 0;
+    const char *   inputFileName;
+    unsigned int   raw;
+    unsigned int   margin;
+    enum Charwidth charWidth;
+};
+
+
+
+static EscapeSequenceSet
+escapeSequenceSet(enum Charwidth const charWidth) {
+
+    EscapeSequenceSet eseqs;
+
+    switch (charWidth) {
+    case CHARWIDTH_8BIT: {
+        eseqs.DCS = "\220";
+        eseqs.ST  = "\234";
+        eseqs.CSI = "\233";
+        eseqs.ESC = "\033";
+    } break;
+    case CHARWIDTH_7BIT: {
+        eseqs.DCS = "\033P";
+        eseqs.ST  = "\033\\";
+        eseqs.CSI = "\033[";
+        eseqs.ESC = "\033";
+    } break;
+    }
+    return eseqs;
 }
 
 
 
 static void
-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 */
-    }
+parseCommandLine(int argc, const char ** argv,
+                 struct CmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Parse the program arguments (given by argc and argv) into a form
+   the program can deal with more easily -- a cmdline_info structure.
+   If the syntax is invalid, issue a message and exit the program via
+   pm_error().
+
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;  /* malloc'ed */
+    optStruct3 opt;  /* set by OPTENT3 */
+    unsigned int option_def_index;
+
+    unsigned int opt7Bit;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0,   "raw",      OPT_FLAG,
+            NULL,                       &cmdlineP->raw, 0);
+    OPTENT3(0,   "margin",   OPT_FLAG,
+            NULL,                       &cmdlineP->margin, 0);
+    OPTENT3(0,   "7bit",     OPT_FLAG,
+            NULL,                       &opt7Bit, 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_optParseOptions4(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    cmdlineP->charWidth = opt7Bit ? CHARWIDTH_7BIT : CHARWIDTH_8BIT;
+
+    if (argc-1 == 0)
+        cmdlineP->inputFileName = "-";
+    else if (argc-1 != 1)
+        pm_error("Program takes zero or one argument (filename).  You "
+                 "specified %d", argc-1);
+    else
+        cmdlineP->inputFileName = argv[1];
+
+    free(option_def);
+}
 
 
 
 static void
-WriteColorMap( colorhist_vector chv, int colors, pixval maxval )
-    {
-    register int colornum;
-    pixel p;
-
-    for ( colornum = 0; colornum < colors ; ++colornum )
-	{
-	p = chv[colornum].color;
-	if ( maxval != MAXVAL )
-	    PPM_DEPTH( p, p, maxval, MAXVAL );
-	printf( "#%d;2;%d;%d;%d", colornum,
-	    (int) PPM_GETR( p ), (int) PPM_GETG( p ), (int) PPM_GETB( p ) );
-	}
-    printf( "\n" );
+writePackedImage(pixel **        const pixels,
+                 unsigned int    const rows,
+                 unsigned int    const cols,
+                 colorhash_table const cht) {
+
+    unsigned int row;
+
+    for (row = 0; row < rows; ++row) {
+
+        unsigned int const b = row % 6;
+
+        unsigned int repeatCt;
+        unsigned int col;
+
+        repeatCt = 1;
+
+        for (col = 0; col < cols; ++col) {
+            const pixel * const thisRow = pixels[row];
+
+            unsigned int const thisColorIdx =
+                ppm_lookupcolor(cht, &thisRow[col]);
+
+            if (col == cols - 1)   /* last pixel in row */
+                if (repeatCt == 1)
+                    printf("#%d%c", thisColorIdx, sixel[b]);
+                else
+                    printf("#%d!%d%c", thisColorIdx, repeatCt, sixel[b]);
+            else {  /* not last pixel in row */
+                unsigned int const nextColorIdx =
+                    ppm_lookupcolor(cht, &thisRow[col+1]);
+                if (thisColorIdx == nextColorIdx)
+                    ++repeatCt;
+                else {
+                    if (repeatCt == 1)
+                        printf("#%d%c", thisColorIdx, sixel[b]);
+                    else {
+                        printf("#%d!%d%c", thisColorIdx, repeatCt, sixel[b]);
+                        repeatCt = 1;
+                    }
+                }
+            }
+        }
+        printf( "$\n" );   /* Carriage Return */
+
+        if (b == 5)
+            printf("-\n");   /* Line Feed (one sixel height) */
     }
+}
 
 
 
 static void
-WriteRawImage( cht, rows, cols )
-    colorhash_table cht;
-    int rows, cols;
-    {
-    int rownum, colnum, b;
-    const char* sixel = "@ACGO_";
-    register pixel* pP;
-
-    for ( rownum = 0; rownum < rows; ++rownum )
-	{
-	b = rownum % 6;
-	for ( colnum = 0, pP = pixels[rownum]; colnum < cols; ++colnum, ++pP )
-	    printf( "#%d%c", ppm_lookupcolor(cht, pP), sixel[b] );
-	printf( "$\n" );   /* Carriage Return */
-	if ( b == 5 )
-	    printf( "-\n" );   /* Line Feed (one sixel height) */
-	}
+writeHeader(bool              const wantMargin,
+            EscapeSequenceSet const eseqs) {
+
+    if (wantMargin)
+        printf("%s%d;%ds", eseqs.CSI, 14, 72);
+
+    printf("%s", eseqs.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 */
+}
+
+
+
+static void
+writeColorMap(colorhist_vector const chv,
+              unsigned int     const colorCt,
+              pixval           const maxval) {
+
+    unsigned int colorIdx;
+
+    for (colorIdx = 0; colorIdx < colorCt ; ++colorIdx) {
+        pixel const p = chv[colorIdx].color;
+
+        pixel scaledColor;
+
+        if (maxval == SIXEL_MAXVAL)
+            scaledColor = p;
+        else
+            PPM_DEPTH(scaledColor, p, maxval, SIXEL_MAXVAL);
+
+        printf("#%d;2;%d;%d;%d", colorIdx,
+               PPM_GETR(scaledColor),
+               PPM_GETG(scaledColor),
+               PPM_GETB(scaledColor));
     }
+    printf("\n");
+}
 
 
 
 static void
-WritePackedImage( cht, rows, cols )
-    colorhash_table cht;
-    int rows, cols;
-    {
-    int rownum, colnum, b, repeat, thiscolor, nextcolor;
-    const char* sixel = "@ACGO_";
-    register pixel* pP;
-
-    for ( rownum = 0; rownum < rows; ++rownum )
-	{
-	b = 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[b] );
-		else
-		    printf( "#%d!%d%c", thiscolor, repeat, sixel[b] );
-	    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[b] );
-		    else
-		    {
-		    printf( "#%d!%d%c", thiscolor, repeat, sixel[b] );
-		    repeat = 1;
-		    }
-		}
-	    }   /* end column loop */
-	printf( "$\n" );   /* Carriage Return */
-	if ( b == 5 )
-	    printf( "-\n" );   /* Line Feed (one sixel height) */
-	}
+writeRawImage(pixel **        const pixels,
+              unsigned int    const rows,
+              unsigned int    const cols,
+              colorhash_table const cht) {
+
+    unsigned int row;
+
+    for (row = 0; row < rows; ++row) {
+        pixel * const thisRow = pixels[row];
+        unsigned int const b = row % 6;
+
+        unsigned int col;
+
+        for (col = 0; col < cols; ++col)
+            printf("#%d%c", ppm_lookupcolor(cht, &thisRow[col]), sixel[b]);
+
+        printf("$\n");   /* Carriage Return */
+
+        if (b == 5)
+            printf("-\n");   /* Line Feed (one sixel height) */
     }
+}
 
 
 
 static void
-WriteEnd()
-    {
-    if ( margin == 1 )
-	printf ( "%c%d;%ds", CSI, 1, 80 );
-    printf( "%c\n", ST );
+writeEnd(bool              const wantMargin,
+         EscapeSequenceSet const eseqs) {
+
+    if (wantMargin)
+        printf ("%s%d;%ds", eseqs.CSI, 1, 80);
+
+    printf("%s\n", eseqs.ST);
+}
+
+
+
+int
+main(int argc, const char ** argv) {
+
+    struct CmdlineInfo cmdline;
+    FILE * ifP;
+    int rows, cols;
+    pixval maxval;
+    int colorCt;
+    pixel ** pixels;
+    colorhist_vector chv;
+        /* List of colors in the image, indexed by colormap index */
+    colorhash_table cht;
+        /* Hash table for fast colormap index lookup */
+    EscapeSequenceSet eseqs;
+        /* The escape sequences we use in our output for various functions */
+
+    pm_proginit(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFileName);
+
+    pixels = ppm_readppm(ifP, &cols, &rows, &maxval);
+
+    if (maxval > SIXEL_MAXVAL) {
+        pm_message(
+            "maxval of input is not the sixel maxval (%u) - "
+            "rescaling to fewer colors", SIXEL_MAXVAL);
     }
+
+    pm_message("computing colormap...");
+    chv = ppm_computecolorhist(pixels, cols, rows, MAXCOLORCT, &colorCt);
+    if (!chv)
+        pm_error("too many colors - try 'pnmquant %u'", MAXCOLORCT);
+
+    pm_message("%d colors found", colorCt);
+
+    cht = ppm_colorhisttocolorhash(chv, colorCt);
+
+    eseqs = escapeSequenceSet(cmdline.charWidth);
+
+    writeHeader(!!cmdline.margin, eseqs);
+
+    writeColorMap(chv, colorCt, maxval);
+
+    if (cmdline.raw)
+        writeRawImage(pixels, rows, cols, cht);
+    else
+        writePackedImage(pixels, rows, cols, cht);
+
+    writeEnd(!!cmdline.margin, eseqs);
+
+    /* If the program failed, it previously aborted with nonzero completion
+       code, via various function calls.
+    */
+    return 0;
+}
+
+
+