/* pgmtopbm.c - read a portable graymap and write a portable bitmap
**
** Copyright (C) 1989 by Jef Poskanzer.
**
** 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 <assert.h>
#include "pgm.h"
#include "dithers.h"
#include "mallocvar.h"

static void init_hilbert ARGS((int w, int h));
static int hilbert ARGS((int *px, int *py));


int
main( argc, argv )
    int argc;
    char* argv[];
{
    FILE* ifp;
    gray* grayrow;
    register gray* gP;
    bit* bitrow;
    register bit* bP;
    int argn, rows, cols, format, row, col, limitcol;
    float fthreshval;
    int clump_size;
    gray maxval;
    const char* const usage = "[-floyd|-fs  | -hilbert | -threshold | -dither8|-d8 |\n     -cluster3|-c3|-cluster4|-c4|-cluster8|-c8] [-value <val>] [-clump <size>] [pgmfile]";
    int halftone;
#define QT_FS 1
#define QT_THRESH 2
#define QT_DITHER8 3
#define QT_CLUSTER3 4
#define QT_CLUSTER4 5
#define QT_CLUSTER8 6
#define QT_HILBERT 7
    long threshval, sum;
    long* thiserr;
    long* nexterr;
    long* temperr;
#define FS_SCALE 1024
#define HALF_FS_SCALE 512
    int fs_direction;

    pgm_init( &argc, argv );

    argn = 1;
    halftone = QT_FS;   /* default quantization is Floyd-Steinberg */
    fthreshval = 0.5;
    clump_size = 5; /* default hilbert curve clump size */

    while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
    {
        if ( pm_keymatch( argv[argn], "-fs", 2 ) ||
             pm_keymatch( argv[argn], "-floyd", 2 ) )
            halftone = QT_FS;
        else if ( pm_keymatch( argv[argn], "-hilbert", 2 ))
            halftone = QT_HILBERT;
        else if ( pm_keymatch( argv[argn], "-threshold", 2 ) )
            halftone = QT_THRESH;
        else if ( pm_keymatch( argv[argn], "-dither8", 2 ) ||
                  pm_keymatch( argv[argn], "-d8", 3 ) )
            halftone = QT_DITHER8;
        else if ( pm_keymatch( argv[argn], "-cluster3", 9 ) ||
                  pm_keymatch( argv[argn], "-c3", 3 ) )
            halftone = QT_CLUSTER3;
        else if ( pm_keymatch( argv[argn], "-cluster4", 9 ) ||
                  pm_keymatch( argv[argn], "-c4", 3 ) )
            halftone = QT_CLUSTER4;
        else if ( pm_keymatch( argv[argn], "-cluster8", 9 ) ||
                  pm_keymatch( argv[argn], "-c8", 3 ) )
            halftone = QT_CLUSTER8;
        else if ( pm_keymatch( argv[argn], "-value", 2 ) )
        {
            /* Before 2001-06-08, this strtod() was a sscanf("%f").  Arjen
               Bax found on his CYGWIN system this causes a comma to be
               used as a decimal separator.  But on 2001-09.03, Charles
               Wilson found this not to be the case.  
            */
            char *ep;
            ++argn;
            if ( argn == argc
                 || (fthreshval = strtod(argv[argn], &ep), *ep != 0)
                 || fthreshval < 0.0 || fthreshval > 1.0 )
            {
                pm_usage( usage );
            }
        }
        else if ( pm_keymatch( argv[argn], "-clump", 2 ) )
        {
            ++argn;
            if ( argn == argc || 
                 sscanf( argv[argn], "%d", &clump_size ) != 1 ||
                 clump_size < 2)
                pm_usage( usage );
        }
        else
            pm_usage( usage );
        ++argn;
    }

    if ( argn != argc )
    {
        ifp = pm_openr( argv[argn] );
        ++argn;
    }
    else
        ifp = stdin;

    if ( argn != argc )
        pm_usage( usage );

    if ( halftone != QT_HILBERT )
    {
        pgm_readpgminit( ifp, &cols, &rows, &maxval, &format );
        grayrow = pgm_allocrow( cols );

        pbm_writepbminit( stdout, cols, rows, 0 );
        bitrow = pbm_allocrow( cols );

        /* Initialize. */
        switch ( halftone )
        {
        case QT_FS:
            /* Initialize Floyd-Steinberg error vectors. */
            thiserr = (long*) pm_allocrow( cols + 2, sizeof(long) );
            nexterr = (long*) pm_allocrow( cols + 2, sizeof(long) );
            srand( (int) ( time( 0 ) ^ getpid( ) ) );
            for ( col = 0; col < cols + 2; ++col )
                thiserr[col] = ( rand( ) % FS_SCALE - HALF_FS_SCALE ) / 4;
            /* (random errors in [-FS_SCALE/8 .. FS_SCALE/8]) */
            fs_direction = 1;
            threshval = fthreshval * FS_SCALE;
            break;

        case QT_HILBERT:
            break;

        case QT_THRESH:
            threshval = fthreshval * maxval + 0.999999;
            break;

        case QT_DITHER8:
            /* Scale dither matrix. */
            for ( row = 0; row < 16; ++row )
                for ( col = 0; col < 16; ++col )
                    dither8[row][col] = 
                        dither8[row][col] * ( maxval + 1 ) / 256;
            break;

        case QT_CLUSTER3:
            /* Scale order-3 clustered dither matrix. */
            for ( row = 0; row < 6; ++row )
                for ( col = 0; col < 6; ++col )
                    cluster3[row][col] = 
                        cluster3[row][col] * ( maxval + 1 ) / 18;
            break;

        case QT_CLUSTER4:
            /* Scale order-4 clustered dither matrix. */
            for ( row = 0; row < 8; ++row )
                for ( col = 0; col < 8; ++col )
                    cluster4[row][col] = 
                        cluster4[row][col] * ( maxval + 1 ) / 32;
            break;

        case QT_CLUSTER8:
            /* Scale order-8 clustered dither matrix. */
            for ( row = 0; row < 16; ++row )
                for ( col = 0; col < 16; ++col )
                    cluster8[row][col] = 
                        cluster8[row][col] * ( maxval + 1 ) / 128;
            break;

        default:
            pm_error( "can't happen" );
            exit( 1 );
        }

        for ( row = 0; row < rows; ++row )
        {
            pgm_readpgmrow( ifp, grayrow, cols, maxval, format );
    
            switch ( halftone )
            {
            case QT_FS:
                for ( col = 0; col < cols + 2; ++col )
                    nexterr[col] = 0;
                if ( fs_direction )
                {
                    col = 0;
                    limitcol = cols;
                    gP = grayrow;
                    bP = bitrow;
                }
                else
                {
                    col = cols - 1;
                    limitcol = -1;
                    gP = &(grayrow[col]);
                    bP = &(bitrow[col]);
                }
                do
                {
                    sum = 
                        ( (long) *gP * FS_SCALE ) / maxval + thiserr[col + 1];
                    if ( sum >= threshval )
                    {
                        *bP = PBM_WHITE;
                        sum = sum - threshval - HALF_FS_SCALE;
                    }
                    else
                        *bP = PBM_BLACK;
    
                    if ( fs_direction )
                    {
                        thiserr[col + 2] += ( sum * 7 ) / 16;
                        nexterr[col    ] += ( sum * 3 ) / 16;
                        nexterr[col + 1] += ( sum * 5 ) / 16;
                        nexterr[col + 2] += ( sum     ) / 16;
    
                        ++col;
                        ++gP;
                        ++bP;
                    }
                    else
                    {
                        thiserr[col    ] += ( sum * 7 ) / 16;
                        nexterr[col + 2] += ( sum * 3 ) / 16;
                        nexterr[col + 1] += ( sum * 5 ) / 16;
                        nexterr[col    ] += ( sum     ) / 16;
    
                        --col;
                        --gP;
                        --bP;
                    }
                }
                while ( col != limitcol );
                temperr = thiserr;
                thiserr = nexterr;
                nexterr = temperr;
                fs_direction = ! fs_direction;
                break;
    
            case QT_THRESH: {
                unsigned int col;
                for ( col = 0; col < cols; ++col )
                    if ( grayrow[col] >= threshval )
                        bitrow[col] = PBM_WHITE;
                    else
                        bitrow[col] = PBM_BLACK;
            }
            break;
    
            case QT_DITHER8: {
                unsigned int col;
                for ( col = 0; col < cols; ++col )
                    if ( grayrow[col] >= dither8[row % 16][col % 16] )
                        bitrow[col] = PBM_WHITE;
                    else
                        bitrow[col] = PBM_BLACK;
            }
            break;
    
            case QT_CLUSTER3: {
                unsigned int col;
                for ( col = 0; col < cols; ++col )
                    if ( grayrow[col] >= cluster3[row % 6][col % 6] )
                        bitrow[col] = PBM_WHITE;
                    else
                        bitrow[col] = PBM_BLACK;
            }
            break;
    
            case QT_CLUSTER4: {
                unsigned int col;
                for ( col = 0; col < cols; ++col )
                    if ( grayrow[col] >= cluster4[row % 8][col % 8] )
                        bitrow[col] = PBM_WHITE;
                    else
                        bitrow[col] = PBM_BLACK;
            }
            break;
                
            case QT_CLUSTER8: {
                unsigned int col;
                for ( col = 0; col < cols; ++col )
                    if ( grayrow[col] >= cluster8[row % 16][col % 16] )
                        bitrow[col] = PBM_WHITE;
                    else
                        bitrow[col] = PBM_BLACK;
            }
            break;
    
            default:
                pm_error( "Internal error: impossible value of 'halftone'" );
            }
            pbm_writepbmrow( stdout, bitrow, cols, 0 );
        }
    }
    else    /* else use hilbert space filling curve dithering */
        /*
         * This is taken from the article "Digital Halftoning with
         * Space Filling Curves" by Luiz Velho, proceedings of
         * SIGRAPH '91, page 81.
         *
         * This is not a terribly efficient or quick version of
         * this algorithm, but it seems to work. - Graeme Gill.
         * graeme@labtam.labtam.OZ.AU
         *
         */
    {
        gray **grays;
        bit **bits;
        int end;
        int *x,*y;
        int sum = 0;

        grays = pgm_readpgm( ifp, &cols,&rows, &maxval );
        bits = pbm_allocarray(cols,rows);

        MALLOCARRAY(x, clump_size);
        MALLOCARRAY(y, clump_size);
        if (x == NULL  || y == NULL)
            pm_error( "Run out of memory" );
        init_hilbert(cols,rows);

        end = clump_size;
        while (end == clump_size)
        {
            int i;
            /* compute the next clust co-ordinates along hilbert path */
            for (i = 0; i < end; i++)
            {
                if (hilbert(&x[i],&y[i])==0)
                    end = i;    /* we reached the end */
            }
            /* sum levels */
            for (i = 0; i < end; i++)
                sum += grays[y[i]][x[i]];
            /* dither half and half along path */
            for (i = 0; i < end; i++)
            {
                if (sum >= maxval)
                {
                    bits[y[i]][x[i]] = PBM_WHITE;
                    sum -= maxval;
                }
                else
                    bits[y[i]][x[i]] = PBM_BLACK;
            }
        }
        pbm_writepbm( stdout, bits, cols, rows, 0 );
    }

    pm_close( ifp );

    exit( 0 );
}


