#ifdef RCSID
static char RCSid[] =
"$Header: d:/cvsroot/tads/html/htmljpeg.cpp,v 1.2 1999/05/17 02:52:21 MJRoberts Exp $";
#endif

/* 
 *   Copyright (c) 1997 by Michael J. Roberts.  All Rights Reserved.
 *   
 *   Please see the accompanying license file, LICENSE.TXT, for information
 *   on using and copying this software.  
 */
/*
Name
  htmljpeg.cpp - JPEG operations
Function
  
Notes
  This code is based in part on Chris Losinger's JpegFile package,
  and uses the JPEG implementation in the Independent JPEG Group's
  (IJG) V.6a code.

  NOTE: This file is NOT required for the portable system.  This is
  a helper file that ports can optionally use.  You should only use
  this file if you want to use the IJG portable reference library to
  implement JPEG image handling.  If you use the IJG library, this
  file is helpful because it implements a simple, portable wrapper
  for the IJG library that makes it easier to use within the HTML
  TADS type system.  Many GUI operating systems have their own native
  support for JPEG image files, so some ports may prefer not to use
  the IJG library at all, but to use the native OS APIs for JPEGs
  instead.  If you're using native OS JPEG handling, or you're using
  some other third-party library instead of the IJG library, you will
  have no use for this file and should simply omit it from your
  makefile.
Modified
  10/24/97 MJRoberts  - Creation
*/

#include <stdio.h>
#include <setjmp.h>
#include <stdlib.h>
#include <memory.h>

/* include TADS OS header - we use the TADS OS-layer file I/O routines */
#include <os.h>

/* include jpeglib header - note that this is a C (not C++) header */
extern "C" {
#include <jpeglib.h>
#include <jerror.h>
}

#ifndef TADSHTML_H
#include "tadshtml.h"
#endif
#ifndef HTML_OS_H
#include "html_os.h"
#endif
#ifndef HTMLJPEG_H
#include "htmljpeg.h"
#endif


/* ------------------------------------------------------------------------ */
/*
 *   Data source object for JPEG implementation.  This object provides an
 *   implementation of the JPEG data source using the TADS OS-layer I/O
 *   routines.  
 */

struct CTadsDataSource : public jpeg_source_mgr
{
public:
    CTadsDataSource()
    {
        /* fill in our implementations of the callback functions */
        init_source = &init_source_cb;
        fill_input_buffer = &fill_input_buffer_cb;
        skip_input_data = &skip_input_data_cb;
        term_source = &term_source_cb;

        /* use the standard jpeglib implementation of this one */
        resync_to_restart = &jpeg_resync_to_restart;

        /* initialize the rest of the jpeglib part of the structure */
        bytes_in_buffer = 0;
        next_input_byte = 0;
    }

    /* set the input file */
    void set_file(osfildef *fp) { fp_ = fp; }

private:
    /* the callback routines */
    static void init_source_cb(j_decompress_ptr);
    static boolean fill_input_buffer_cb(j_decompress_ptr);
    static void skip_input_data_cb(j_decompress_ptr, long);
    static void term_source_cb(j_decompress_ptr);
    
    /* the file we're reading */
    osfildef *fp_;

    /* my input buffer */
    JOCTET buffer_[4096];

    /* flag indicating that we're at the beginning of the file */
    int start_of_file_;
};

/*
 *   Method implementations for TADS JPEG data source.  These are mostly
 *   copied from the jpeglib stdio data source implementation, with
 *   appropriate changes for the TADS OS-layer I/O routines.  
 */

void CTadsDataSource::init_source_cb(j_decompress_ptr cinfo)
{
    CTadsDataSource *src = (CTadsDataSource *)cinfo->src;

    /* note that we haven't started reading yet */
    src->start_of_file_ = TRUE;
}

