/*++

Copyright (c) 1996  Microsoft Corporation

Module Name:

    cpsi2tif.c

Abstract:

    converting the bitmap from CPSI to a TIFF file
    usage: cpsi2tif input-file output-file

Revision History:

	12/30/96 -davidx-
		Created it.

--*/


#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>

#define LITTLE_ENDIAN   1

#define PAGE_HEADER     0x00000101
#define PAGE_TRAILER    0x00000004
#define BAND_HEADER     0x00000003

// we assume 32-bit integer values are stored in
// big-endian format in the input file.

#if LITTLE_ENDIAN

#define swap_integer(n) \
        ((((n) >> 24) & 0x00ff) | \
         (((n) >>  8) & 0xff00) | \
         (((n) & 0xff00) <<  8) | \
         (((n) & 0x00ff) << 24))

#else //!LITTLE_ENDIAN

#define swap_integer(n) (n)

#endif //!LITTLE_ENDIAN

#define ASSERT(cond) { \
            if (! (cond)) \
                error("assertion failed on line %d\n", __LINE__); \
        }


// bitmap header structure at the beginning of each page

typedef long INT32;
typedef unsigned long UINT32;
typedef short INT16;
typedef unsigned short UINT16;
typedef unsigned char UINT8;

typedef struct {

	INT32   byteWidth;
	INT32   height;
	INT32   topMargin;
	INT32   leftMargin;
	INT32   bandHeight;
	INT32   xResolution;
	INT32   yResolution;
	INT32   imagingOrder;
	INT32   planeOrder;
	INT32   depth;
	INT32   dcc;
	INT32   firstColor;
	INT32   redFirst;
	INT32   redN;
	INT32   redDelta;
	INT32   greenFirst;
	INT32   greenN;
	INT32   greenDelta;
	INT32   blueFirst;
	INT32   blueN;
	INT32   blueDelta;
	INT32   grayFirst;
	INT32   grayN;
	INT32   grayDelta;
	INT32   isPlanar;
	INT32   scanUnit;
	INT32   swapBits;

} BITMAP_HEADER, *PBITMAP_HEADER;


char*   progname;
FILE*   fin;
FILE*   fout;
INT32   outputByteCnt;
int     useStrips = 1;


void
error(
    char *fmtstr,
    ...
    )

{
    va_list arglist;

    fprintf(stderr, "%s: ", progname);

    va_start(arglist, fmtstr);
    vfprintf(stderr, fmtstr, arglist);
    va_end(arglist);

    exit(-1);
}


void*
alloc_memory(
    int size
    )

{
    void*   p;

    if ((p = malloc(size)) == NULL)
        error("out of memory\n");
    
    memset(p, 0, size);
    return p;
}


INT32
read_integer(
    void
    )

{
    INT32 n;

    if (fread(&n, sizeof(n), 1, fin) != 1)
        error("error reading input file\n");

    return swap_integer(n);
}


void
write_output_data(
    void*   p,
    int     size
    )

{
    if (fwrite(p, size, 1, fout) != 1)
        error("error writing output file\n");
    
    outputByteCnt += size;
}


#include "tiffcomp.c"


int
write_tiff_fileheader(
    void
    )

{
    write_output_data(&tiffFileHeader, sizeof(tiffFileHeader));
    return offsetof(TIFF_FILEHEADER, firstIFDOffset);
}


void
align_output_data(
    void
    )

{
    if (outputByteCnt & 1)
    {
        char ch = 0;
        write_output_data(&ch, 1);
    }
}


void
write_nextifd_offset(
    int     offset,
    INT32   value
    )

{
    // update the next IFD offset and move to end of file

    if (fseek(fout, offset, SEEK_SET) != 0 ||
        fwrite(&value, sizeof(value), 1, fout) != 1 ||
        fseek(fout, 0, SEEK_END) != 0)
    {
        error("couldn't write IFD offset\n");
    }
}


int
process_page(
    int     prevIFDOffset
    )

