/* pnmtoxwd.c - read a portable anymap and produce a color X11 window dump
**
** Copyright (C) 1989, 1991 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 "pnm.h"
#include "x11wd.h"

#include "ppmcmap.h"

int
main( argc, argv )
    int argc;
    char* argv[];
    {
    FILE* ifp;
    const xel** xels;
    const char* dumpname;
    int argn, rows, cols, format, colors, i, row, col;
    int pseudodepth;
    int forcedirect, direct, grayscale;
    xelval maxval;
    long lmaxval, xmaxval;
    colorhist_vector chv;
    colorhash_table cht;
    X11WDFileHeader h11;
    X11XColor color;
    const char* const usage = "[-pseudodepth n] [-directcolor] [pnmfile]";

    pnm_init( &argc, argv );

    argn = 1;
    pseudodepth = 8;
    forcedirect = 0;

    while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
	{
	if ( pm_keymatch( argv[argn], "-pseudodepth", 2 ) )
	    {
	    ++argn;
	    if ( argn == argc || sscanf( argv[argn], "%d", &pseudodepth ) != 1 )
		pm_usage( usage );
	    if ( pseudodepth < 1 || pseudodepth > 16 )
		pm_usage( usage );
	    }
	else if ( pm_keymatch( argv[argn], "-directcolor", 2 ) )
	    forcedirect = 1;
	else
	    pm_usage( usage );
	++argn;
	}

    if ( argn != argc )
	{
	dumpname = argv[1];
	ifp = pm_openr( argv[1] );
	++argn;
	}
    else
	{
	dumpname = "stdin";
	ifp = stdin;
	}
    
    if ( argn != argc )
	pm_usage( usage );

    xels = (const xel **) pnm_readpnm( ifp, &cols, &rows, &maxval, &format );
    lmaxval = (long) maxval;
    xmaxval = ( 1 << pseudodepth ) - 1;
    pm_close( ifp );
    
    if ( forcedirect )
	direct = 1;
    else
	{
	/* Figure out the colormap. */
	switch ( PNM_FORMAT_TYPE(format) )
	    {
	    case PPM_TYPE:
	    direct = 0;
	    pm_message( "computing colormap..." );
	    chv = ppm_computecolorhist( (xel **) xels, cols, rows, xmaxval+1, 
                                    &colors );
	    if ( chv == (colorhist_vector) 0 )
		{
		pm_message(
		 "Too many colors - proceeding to write a 24-bit DirectColor" );
		pm_message(
	      "dump file.  If you want PseudoColor, try doing a 'pnmquant %ld'.",
		    xmaxval );
		direct = 1;
		}
	    else
		{
		pm_message( "%d colors found", colors );
		grayscale = 0;
		direct = 0;
		/* Make a hash table for fast color lookup. */
		cht = ppm_colorhisttocolorhash( chv, colors );
		}
	    break;

	    default:
	    direct = 0;
	    grayscale = 1;
	    break;
	    }

	}

    /* Set up the header. */
    h11.header_size = sizeof(h11) + strlen( dumpname ) + 1;
    h11.file_version = X11WD_FILE_VERSION;
    h11.pixmap_format = ZPixmap;
    h11.pixmap_width = cols;
    h11.pixmap_height = rows;
    h11.xoffset = 0;
    h11.byte_order = MSBFirst;
    h11.bitmap_bit_order = MSBFirst;
    h11.window_width = cols;
    h11.window_height = rows;
    h11.window_x = 0;
    h11.window_y = 0;
    h11.window_bdrwidth = 0;

    if ( direct )
	{
	h11.pixmap_depth = 24;
	h11.bitmap_unit = 32;
	h11.bitmap_pad = 32;
	h11.bits_per_pixel = 32;
	h11.visual_class = DirectColor;
	h11.colormap_entries = 256;
	h11.ncolors = 0;
	h11.red_mask = 0xff0000;
	h11.green_mask = 0xff00;
	h11.blue_mask = 0xff;
	h11.bytes_per_line = cols * 4;
	}
    else if ( grayscale )
	{
	if ( PNM_FORMAT_TYPE(format) == PBM_TYPE )
	    {
	    h11.pixmap_depth = 1;
	    h11.bits_per_pixel = 1;
	    colors = 2;
	    h11.colormap_entries = colors;
	    h11.ncolors = colors;
	    h11.bytes_per_line = ( cols + 7 ) / 8;
	    }
	else
	    {
	    h11.pixmap_depth = pseudodepth;
	    h11.bits_per_pixel = pseudodepth;
	    colors = xmaxval + 1;
	    h11.colormap_entries = colors;
	    h11.ncolors = colors;
	    h11.bytes_per_line = cols;
	    }
	h11.bitmap_unit = 8;
	h11.bitmap_pad = 8;
	h11.visual_class = StaticGray;
	h11.red_mask = 0;
	h11.green_mask = 0;
	h11.blue_mask = 0;
	}
    else
	{
	h11.pixmap_depth = pseudodepth;
	h11.bits_per_pixel = pseudodepth;
	h11.visual_class = PseudoColor;
	h11.colormap_entries = xmaxval + 1;
	h11.ncolors = colors;
	h11.red_mask = 0;
	h11.green_mask = 0;
	h11.blue_mask = 0;
	h11.bytes_per_line = cols;
	h11.bitmap_unit = 8;
	h11.bitmap_pad = 8;
	}
    h11.bits_per_rgb = h11.pixmap_depth;

    /* Write out the header in big-endian order. */
    pm_writebiglong( stdout, h11.header_size );
    pm_writebiglong( stdout, h11.file_version );
    pm_writebiglong( stdout, h11.pixmap_format );
    pm_writebiglong( stdout, h11.pixmap_depth );
    pm_writebiglong( stdout, h11.pixmap_width );
    pm_writebiglong( stdout, h11.pixmap_height );
    pm_writebiglong( stdout, h11.xoffset );
    pm_writebiglong( stdout, h11.byte_order );
    pm_writebiglong( stdout, h11.bitmap_unit );
    pm_writebiglong( stdout, h11.bitmap_bit_order );
    pm_writebiglong( stdout, h11.bitmap_pad );
    pm_writebiglong( stdout, h11.bits_per_pixel );
    pm_writebiglong( stdout, h11.bytes_per_line );
    pm_writebiglong( stdout, h11.visual_class );
    pm_writebiglong( stdout, h11.red_mask );
    pm_writebiglong( stdout, h11.green_mask );
    pm_writebiglong( stdout, h11.blue_mask );
    pm_writebiglong( stdout, h11.bits_per_rgb );
    pm_writebiglong( stdout, h11.colormap_entries );
    pm_writebiglong( stdout, h11.ncolors );
    pm_writebiglong( stdout, h11.window_width );
    pm_writebiglong( stdout, h11.window_height );
    pm_writebiglong( stdout, h11.window_x );
    pm_writebiglong( stdout, h11.window_y );
    pm_writebiglong( stdout, h11.window_bdrwidth );

    /* Write out the dump name. */
    fwrite( dumpname, 1, strlen( dumpname ) + 1, stdout );

    if ( ! direct )
	{
	/* Write out the colormap, big-endian order. */
	color.flags = 7;
	color.pad = 0;
	for ( i = 0; i < colors; ++i )
	    {
	    color.num = i;
	    if ( grayscale )
		{

		/* Stupid hack because xloadimage and xwud disagree on
		** how to interpret bitmaps. */
		if ( PNM_FORMAT_TYPE(format) == PBM_TYPE )
		    color.red = (long) ( colors-1-i ) * 65535L / ( colors - 1 );
		else
		    color.red = (long) i * 65535L / ( colors - 1 );

		color.green = color.red;
		color.blue = color.red;
		}
	    else
		{
		color.red = PPM_GETR( chv[i].color );
		color.green = PPM_GETG( chv[i].color );
		color.blue = PPM_GETB( chv[i].color );
		if ( lmaxval != 65535L )
		    {
		    color.red = (long) color.red * 65535L / lmaxval;
		    color.green = (long) color.green * 65535L / lmaxval;
		    color.blue = (long) color.blue * 65535L / lmaxval;
		    }
		}
	    pm_writebiglong( stdout, color.num );
	    pm_writebigshort( stdout, color.red );
	    pm_writebigshort( stdout, color.green );
	    pm_writebigshort( stdout, color.blue );
	    (void) putc( color.flags, stdout );
	    (void) putc( color.pad, stdout );
	    }
	}

    /* Finally, write out the data. */
    for ( row = 0; row < rows; ++row )
	if ( direct )
	    {
	    switch ( PNM_FORMAT_TYPE(format) )
		{
		case PPM_TYPE:
		for ( col = 0; col < cols; ++col )
		    {
		    unsigned long ul;

		    ul = ( ( PPM_GETR( xels[row][col] ) * xmaxval / lmaxval ) << 16 ) |
			 ( ( PPM_GETG( xels[row][col] ) * xmaxval / lmaxval ) << 8 ) |
			 ( PPM_GETB( xels[row][col] ) * xmaxval / lmaxval );
		    fwrite( &ul, sizeof(ul), 1, stdout );
		    }
		break;

		default:
		for ( col = 0; col < cols; ++col )
		    {
		    unsigned long ul;
		    register unsigned long val;

		    val = PNM_GET1( xels[row][col] );
		    ul = ( ( val * xmaxval / lmaxval ) << 16 ) |
			 ( ( val * xmaxval / lmaxval ) << 8 ) |
			 ( val * xmaxval / lmaxval );
		    fwrite( &ul, sizeof(ul), 1, stdout );
		    }
		break;
		}
	    }
	else if ( grayscale )
	    {
	    register xelval bigger_maxval;
	    register int bitshift;
	    unsigned char byte;
	    register xelval s;

	    bigger_maxval = pm_bitstomaxval( h11.bits_per_pixel );
	    bitshift = 8 - h11.bits_per_pixel;
	    byte = 0;
	    for ( col = 0; col < cols; ++col )
		{
		s = PNM_GET1( xels[row][col] );

		/* More stupid hack. */
		if ( PNM_FORMAT_TYPE(format) == PBM_TYPE )
		    s = 1 - s;

		if ( maxval != bigger_maxval )
		    s = (long) s * bigger_maxval / maxval;
		byte |= s << bitshift;
		bitshift -= h11.bits_per_pixel;
		if ( bitshift < 0 )
		    {
		    putchar( byte );
		    bitshift = 8 - h11.bits_per_pixel;
		    byte = 0;
		    }
		}
	    if ( bitshift < 8 - h11.bits_per_pixel )
		putchar( byte );
	    }
	else
	    {
	    for ( col = 0; col < cols; ++col)
		putchar( ppm_lookupcolor( cht, &xels[row][col] ) );
	    }

    exit( 0 );
    }