/* Hilbert curve tracer */

#define MAXORD 18

static int hil_order,hil_ord;
static int hil_turn;
static int hil_dx,hil_dy;
static int hil_x,hil_y;
static int hil_stage[MAXORD];
static int hil_width,hil_height;

/* Initialise the Hilbert curve tracer */
static void init_hilbert(w,h)
    int w,h;
{
    int big,ber;
    hil_width = w;
    hil_height = h;
    big = w > h ? w : h;
    for(ber = 2, hil_order = 1; ber < big; ber <<= 1, hil_order++);
    if (hil_order > MAXORD)
        pm_error( "Sorry, hilbert order is too large" );
    hil_ord = hil_order;
    hil_order--;
}

/* Return non-zero if got another point */
static int hilbert(px,py)
    int *px,*py;
{
    int temp;
    if (hil_ord > hil_order)    /* have to do first point */
    {
        hil_ord--;
        hil_stage[hil_ord] = 0;
        hil_turn = -1;
        hil_dy = 1;
        hil_dx = hil_x = hil_y = 0;
        *px = *py = 0;
        return 1;
    }
    for(;;) /* Operate the state machine */
    {
        switch (hil_stage[hil_ord])
        {
        case 0:
            hil_turn = -hil_turn;
            temp = hil_dy;
            hil_dy = -hil_turn * hil_dx;
            hil_dx = hil_turn * temp;
            if (hil_ord > 0) 
            {
                hil_stage[hil_ord] = 1;
                hil_ord--;
                hil_stage[hil_ord]=0;
                continue;
            }
        case 1:
            hil_x += hil_dx;
            hil_y += hil_dy;
            if (hil_x < hil_width && hil_y < hil_height)
            {
                hil_stage[hil_ord] = 2;
                *px = hil_x;
                *py = hil_y;
                return 1;
            }
        case 2:
            hil_turn = -hil_turn;
            temp = hil_dy;
            hil_dy = -hil_turn * hil_dx;
            hil_dx = hil_turn * temp;
            if (hil_ord > 0) /* recurse */
            {
                hil_stage[hil_ord] = 3;
                hil_ord--;
                hil_stage[hil_ord]=0;
                continue;
            }
        case 3:
            hil_x += hil_dx;
            hil_y += hil_dy;
            if (hil_x < hil_width && hil_y < hil_height)
            {
                hil_stage[hil_ord] = 4;
                *px = hil_x;
                *py = hil_y;
                return 1;
            }
        case 4:
            if (hil_ord > 0) /* recurse */
            {
                hil_stage[hil_ord] = 5;
                hil_ord--;
                hil_stage[hil_ord]=0;
                continue;
            }
        case 5:
            temp = hil_dy;
            hil_dy = -hil_turn * hil_dx;
            hil_dx = hil_turn * temp;
            hil_turn = -hil_turn;
            hil_x += hil_dx;
            hil_y += hil_dy;
            if (hil_x < hil_width && hil_y < hil_height)
            {
                hil_stage[hil_ord] = 6;
                *px = hil_x;
                *py = hil_y;
                return 1;
            }
        case 6:
            if (hil_ord > 0) /* recurse */
            {
                hil_stage[hil_ord] = 7;
                hil_ord--;
                hil_stage[hil_ord]=0;
                continue;
            }
        case 7:
            temp = hil_dy;
            hil_dy = -hil_turn * hil_dx;
            hil_dx = hil_turn * temp;
            hil_turn = -hil_turn;
            /* Return from a recursion */
            if (hil_ord < hil_order)
                hil_ord++;
            else
                return 0;
        }
    }
}