{
    BITMAP_HEADER   bitmapHeader;
    int             ifd_offset;
    int             strip_count, strip_index;
    INT16           ifd_count;
    IFD             ifd;
    UINT8*          cur_line;
    UINT8*          ref_line;
    int             totalLines, bandLines, lineBytes, lineBits;
    static int      pageNumber = 0;

    // read in bitmap header

    if (fread(&bitmapHeader, sizeof(bitmapHeader), 1, fin) != 1)
        error("couldn't read bitmap header\n");
    
    if (LITTLE_ENDIAN)
    {
        INT32*  p;
        int     n;

        p = (INT32*) &bitmapHeader;
        n = sizeof(bitmapHeader) / sizeof(INT32);
        ASSERT(sizeof(bitmapHeader) % sizeof(INT32) == 0)

        while (n--)
        {
            *p = swap_integer(*p);
            p++;
        }
    }

    // valid bitmap header fields
    // we're only able to deal with 1-bpp monochrome images for now

    if (bitmapHeader.byteWidth <= 0 ||
        bitmapHeader.height <= 0 ||
        bitmapHeader.topMargin != 0 ||
        bitmapHeader.leftMargin != 0 ||
        bitmapHeader.bandHeight <= 0 ||
        bitmapHeader.bandHeight > bitmapHeader.height ||
        bitmapHeader.xResolution <= 0 ||
        bitmapHeader.yResolution <= 0 ||
        bitmapHeader.imagingOrder != 0 ||
        bitmapHeader.planeOrder != 0 ||
        bitmapHeader.depth != 1 ||
        bitmapHeader.dcc != 0 ||
        bitmapHeader.firstColor != 0 ||
        bitmapHeader.redFirst != 0 ||
        bitmapHeader.redN != 0 ||
        bitmapHeader.redDelta != 0 ||
        bitmapHeader.greenFirst != 0 ||
        bitmapHeader.greenN != 0 ||
        bitmapHeader.greenDelta != 0 ||
        bitmapHeader.blueFirst != 0 ||
        bitmapHeader.blueN != 0 ||
        bitmapHeader.blueDelta != 0 ||
        bitmapHeader.grayFirst != 0 ||
        bitmapHeader.grayN != 2 ||
        bitmapHeader.grayDelta != 1 ||
        bitmapHeader.isPlanar != 0 ||
        bitmapHeader.scanUnit != 4 ||
        bitmapHeader.swapBits != 0)
    {
        error("invalid bitmap header\n");
    }

    totalLines = bitmapHeader.height;
    lineBytes = bitmapHeader.byteWidth;
    lineBits = lineBytes * 8;

    // allocate memory for various data structures:
    //  reference line
    //  current line
    //  strip offsets
    //  strip bytecounts

    memcpy(&ifd, &ifdTemplate, sizeof(ifd));

    strip_count = (totalLines + bitmapHeader.bandHeight - 1) / bitmapHeader.bandHeight;
    ref_line = alloc_memory(lineBytes);
    cur_line = alloc_memory(lineBytes);

    if (useStrips)
    {
        INT32*  strip_offsets;
        INT32*  strip_bytecounts;

        strip_offsets = alloc_memory(sizeof(INT32) * strip_count);
        strip_bytecounts = alloc_memory(sizeof(INT32) * strip_count);

        // process one band at a time
        
        for (strip_index=0; strip_index < strip_count; strip_index++)
        {
            strip_offsets[strip_index] = outputByteCnt;

            // read band header

            if (read_integer() != BAND_HEADER ||
                (bandLines = read_integer()) > bitmapHeader.bandHeight ||
                bandLines <= 0)
            {
                error("invalid band header\n");
            }

            init_tiff_encoder();
            memset(ref_line, 0, lineBytes);

            while (bandLines--)
            {
                UINT8* p;

                // read input scanline

                if (fread(cur_line, lineBytes, 1, fin) != 1)
                    error("error reading scanline\n");
                
                // compress it

                compress_tiff_line(cur_line, ref_line, lineBits);

                // switch current and reference line buffer

                p = ref_line;
                ref_line = cur_line;
                cur_line = p;
            }

            cleanup_tiff_encoder();

            // calculate the size of compressed data for the current strip

            strip_bytecounts[strip_index] = outputByteCnt - strip_offsets[strip_index];
            align_output_data();
        }

        // output strip offsets and strip bytecounts

        ifd.ifdRowsPerStrip.value = bitmapHeader.bandHeight;
        ifd.ifdStripOffsets.count = strip_count;
        ifd.ifdStripOffsets.value = outputByteCnt;
        write_output_data(strip_offsets, strip_count * sizeof(INT32));

        ifd.ifdStripByteCounts.count = strip_count;
        ifd.ifdStripByteCounts.value = outputByteCnt;
        write_output_data(strip_bytecounts, strip_count * sizeof(INT32));

        free(strip_offsets);
        free(strip_bytecounts);
    }
    else
    {
        ifd.ifdStripOffsets.count = 1;
        ifd.ifdStripOffsets.value = outputByteCnt;
        ifd.ifdRowsPerStrip.value = totalLines;

        init_tiff_encoder();
        memset(ref_line, 0, lineBytes);

        // process one band at a time
        
        for (strip_index=0; strip_index < strip_count; strip_index++)
        {
            // read band header

            if (read_integer() != BAND_HEADER ||
                (bandLines = read_integer()) > bitmapHeader.bandHeight ||
                bandLines <= 0)
            {
                error("invalid band header\n");
            }

            while (bandLines--)
            {
                UINT8* p;

                // read input scanline

                if (fread(cur_line, lineBytes, 1, fin) != 1)
                    error("error reading scanline\n");
                
                // compress it

                compress_tiff_line(cur_line, ref_line, lineBits);

                // switch current and reference line buffer

                p = ref_line;
                ref_line = cur_line;
                cur_line = p;
            }
        }

        cleanup_tiff_encoder();

        ifd.ifdStripByteCounts.count = 1;
        ifd.ifdStripByteCounts.value = outputByteCnt - ifd.ifdStripOffsets.value;
        align_output_data();
    }

    // update the nextIFDOffset field in the previous IFD

    write_nextifd_offset(prevIFDOffset, outputByteCnt);

    // write out IFD

    ASSERT(sizeof(ifd_count) == 2);
    ifd_count = IFD_COUNT;
    write_output_data(&ifd_count, 2);
    ifd_offset = outputByteCnt;

    ifd.ifdImageWidth.value = lineBits;
    ifd.ifdImageHeight.value = totalLines;
    ifd.ifdXResolution.value = ifd_offset + offsetof(IFD, xresNum);
    ifd.xresNum = bitmapHeader.xResolution;
    ifd.ifdYResolution.value = ifd_offset + offsetof(IFD, yresNum);
    ifd.yresNum = bitmapHeader.yResolution;
    ifd.ifdPageNumber.value = pageNumber++;
    ifd.ifdSoftware.value = ifd_offset + offsetof(IFD, software);

    write_output_data(&ifd, sizeof(ifd));

    free(ref_line);
    free(cur_line);

    return ifd_offset + offsetof(IFD, nextIFDOffset);
}


int
end_of_file(
    FILE*   fin
    )

{
    int ch;

    if ((ch = fgetc(fin)) == EOF)
        return 1;
    
    ungetc(ch, fin);
    return 0;
}


int __cdecl
main(
    int argc,
    char **argv
    )

{
    int offset;

    // check command line arguments

    progname = *argv;

    if (argc != 3)
    {
        fprintf(stderr, "usage: %s input-file output-file\n", progname);
        return 1;
    }

    // open input and output file

    if ((fin = fopen(argv[1], "r")) == NULL)
        error("couldn't open input file '%s'\n", argv[1]);

    if ((fout = fopen(argv[2], "w")) == NULL)
        error("couldn't open output file '%s'\n", argv[2]);

    // output tiff file header

    offset = write_tiff_fileheader();

    while (! end_of_file(fin))
    {
        // look for page header

        if (read_integer() != PAGE_HEADER)
            error("invalid page header\n");

        // process a single page

        offset = process_page(offset);

        // look for page trailer

        if (read_integer() != PAGE_TRAILER)
            error("invalid page trailer\n");
    }

    write_nextifd_offset(offset, 0);

    fclose(fin);
    fclose(fout);
    return 0;
}