boolean CTadsDataSource::fill_input_buffer_cb(j_decompress_ptr cinfo)
{
    CTadsDataSource *src = (CTadsDataSource *)cinfo->src;
    size_t nbytes;

    /* read the buffer */
    nbytes = osfrbc(src->fp_, src->buffer_, sizeof(src->buffer_));
    if (nbytes <= 0)
    {
        /* Treat empty input file as fatal error */
        if (src->start_of_file_)
            ERREXIT(cinfo, JERR_INPUT_EMPTY);

        /* warn that we hit EOF */
        WARNMS(cinfo, JWRN_JPEG_EOF);

        /* Insert a fake EOI marker */
        src->buffer_[0] = (JOCTET) 0xFF;
        src->buffer_[1] = (JOCTET) JPEG_EOI;
        nbytes = 2;
    }

    /* fill in statistics */
    src->next_input_byte = src->buffer_;
    src->bytes_in_buffer = nbytes;
    src->start_of_file_ = FALSE;

    /* success */
    return TRUE;
}

void CTadsDataSource::skip_input_data_cb(j_decompress_ptr cinfo, long nbytes)
{
    CTadsDataSource *src = (CTadsDataSource *)cinfo->src;

    /* 
     *   Do this the dumb easy way.  We could use fseek(), but it would be
     *   more trouble (and more error-prone) to figure out what the
     *   disposition of the buffer would be after the operation.  
     */
    if (nbytes > 0)
    {
        while (nbytes > (long)src->bytes_in_buffer)
        {
            nbytes -= (long)src->bytes_in_buffer;
            fill_input_buffer_cb(cinfo);

            /* 
             *   note we assume that fill_input_buffer will never return
             *   FALSE, so suspension need not be handled.  
             */
        }
        src->next_input_byte += (size_t)nbytes;
        src->bytes_in_buffer -= (size_t)nbytes;
    }
}

void CTadsDataSource::term_source_cb(j_decompress_ptr)
{
    /* we don't need to do anything here */
}

/* ------------------------------------------------------------------------ */
/*
 *   JPEG object 
 */

/* statics */
unsigned int CHtmlJpeg::options_  = 0;

/*
 *   create 
 */
CHtmlJpeg::CHtmlJpeg()
{
    /* initialize members - no image yet */
    height_ = 0;
    width_ = 0;
    image_buf_ = 0;
    palette_ = 0;
    palette_size_ = 0;
}

/*
 *   destroy 
 */
CHtmlJpeg::~CHtmlJpeg()
{
    /* if we have an image, delete it */
    delete_image();
}

/*
 *   Delete the image currently stored in the object 
 */
void CHtmlJpeg::delete_image()
{
    /* if we have an image buffer, delete it */
    if (image_buf_ != 0)
        os_free_huge(image_buf_);

    /* delete the palette as well */
    if (palette_ != 0)
        delete [] palette_;

    /* forget all image parameters */
    width_ = 0;
    height_ = 0;
    image_buf_ = 0;
    palette_ = 0;
    palette_size_ = 0;
}


/*
 *   Store an RGB scan line 
 */
static void j_putRGBScanline(unsigned char *jpegline, int widthPix,
                             unsigned char *outBuf, int row)
{
    int offset = row * widthPix * 3;
    int count;
    for (count = 0 ; count < widthPix ; ++count)
    {
        unsigned char iRed, iBlu, iGrn;
        unsigned char *oRed, *oBlu, *oGrn;
        
        iRed = *(jpegline + count * 3 + 0);
        iGrn = *(jpegline + count * 3 + 1);
        iBlu = *(jpegline + count * 3 + 2);
        
        oRed = outBuf + offset + count * 3 + 0;
        oGrn = outBuf + offset + count * 3 + 1;
        oBlu = outBuf + offset + count * 3 + 2;
        
        *oRed = iRed;
        *oGrn = iGrn;
        *oBlu = iBlu;
    }
}

/*
 *   store a palette-indexed scan line 
 */
void j_putMapScanline(unsigned char *jpegline, int widthPix,
                      unsigned char *outBuf, int row)
{
    memcpy(outBuf + row * widthPix, jpegline, widthPix);
}


/*
 *   store a gray-scale scan line 
 */
void j_putGrayScanlineToRGB(unsigned char *jpegline, int widthPix,
                            unsigned char *outBuf, int row)
{
    int offset = row * widthPix * 3;
    int count;
    for (count=0;count<widthPix;count++)
    {
        unsigned char iGray;
        unsigned char *oRed, *oBlu, *oGrn;
        
        /* get our grayscale value */
        iGray = *(jpegline + count);
        
        oRed = outBuf + offset + count * 3;
        oGrn = outBuf + offset + count * 3 + 1;
        oBlu = outBuf + offset + count * 3 + 2;
        
        *oRed = iGray;
        *oGrn = iGray;
        *oBlu = iGray;
    }
}


/*
 *   Extended version of jpeg_error_mgr for use with our error handler 
 */
struct html_error_mgr
{
    struct jpeg_error_mgr pub;                           /* "public" fields */
    jmp_buf setjmp_buffer;                          /* for return to caller */
};

/*
 *   Error handler for jpeglib errors
 */
static void html_error_exit(j_common_ptr cinfo)
{
    /* cinfo->err really points to an html_error_mgr struct; coerce it */
    html_error_mgr *myerr = (html_error_mgr *)cinfo->err;

    /* we could generate the error message: */
    // char buffer[JMSG_LENGTH_MAX];
    // (*cinfo->err->format_message)(cinfo, buffer);

    /* return control to the setjmp point in the caller */
    longjmp(myerr->setjmp_buffer, 1);
}


/*
 *   Read a JPEG file into memory.  This routine roughly follows the
 *   cookbook in the IJG Jpeg code v6 sample file example.c.  
 */
int CHtmlJpeg::read_jpeg_file(const textchar_t *filename,
                              unsigned long seekpos,
                              unsigned long /*filesize*/,
                              int convert_256_color)
{
    struct jpeg_decompress_struct cinfo;
    osfildef *infile;
    JSAMPARRAY row_buffer;                             /* Output row buffer */
    int row_stride;                  /* physical row width in output buffer */
    unsigned long image_buf_size;
    html_error_mgr jerr;
    CTadsDataSource my_data_src;

    /* open the file; return error if it fails */
    if ((infile = osfoprb(filename, OSFTBIN)) == 0)
        return 1;

    /* 
     *   if the global options require 24-bit RGB as the in-memory format,
     *   ignore the caller's request for 8-bit palette conversion 
     */
    if ((options_ & HTMLJPEG_OPT_RGB24) != 0)
        convert_256_color = FALSE;

    /* seek to the starting position in the file */
    osfseek(infile, seekpos, OSFSK_SET);

    /* delete any existing image */
    delete_image();

    /* set up the normal JPEG error routines, then override error_exit. */
    cinfo.err = jpeg_std_error(&jerr.pub);
    jerr.pub.error_exit = html_error_exit;

    /* 
     *   establish longjmp context - we'll come here if the JPEG code
     *   throws an error 
     */
    if (setjmp(jerr.setjmp_buffer))
    {
        /* 
         *   If we get here, the JPEG code has signaled an error.  We need
         *   to clean up the JPEG object, delete the buffer, close the
         *   input file, and return failure.  
         */
        jpeg_destroy_decompress(&cinfo);
        delete_image();
        if (infile != 0)
            osfcls(infile);

        /* indicate failure */
        return 1;
    }

    /* Step 1: allocate and initialize JPEG decompression object */
    jpeg_create_decompress(&cinfo);

    /* 
     *   Step 2: specify data source - in this case, our input file.  Note
     *   that we define our own data source, since we want to use the TADS
     *   OS-layer I/O routines.  
     */
    cinfo.src = &my_data_src;
    my_data_src.set_file(infile);

    /* Step 3: read parameters from the JPEG file header */
    jpeg_read_header(&cinfo, TRUE);

    /* 
     *   Step 4: set parameters for decompression.  If the caller
     *   specified that they want the picture converted to a 256-color
     *   palette, quantize the palette. 
     */
    if (convert_256_color)
    {
        size_t i;
        static const struct
        {
            unsigned char r;
            unsigned char g;
            unsigned char b;
        }
        rainbow[] =
        {
            /* a uniform color cube */
            { 0x00, 0x00, 0x00 },
            { 0x00, 0x00, 0x33 },
            { 0x00, 0x00, 0x66 },
            { 0x00, 0x00, 0x99 },
            { 0x00, 0x00, 0xcc },
            { 0x00, 0x00, 0xff },
            { 0x00, 0x33, 0x00 },
            { 0x00, 0x33, 0x33 },
            { 0x00, 0x33, 0x66 },
            { 0x00, 0x33, 0x99 },
            { 0x00, 0x33, 0xcc },
            { 0x00, 0x33, 0xff },
            { 0x00, 0x66, 0x00 },
            { 0x00, 0x66, 0x33 },
            { 0x00, 0x66, 0x66 },
            { 0x00, 0x66, 0x99 },
            { 0x00, 0x66, 0xcc },
            { 0x00, 0x66, 0xff },
            { 0x00, 0x99, 0x00 },
            { 0x00, 0x99, 0x33 },
            { 0x00, 0x99, 0x66 },
            { 0x00, 0x99, 0x99 },
            { 0x00, 0x99, 0xcc },
            { 0x00, 0x99, 0xff },
            { 0x00, 0xcc, 0x00 },
            { 0x00, 0xcc, 0x33 },
            { 0x00, 0xcc, 0x66 },
            { 0x00, 0xcc, 0x99 },
            { 0x00, 0xcc, 0xcc },
            { 0x00, 0xcc, 0xff },
            { 0x00, 0xff, 0x00 },
            { 0x00, 0xff, 0x33 },
            { 0x00, 0xff, 0x66 },
            { 0x00, 0xff, 0x99 },
            { 0x00, 0xff, 0xcc },
            { 0x00, 0xff, 0xff },
            { 0x33, 0x00, 0x00 },
            { 0x33, 0x00, 0x33 },
            { 0x33, 0x00, 0x66 },
            { 0x33, 0x00, 0x99 },
            { 0x33, 0x00, 0xcc },
            { 0x33, 0x00, 0xff },
            { 0x33, 0x33, 0x00 },
            { 0x33, 0x33, 0x33 },
            { 0x33, 0x33, 0x66 },
            { 0x33, 0x33, 0x99 },
            { 0x33, 0x33, 0xcc },
            { 0x33, 0x33, 0xff },
            { 0x33, 0x66, 0x00 },
            { 0x33, 0x66, 0x33 },
            { 0x33, 0x66, 0x66 },
            { 0x33, 0x66, 0x99 },
            { 0x33, 0x66, 0xcc },
            { 0x33, 0x66, 0xff },
            { 0x33, 0x99, 0x00 },
            { 0x33, 0x99, 0x33 },
            { 0x33, 0x99, 0x66 },
            { 0x33, 0x99, 0x99 },
            { 0x33, 0x99, 0xcc },
            { 0x33, 0x99, 0xff },
            { 0x33, 0xcc, 0x00 },
            { 0x33, 0xcc, 0x33 },
            { 0x33, 0xcc, 0x66 },
            { 0x33, 0xcc, 0x99 },
            { 0x33, 0xcc, 0xcc },
            { 0x33, 0xcc, 0xff },
            { 0x33, 0xff, 0x00 },
            { 0x33, 0xff, 0x33 },
            { 0x33, 0xff, 0x66 },
            { 0x33, 0xff, 0x99 },
            { 0x33, 0xff, 0xcc },
            { 0x33, 0xff, 0xff },
            { 0x66, 0x00, 0x00 },
            { 0x66, 0x00, 0x33 },
            { 0x66, 0x00, 0x66 },
            { 0x66, 0x00, 0x99 },
            { 0x66, 0x00, 0xcc },
            { 0x66, 0x00, 0xff },
            { 0x66, 0x33, 0x00 },
            { 0x66, 0x33, 0x33 },
            { 0x66, 0x33, 0x66 },
            { 0x66, 0x33, 0x99 },
            { 0x66, 0x33, 0xcc },
            { 0x66, 0x33, 0xff },
            { 0x66, 0x66, 0x00 },
            { 0x66, 0x66, 0x33 },
            { 0x66, 0x66, 0x66 },
            { 0x66, 0x66, 0x99 },
            { 0x66, 0x66, 0xcc },
            { 0x66, 0x66, 0xff },
            { 0x66, 0x99, 0x00 },
            { 0x66, 0x99, 0x33 },
            { 0x66, 0x99, 0x66 },
            { 0x66, 0x99, 0x99 },
            { 0x66, 0x99, 0xcc },
            { 0x66, 0x99, 0xff },
            { 0x66, 0xcc, 0x00 },
            { 0x66, 0xcc, 0x33 },
            { 0x66, 0xcc, 0x66 },
            { 0x66, 0xcc, 0x99 },
            { 0x66, 0xcc, 0xcc },
            { 0x66, 0xcc, 0xff },
            { 0x66, 0xff, 0x00 },
            { 0x66, 0xff, 0x33 },
            { 0x66, 0xff, 0x66 },
            { 0x66, 0xff, 0x99 },
            { 0x66, 0xff, 0xcc },
            { 0x66, 0xff, 0xff },
            { 0x99, 0x00, 0x00 },
            { 0x99, 0x00, 0x33 },
            { 0x99, 0x00, 0x66 },
            { 0x99, 0x00, 0x99 },
            { 0x99, 0x00, 0xcc },
            { 0x99, 0x00, 0xff },
            { 0x99, 0x33, 0x00 },
            { 0x99, 0x33, 0x33 },
            { 0x99, 0x33, 0x66 },
            { 0x99, 0x33, 0x99 },
            { 0x99, 0x33, 0xcc },
            { 0x99, 0x33, 0xff },
            { 0x99, 0x66, 0x00 },
            { 0x99, 0x66, 0x33 },
            { 0x99, 0x66, 0x66 },
            { 0x99, 0x66, 0x99 },
            { 0x99, 0x66, 0xcc },
            { 0x99, 0x66, 0xff },
            { 0x99, 0x99, 0x00 },
            { 0x99, 0x99, 0x33 },
            { 0x99, 0x99, 0x66 },
            { 0x99, 0x99, 0x99 },
            { 0x99, 0x99, 0xcc },
            { 0x99, 0x99, 0xff },
            { 0x99, 0xcc, 0x00 },
            { 0x99, 0xcc, 0x33 },
            { 0x99, 0xcc, 0x66 },
            { 0x99, 0xcc, 0x99 },
            { 0x99, 0xcc, 0xcc },
            { 0x99, 0xcc, 0xff },
            { 0x99, 0xff, 0x00 },
            { 0x99, 0xff, 0x33 },
            { 0x99, 0xff, 0x66 },
            { 0x99, 0xff, 0x99 },
            { 0x99, 0xff, 0xcc },
            { 0x99, 0xff, 0xff },
            { 0xcc, 0x00, 0x00 },
            { 0xcc, 0x00, 0x33 },
            { 0xcc, 0x00, 0x66 },
            { 0xcc, 0x00, 0x99 },
            { 0xcc, 0x00, 0xcc },
            { 0xcc, 0x00, 0xff },
            { 0xcc, 0x33, 0x00 },
            { 0xcc, 0x33, 0x33 },
            { 0xcc, 0x33, 0x66 },
            { 0xcc, 0x33, 0x99 },
            { 0xcc, 0x33, 0xcc },
            { 0xcc, 0x33, 0xff },
            { 0xcc, 0x66, 0x00 },
            { 0xcc, 0x66, 0x33 },
            { 0xcc, 0x66, 0x66 },
            { 0xcc, 0x66, 0x99 },
            { 0xcc, 0x66, 0xcc },
            { 0xcc, 0x66, 0xff },
            { 0xcc, 0x99, 0x00 },
            { 0xcc, 0x99, 0x33 },
            { 0xcc, 0x99, 0x66 },
            { 0xcc, 0x99, 0x99 },
            { 0xcc, 0x99, 0xcc },
            { 0xcc, 0x99, 0xff },
            { 0xcc, 0xcc, 0x00 },
            { 0xcc, 0xcc, 0x33 },
            { 0xcc, 0xcc, 0x66 },
            { 0xcc, 0xcc, 0x99 },
            { 0xcc, 0xcc, 0xcc },
            { 0xcc, 0xcc, 0xff },
            { 0xcc, 0xff, 0x00 },
            { 0xcc, 0xff, 0x33 },
            { 0xcc, 0xff, 0x66 },
            { 0xcc, 0xff, 0x99 },
            { 0xcc, 0xff, 0xcc },
            { 0xcc, 0xff, 0xff },
            { 0xff, 0x00, 0x00 },
            { 0xff, 0x00, 0x33 },
            { 0xff, 0x00, 0x66 },
            { 0xff, 0x00, 0x99 },
            { 0xff, 0x00, 0xcc },
            { 0xff, 0x00, 0xff },
            { 0xff, 0x33, 0x00 },
            { 0xff, 0x33, 0x33 },
            { 0xff, 0x33, 0x66 },
            { 0xff, 0x33, 0x99 },
            { 0xff, 0x33, 0xcc },
            { 0xff, 0x33, 0xff },
            { 0xff, 0x66, 0x00 },
            { 0xff, 0x66, 0x33 },
            { 0xff, 0x66, 0x66 },
            { 0xff, 0x66, 0x99 },
            { 0xff, 0x66, 0xcc },
            { 0xff, 0x66, 0xff },
            { 0xff, 0x99, 0x00 },
            { 0xff, 0x99, 0x33 },
            { 0xff, 0x99, 0x66 },
            { 0xff, 0x99, 0x99 },
            { 0xff, 0x99, 0xcc },
            { 0xff, 0x99, 0xff },
            { 0xff, 0xcc, 0x00 },
            { 0xff, 0xcc, 0x33 },
            { 0xff, 0xcc, 0x66 },
            { 0xff, 0xcc, 0x99 },
            { 0xff, 0xcc, 0xcc },
            { 0xff, 0xcc, 0xff },
            { 0xff, 0xff, 0x00 },
            { 0xff, 0xff, 0x33 },
            { 0xff, 0xff, 0x66 },
            { 0xff, 0xff, 0x99 },
            { 0xff, 0xff, 0xcc },
            { 0xff, 0xff, 0xff },

            /* MS Windows standard system colors not in the above cube */
            { 0x80, 0x00, 0x00 },
            { 0x00, 0x80, 0x00 },
            { 0x80, 0x80, 0x00 },
            { 0x00, 0x00, 0x80 },
            { 0x80, 0x00, 0x80 },
            { 0x00, 0x80, 0x80 },
            { 0xc0, 0xc0, 0xc0 },
            { 0xc0, 0xdc, 0xc0 },
            { 0xa6, 0xca, 0xf0 },
            { 0xff, 0xfb, 0xf0 },
            { 0xa0, 0xa0, 0xa4 },
            { 0x80, 0x80, 0x80 },

            /* 
             *   We have 216 colors in the cube plus 12 above, which
             *   leaves us with 28 colors.  Allocate the remaining colors
             *   to ramps of red, green, blue, and gray, each with 7
             *   levels, filling in holes in the color cube for those
             *   colors.
             *   
             *   However, note that in the Windows colors above, we
             *   already have a mid-level entry for each color (0x80,
             *   0x00, 0x00 for red, likewise for blue, green, and gray),
             *   so we can omit this from our ramp.  
             */
            { 0x11, 0x00, 0x00 },
            { 0x22, 0x00, 0x00 },
            { 0x44, 0x00, 0x00 },
            { 0x55, 0x00, 0x00 },
            { 0xb2, 0x00, 0x00 },
            { 0xdd, 0x00, 0x00 },
            { 0xee, 0x00, 0x00 },

            { 0x00, 0x11, 0x00 },
            { 0x00, 0x22, 0x00 },
            { 0x00, 0x44, 0x00 },
            { 0x00, 0x55, 0x00 },
            { 0x00, 0xb2, 0x00 },
            { 0x00, 0xdd, 0x00 },
            { 0x00, 0xee, 0x00 },

            { 0x00, 0x00, 0x11 },
            { 0x00, 0x00, 0x22 },
            { 0x00, 0x00, 0x44 },
            { 0x00, 0x00, 0x55 },
            { 0x00, 0x00, 0xb2 },
            { 0x00, 0x00, 0xdd },
            { 0x00, 0x00, 0xee },

            { 0x11, 0x11, 0x11 },
            { 0x22, 0x22, 0x22 },
            { 0x44, 0x44, 0x44 },
            { 0x55, 0x55, 0x55 },
            { 0xb2, 0xb2, 0xb2 },
            { 0xdd, 0xdd, 0xdd },
            { 0xee, 0xee, 0xee }
        };
        const size_t rainbow_count = sizeof(rainbow)/sizeof(rainbow[0]);
        
        /* tell the decompressor to quantize the picture to 256 colors */
        cinfo.quantize_colors = TRUE;
        cinfo.desired_number_of_colors = 256;
        cinfo.two_pass_quantize = TRUE;
        cinfo.enable_2pass_quant = TRUE;
        cinfo.dither_mode = JDITHER_FS;

        /* set up our standard rainbow palette */
        cinfo.actual_number_of_colors = rainbow_count;
        cinfo.colormap = (*cinfo.mem->alloc_sarray)
                         ((j_common_ptr)&cinfo, JPOOL_IMAGE,
                          (JDIMENSION)rainbow_count, (JDIMENSION)3);

        for (i = 0 ; i < rainbow_count ; ++i)
        {
            cinfo.colormap[0][i] = rainbow[i].r;
            cinfo.colormap[1][i] = rainbow[i].g;
            cinfo.colormap[2][i] = rainbow[i].b;
        }
    }
    else
    {
        /* 
         *   we want a 24-bit format - ask for RGB output so that the jpeg
         *   decoder will convert other formats for us 
         */
        cinfo.out_color_space = JCS_RGB;
    }

    /* 
     *   Step 5: start decompression; since suspension isn't possible with
     *   a file data source, "starting" is sufficient to decompress the
     *   entire image.  
     */
    jpeg_start_decompress(&cinfo);

    /* 
     *   allocate a buffer big enough to hold the entire image - there are
     *   (height times width) pixels, and cinfo.output_components bytes
     *   per pixel 
     */
    image_buf_size = cinfo.output_width * cinfo.output_height
                     * cinfo.output_components;
    image_buf_ = (OS_HUGEPTR(unsigned char))os_alloc_huge(image_buf_size);
    if (image_buf_ == 0)
    {
        /* failed to allocate a buffer - clean up and return failure */
        jpeg_destroy_decompress(&cinfo);
        osfcls(infile);
        return 1;
    }

    /* we now know the dimensions - store them */
    width_ = cinfo.output_width;
    height_ = cinfo.output_height;

    /* JSAMPLEs per row in output buffer */
    row_stride = cinfo.output_width * cinfo.output_components;

    /* allocate a temporary one-row-high sample array */
    row_buffer = (*cinfo.mem->alloc_sarray)
                 ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);

    /* Step 6: read scan lines until we run out of scan lines */
    while (cinfo.output_scanline < cinfo.output_height)
    {
        /* read the next scan line */
        jpeg_read_scanlines(&cinfo, row_buffer, 1);

        /* get the appropriate type of scan line */
        if (cinfo.out_color_components == 3 && cinfo.output_components == 3)
        {
            /* move an RGB scan line into the buffer */
            j_putRGBScanline(row_buffer[0], width_, image_buf_,
                             cinfo.output_scanline - 1);
        }
        else if ((cinfo.out_color_components == 3
                  && cinfo.output_components == 1)
                 || (cinfo.out_color_components == 1
                     && cinfo.output_components == 1))
        {
            /* move a palette-indexed scan line into the buffer */
            j_putMapScanline(row_buffer[0], width_, image_buf_,
                             cinfo.output_scanline - 1);
        }
        else if (cinfo.out_color_components == 1
                 && cinfo.out_color_components == 3)
        {
            /* move a grayscale scan line into the buffer */
            j_putGrayScanlineToRGB(row_buffer[0], width_,
                                   image_buf_, cinfo.output_scanline-1);
        }
    }

    /*
     *   If we're converting the image to a palette-based 256-color image,
     *   remember the palette.  
     */
    if (convert_256_color)
    {
        HTML_color_t *palptr;
        unsigned int indx;

        /* create the palette */
        palette_size_ = cinfo.actual_number_of_colors;
        if (palette_size_ > 256)
            palette_size_ = 256;
        palette_ = new HTML_color_t[palette_size_];

        /* store the palette */
        for (indx = 0, palptr = palette_ ; indx < palette_size_ ;
             ++indx, ++palptr)
        {
            // $$$ should change these array dereferences to use pointers
            *palptr = HTML_make_color(cinfo.colormap[0][indx],
                                      cinfo.colormap[1][indx],
                                      cinfo.colormap[2][indx]);
        }
    }

    /* Step 7: Finish decompression. */
    jpeg_finish_decompress(&cinfo);

    /* Step 8: Delete the JPEG decompression object */
    jpeg_destroy_decompress(&cinfo);

    /* 
     *   After finish_decompress, we can close the input file.  Here we
     *   postpone it until after no more JPEG errors are possible, so as
     *   to simplify the setjmp error logic above.  
     */
    osfcls(infile);

    /* success */
    return 0;
}

