/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/NPL/
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation. All
 * Rights Reserved.
 *
 * Contributor(s): 
 */

/*
 *    jpeg.c --- Glue code to Independent JPEG Group decoder library
 *    $Id: jpeg.cpp,v 1.27 2001/03/20 05:30:52 cls%seawood.org Exp $
 */



#include "nsIImgDecoder.h" // include if_struct.h Needs to be first
#include "nsIImgDCallbk.h"
#include "nsCRT.h"
#include "nsJPGDecoder.h"
#include "jpeg.h"
#include "ilErrors.h"
#include "il.h"

#include <ctype.h>      /* to declare isprint() */
#ifndef XP_MAC
#    include <sys/types.h>
#endif

/* This is a lame hack to get around a problem with boolean on Solaris 2.3 */
#if defined(__sun) && defined(__sparc) && defined(__svr4__) && (OSMINOR == 3)
#define HAVE_BOOLEAN
#undef MUST_UNDEF_HAVE_BOOLEAN_AFTER_INCLUDES
#endif

PR_BEGIN_EXTERN_C
#include "jpeglib.h"
#include "jerror.h"
PR_END_EXTERN_C

#ifdef XP_OS2
/* IBM-MAS: We removed setjmp.h from XP_core.h, now we need it here. */
/*          We need to see if we can fix hwthreads/XP_CORE correctly.. */
#include <setjmp.h>
#endif

#ifdef PROFILE
#    pragma profile on
#endif

/* Normal JFIF markers can't have more bytes than this. */
#define MAX_JPEG_MARKER_LENGTH  (((PRUint32)1 << 16) - 1)

#ifdef DEBUG
static int il_debug_jpg = 0;
static PRLogModuleInfo *il_log_module_jpg = NULL;
#define ILTRACE(l,t) { if(il_debug_jpg>l) {PR_LOG(il_log_module_jpg, 1, t);} } 
#else
#define ILTRACE(l,t) {}
#endif

/*
 * States that the jpeg decoder might be in
 */
typedef enum {
    JPEG_HEADER,                          /* Reading JFIF headers */
    JPEG_START_DECOMPRESS,
    JPEG_DECOMPRESS_PROGRESSIVE,          /* Output progressive pixels */
    JPEG_DECOMPRESS_SEQUENTIAL,           /* Output sequential pixels */
    JPEG_FINAL_PROGRESSIVE_SCAN_OUTPUT,
    JPEG_DONE,
    JPEG_SINK_NON_JPEG_TRAILER,          /* Some image files have a */
                                         /* non-JPEG trailer */
    JPEG_ERROR    
} jstate;
    
typedef struct {
    struct jpeg_error_mgr pub;  /* "public" fields for IJG library*/
    jmp_buf setjmp_buffer;      /* For handling catastropic errors */
} il_error_mgr;

/*
 * Structure used to manage the JPEG decoder stream.
 */
typedef struct jpeg_struct {
    jstate state;               /* Decoder FSM state */
    int pass_num;
    int completed_output_passes;

    int rows_per_chunk;
    void *timeout;
    
    /* One scanline's worth of post-processed sample data */
    JSAMPARRAY samples;
    JSAMPARRAY samples3;
    /* IJG JPEG library decompressor state */
    struct jpeg_decompress_struct jd;
    il_error_mgr jerr;
    il_container *ic;
} jpeg_struct;




/* Possible states for JPEG source manager */
enum data_source_state {
    dss_consuming_backtrack_buffer = 0, /* Must be zero for init purposes */
    dss_consuming_netlib_buffer
};

/*
 *  Implementation of a JPEG src object that understands our state machine
 */
typedef struct {
    /* public fields; must be first in this struct! */
    struct jpeg_source_mgr pub;

    jpeg_struct *js;              /* pointer to netlib stream object */

    int bytes_to_skip;            /* remaining bytes to skip */

    enum data_source_state state;

    JOCTET *netlib_buffer;        /* next buffer for fill_input_buffer */
    PRUint32 netlib_buflen;

    /*
     * Buffer of "remaining" characters left over after a call to 
     * fill_input_buffer(), when no additional data is available.
     */ 
    JOCTET *backtrack_buffer;
    size_t backtrack_buffer_size; /* Allocated size of backtrack_buffer     */
    size_t backtrack_buflen;      /* Offset of end of active backtrack data */
    size_t backtrack_num_unread_bytes; /* Length of active backtrack data   */
} il_source_mgr;


/* Override the standard error method in the IJG JPEG decoder code. */
void PR_CALLBACK
il_error_exit (j_common_ptr cinfo)
{
    int error_code;
    il_error_mgr *err = (il_error_mgr *) cinfo->err;

#ifdef DEBUG
#if 0
  /*ptn fix later */
    if (il_debug >= 1) {
        char buffer[JMSG_LENGTH_MAX];

        /* Create the message */
        (*cinfo->err->format_message) (cinfo, buffer);

        ILTRACE(1,("%s\n", buffer));
    }
#endif
#endif

    /* Convert error to a browser error code */
    if (cinfo->err->msg_code == JERR_OUT_OF_MEMORY)
        error_code = MK_OUT_OF_MEMORY;
    else
        error_code = MK_IMAGE_LOSSAGE;
        
    /* Return control to the setjmp point. */
    longjmp(err->setjmp_buffer, error_code);
}


void PR_CALLBACK
init_source (j_decompress_ptr jd)
{
}

/*-----------------------------------------------------------------------------
 * This is the callback routine from the IJG JPEG library used to supply new
 * data to the decompressor when its input buffer is exhausted.  It juggles
 * multiple buffers in an attempt to avoid unnecessary copying of input data.
 *
 * (A simpler scheme is possible: It's much easier to use only a single
 * buffer; when fill_input_buffer() is called, move any unconsumed data
 * (beyond the current pointer/count) down to the beginning of this buffer and
 * then load new data into the remaining buffer space.  This approach requires
 * a little more data copying but is far easier to get right.)
 *
 * At any one time, the JPEG decompressor is either reading from the netlib
 * input buffer, which is volatile across top-level calls to the IJG library,
 * or the "backtrack" buffer.  The backtrack buffer contains the remaining
 * unconsumed data from the netlib buffer after parsing was suspended due
 * to insufficient data in some previous call to the IJG library.
 *
 * When suspending, the decompressor will back up to a convenient restart
 * point (typically the start of the current MCU). The variables
 * next_input_byte & bytes_in_buffer indicate where the restart point will be
 * if the current call returns FALSE.  Data beyond this point must be
 * rescanned after resumption, so it must be preserved in case the decompressor
 * decides to backtrack.
 *
 * Returns:
 *  TRUE if additional data is available, FALSE if no data present and
 *   the JPEG library should therefore suspend processing of input stream
 *---------------------------------------------------------------------------*/
boolean PR_CALLBACK
fill_input_buffer (j_decompress_ptr jd)
{
    il_source_mgr *src = (il_source_mgr *)jd->src;
    enum data_source_state src_state = src->state;
    PRUint32 bytesToSkip, new_backtrack_buflen, new_buflen, roundup_buflen;
    unsigned char *new_buffer;

    ILTRACE(5,("il:jpeg: fill, state=%d, nib=0x%x, bib=%d", src_state,
               src->pub.next_input_byte, src->pub.bytes_in_buffer));

    switch (src_state) {

    /* Decompressor reached end of backtrack buffer. Return netlib buffer.*/
    case dss_consuming_backtrack_buffer:
        new_buffer = (unsigned char *)src->netlib_buffer;
        new_buflen = src->netlib_buflen;
        if ((new_buffer == NULL) || (new_buflen == 0))
            goto suspend;

        /*
         * Clear, so that repeated calls to fill_input_buffer() do not
         * deliver the same netlib buffer more than once.
         */
        src->netlib_buflen = 0;
        
        /* Discard data if asked by skip_input_data(). */
        bytesToSkip = src->bytes_to_skip;
        if (bytesToSkip) {
            if (bytesToSkip < new_buflen) {
                /* All done skipping bytes; Return what's left. */
                new_buffer += bytesToSkip;
                new_buflen -= bytesToSkip;
                src->bytes_to_skip = 0;
            } else {
                /* Still need to skip some more data in the future */
                src->bytes_to_skip -= (size_t)new_buflen;
                goto suspend;
            }
        }

        /* Save old backtrack buffer parameters, in case the decompressor
         * backtracks and we're forced to restore its contents.
         */
        src->backtrack_num_unread_bytes = src->pub.bytes_in_buffer;

        src->pub.next_input_byte = new_buffer;
        src->pub.bytes_in_buffer = (size_t)new_buflen;
        src->state = dss_consuming_netlib_buffer;
        return TRUE;

    /* Reached end of netlib buffer. Suspend */
    case dss_consuming_netlib_buffer:
        if (src->pub.next_input_byte != src->netlib_buffer) {
            /* Backtrack data has been permanently consumed. */
            src->backtrack_num_unread_bytes = 0;
            src->backtrack_buflen = 0;
        }

        /* Save remainder of netlib buffer in backtrack buffer */
        new_backtrack_buflen = src->pub.bytes_in_buffer + src->backtrack_buflen;
                
        /* Make sure backtrack buffer is big enough to hold new data. */
        if (src->backtrack_buffer_size < new_backtrack_buflen) {

            /* Round up to multiple of 16 bytes. */
            roundup_buflen = ((new_backtrack_buflen + 15) >> 4) << 4;
            if (src->backtrack_buffer_size) {
                src->backtrack_buffer =
                    (JOCTET *)PR_REALLOC(src->backtrack_buffer, roundup_buflen);
            } else {
                src->backtrack_buffer = (JOCTET*)PR_MALLOC(roundup_buflen);
            }

            /* Check for OOM */
            if (! src->backtrack_buffer) {
                j_common_ptr cinfo = (j_common_ptr)(&src->js->jd);
                cinfo->err->msg_code = JERR_OUT_OF_MEMORY;
                il_error_exit(cinfo);
            }
                
            src->backtrack_buffer_size = (size_t)roundup_buflen;

            /* Check for malformed MARKER segment lengths. */
            if (new_backtrack_buflen > MAX_JPEG_MARKER_LENGTH)
                il_error_exit((j_common_ptr)(&src->js->jd));
        }

        /* Copy remainder of netlib buffer into backtrack buffer. */
        nsCRT::memmove(src->backtrack_buffer + src->backtrack_buflen,
                  src->pub.next_input_byte,
                  src->pub.bytes_in_buffer);

        /* Point to start of data to be rescanned. */
        src->pub.next_input_byte = src->backtrack_buffer +
            src->backtrack_buflen - src->backtrack_num_unread_bytes;
        src->pub.bytes_in_buffer += src->backtrack_num_unread_bytes;
        src->backtrack_buflen = (size_t)new_backtrack_buflen;
        
        src->state = dss_consuming_backtrack_buffer;
        goto suspend;
            
    default:
        PR_ASSERT(0);
        return FALSE;
    }

  suspend:
    ILTRACE(5,("         Suspending, bib=%d", src->pub.bytes_in_buffer));
    return FALSE;
}

void PR_CALLBACK
skip_input_data (j_decompress_ptr jd, long num_bytes)
{
    il_source_mgr *src = (il_source_mgr *)jd->src;

#ifdef DEBUG
    ILTRACE(5, ("il:jpeg: skip_input_data js->buf=0x%x js->buflen=%d skip=%d",
                src->netlib_buffer, src->netlib_buflen,
                num_bytes));
#endif

    if (num_bytes > (long)src->pub.bytes_in_buffer) {
        /*
         * Can't skip it all right now until we get more data from
         * network stream. Set things up so that fill_input_buffer
         * will skip remaining amount.
         */
        src->bytes_to_skip = (size_t)num_bytes - src->pub.bytes_in_buffer;
        src->pub.next_input_byte += src->pub.bytes_in_buffer;
        src->pub.bytes_in_buffer = 0;
    } else {
        /* Simple case. Just advance buffer pointer */
        src->pub.bytes_in_buffer -= (size_t)num_bytes;
        src->pub.next_input_byte += num_bytes;
    }
}


/*
 * Terminate source --- called by jpeg_finish_decompress() after all
 * data has been read to clean up JPEG source manager.
 */
void PR_CALLBACK
term_source (j_decompress_ptr jd)
{
    /* No work necessary here */
}


/*-----------------------------------------------------------------------------
 * Setup a JPEG source object for streaming data in a demand-driven
 * fashion into the IJG JPEG decompression library.  A JPEG source
 * object consists of a set of callback functions which the
 * decompressor library calls when it needs more data or to seek ahead
 * in the input stream.
 *
 * Returns:
 *  TRUE if setup succeeds, FALSE otherwise.
 *---------------------------------------------------------------------------*/
PRBool
setup_jpeg_src (j_decompress_ptr jd, jpeg_struct *js)
{
    il_source_mgr *src;

    if (jd->src == NULL) {
        src = PR_NEWZAP(il_source_mgr);
        if (!src) {
            ILTRACE(1,("il:jpeg: src manager memory lossage"));
            return PR_FALSE;
        }
        jd->src = (struct jpeg_source_mgr *) src;
    }

    src = (il_source_mgr *)jd->src;
    src->js = js;

    /* Setup callback functions. */
    src->pub.init_source = init_source;
    src->pub.fill_input_buffer = fill_input_buffer;
    src->pub.skip_input_data = skip_input_data;
    src->pub.resync_to_restart = jpeg_resync_to_restart;
    src->pub.term_source = term_source;

    return PR_TRUE;
}



/*
 * Macros for fetching data from the data source module.
 *
 * At all times, cinfo->src->next_input_byte and ->bytes_in_buffer reflect
 * the current restart point; we update them only when we have reached a
 * suitable place to restart if a suspension occurs.
 */

/* Declare and initialize local copies of input pointer/count */
#define INPUT_VARS(cinfo)  \
    struct jpeg_source_mgr * datasrc = (cinfo)->src;  \
    const JOCTET * next_input_byte = datasrc->next_input_byte;  \
    size_t bytes_in_buffer = datasrc->bytes_in_buffer

/* Unload the local copies --- do this only at a restart boundary */
#define INPUT_SYNC(cinfo)  \
    ( datasrc->next_input_byte = next_input_byte,  \
      datasrc->bytes_in_buffer = bytes_in_buffer )

/* Reload the local copies --- seldom used except in MAKE_BYTE_AVAIL */
#define INPUT_RELOAD(cinfo)  \
    ( next_input_byte = datasrc->next_input_byte,  \
      bytes_in_buffer = datasrc->bytes_in_buffer )

/* Internal macro for INPUT_BYTE and INPUT_2BYTES: make a byte available.
 * Note we do *not* do INPUT_SYNC before calling fill_input_buffer,
 * but we must reload the local copies after a successful fill.
 */
#define MAKE_BYTE_AVAIL(cinfo,action)  \
    if (bytes_in_buffer == 0) {  \
      if (! (*datasrc->fill_input_buffer) (cinfo))  \
        { action; }  \
      INPUT_RELOAD(cinfo);  \
    }  \
    bytes_in_buffer--

/* Read a byte into variable V.
 * If must suspend, take the specified action (typically "return FALSE").
 */
#define INPUT_BYTE(cinfo,V,action)  \
    MAKESTMT( MAKE_BYTE_AVAIL(cinfo,action); \
          V = GETJOCTET(*next_input_byte++); )

/* As above, but read two bytes interpreted as an unsigned 16-bit integer.
 * V should be declared unsigned int or perhaps INT32.
 */
#define INPUT_2BYTES(cinfo,V,action)  \
    MAKESTMT( MAKE_BYTE_AVAIL(cinfo,action); \
          V = ((unsigned int) GETJOCTET(*next_input_byte++)) << 8; \
          MAKE_BYTE_AVAIL(cinfo,action); \
          V += GETJOCTET(*next_input_byte++); )


/* Process the COM marker segment, which contains human-readable comments. */
boolean PR_CALLBACK
il_jpeg_COM_handler (j_decompress_ptr cinfo)
{
    PRUintn length;
    char *comment;
    unsigned int ch;

    il_source_mgr *src = (il_source_mgr*) cinfo->src;
    il_container *ic = src->js->ic;
    static unsigned int lastch = 0;

    INPUT_VARS(cinfo);

    /* Get 16-bit comment length word. */
    INPUT_2BYTES(cinfo, length, return FALSE);
    if (length < 2) {
       cinfo->err->msg_code = JERR_BAD_LENGTH;
       il_error_exit((j_common_ptr)cinfo);
    }
    length -= 2;            /* discount the length word itself */
  
    PR_FREEIF(ic->comment);
    comment = ic->comment = (char *)PR_MALLOC(length + 1);
    ic->comment_length = length;
    
    if (!ic->comment) {
        skip_input_data(cinfo, length + 2);
        return TRUE;
    }

    /* Emit the character in a readable form.
     * Newlines in CR, CR/LF, or LF form will be printed as one newline.
     */
    while (length-- > 0) {
        INPUT_BYTE(cinfo, ch, return FALSE);

        if (ch == '\r') {
            *comment++ = '\n';
        } else if (ch == '\n') {
            if (lastch != '\r')
                *comment++ = '\n';
        } else if (isprint(ch) || !ch) {
            *comment++ = ch;
        }
        lastch = ch;
    }

    *comment = 0;

    INPUT_SYNC(cinfo);

    return TRUE;
}

PRBool 
il_jpeg_init(il_container *ic)
{
    jpeg_struct *js;
    j_decompress_ptr jd;
    NI_ColorSpace *src_color_space = ic->src_header->color_space;
    NI_RGBBits *rgb = &src_color_space->bit_alloc.rgb;

    js = PR_NEWZAP(jpeg_struct);
    if (!js) {
        ILTRACE(1,("il:jpeg: jpeg_struct memory lossage"));
        return PR_FALSE;
    }

    /* Init jpeg_struct */
    ic->ds = js;
    js->state = JPEG_HEADER;
    js->samples = NULL;
    js->samples3 = NULL;
    js->ic = ic;

    jd = &js->jd;

    /* Install our error handler because the default is to exit(). */
    jd->err = jpeg_std_error(&js->jerr.pub);
    js->jerr.pub.error_exit = il_error_exit;

    /* Control returns here if an error occurs before setup completes. */
    if(setjmp(js->jerr.setjmp_buffer)) {
        /* Free up all the data structures */
        il_jpeg_abort(ic);
        return PR_FALSE;
    }

#ifdef DEBUG
#if 0
    if (il_debug > 20) {
        jd->err->trace_level = 99;
    }
#endif
#endif
    jpeg_create_decompress(jd);

    /* Setup jpeg data source object */
    if (!setup_jpeg_src(jd, js)) {
      ILTRACE(1,("il:jpeg: jpeg source memory lossage"));
          /* Free up all the data structures */
          il_jpeg_abort(ic);
      return PR_FALSE;
    }

    /* Insert custom COM comment marker processor. */
    jpeg_set_marker_processor(jd, JPEG_COM, il_jpeg_COM_handler);


    /* Initialize the container's source image header. */
    src_color_space->type = NI_TrueColor;
    src_color_space->pixmap_depth = 24;
    rgb->red_bits = 8;
    rgb->red_shift = 16;
    rgb->green_bits = 8;
    rgb->green_shift = 8;
    rgb->blue_bits = 8;
    rgb->blue_shift = 0;

    return PR_TRUE;
}


/*-----------------------------------------------------------------------------
 * Calling this routine sends scanlines to the front-end for display.
 * Scanlines will be emitted until the entire scan has been displayed or
 * until insufficient input data is available to continue output.
 *
 * The maximum number of scanlines to be output is controlled by the
 * `num_scanlines' parameter.  Set num_scanlines to -1 to output scanlines
 * until input data is exhausted.
 *
 * Returns:
 *  TRUE if output was discontinued due to lack of input data
 *  FALSE, otherwise
 *---------------------------------------------------------------------------*/
int
output_jpeg_scanlines(il_container *ic, int num_scanlines)
{
    jpeg_struct *js = (jpeg_struct *)ic->ds;
    j_decompress_ptr jd = &js->jd;
    int input_exhausted;
    int pass;
    
#ifdef DEBUG
    PRUintn start_scanline = jd->output_scanline;
#endif

    if (js->state == JPEG_FINAL_PROGRESSIVE_SCAN_OUTPUT)
        pass = IL_FINAL_PASS;
    else
        pass = js->completed_output_passes + 1;

    while ((jd->output_scanline < jd->output_height) && num_scanlines--) {
        JSAMPROW samples;
        
        /* Request one scanline.  Returns 0 or 1 scanlines. */
        int ns = jpeg_read_scanlines(jd, js->samples, 1);
        ILTRACE(15,("il:jpeg: scanline %d, ns = %d",
                    jd->output_scanline, ns));
        if (ns != 1) {
            ILTRACE(5,("il:jpeg: suspending scanline"));
            input_exhausted = TRUE;
            goto done;
        }

        /* If grayscale image ... */
        if (jd->output_components == 1) {
            JSAMPLE j, *j1, *j1end, *j3;

            /* Convert from grayscale to RGB. */
            j1 = js->samples[0];
            j1end = j1 + jd->output_width;
            j3 = js->samples3[0];
            while (j1 < j1end) {
                j = *j1++;
                j3[0] = j;
                j3[1] = j;
                j3[2] = j;
                j3 += 3;
            }
            samples = js->samples3[0];
        } else {        /* 24-bit color image */
            samples = js->samples[0];
        }

        ic->imgdcb->ImgDCBHaveRow( 0, samples, 0, jd->output_width, jd->output_scanline-1,
                    1, ilErase, pass);
    }

    input_exhausted = FALSE;

  done:

#ifdef DEBUG
    if (start_scanline != jd->output_scanline)
        ILTRACE(4, ("il: jpeg: Input pass=%2d, next input scanline=%3d,"
                    " emitted %3d - %3d\n",
                    jd->input_scan_number, jd->input_iMCU_row * 16,
                    start_scanline, jd->output_scanline - 1));

#endif
    
    return input_exhausted;
}

#define JPEG_OUTPUT_CHUNK_SIZE     150000

/* Timeout durations, in milliseconds */

/* Delay between displaying chunks of pixels for the first scan. */
#define JPEG_TIMEOUT_INITIAL_DELAY   32

/* Delay between displaying chunks of pixels for subsequent scans */
#define JPEG_TIMEOUT_DELAY           250

static void
jpeg_timeout_callback(void *closure)
{
    PRUint32 delay;
    jpeg_struct *js = (jpeg_struct *)closure;
    j_decompress_ptr jd = &js->jd;

    if (jd->input_scan_number == 1)
        delay = JPEG_TIMEOUT_INITIAL_DELAY;
    else
        delay = JPEG_TIMEOUT_DELAY;

    /*
     * Perform incremental display of progressive scans,
     * except don't display unless enough time has elapsed
     * since the previous scan was displayed.
     */

    if (js->pass_num != js->completed_output_passes + 1) {
        if (! jpeg_start_output(jd, jd->input_scan_number)) {
            ILTRACE(1, ("il: jpeg: jpeg_start_output returned"
                        "FALSE!\n"));
            goto done;
        }
        js->pass_num = js->completed_output_passes + 1;
    }
    
    js->timeout = NULL;

    /* If there's no more data to process for this scan,
       wait until jpeg_write() wakes us up by scheduling a
       new timeout */
    if (output_jpeg_scanlines(js->ic, js->rows_per_chunk))
        return;
    
    /* If we're at the end of this progressive scan ... */
    if (jd->output_scanline == jd->output_height) {
        if (jpeg_finish_output(jd))
            js->completed_output_passes++;
    }

  done:
    js->timeout = js->ic->imgdcb->ImgDCBSetTimeout(jpeg_timeout_callback, js, delay);
}

/*
 * Force the display of scans, even if no data has entered the netlib
 * buffer since jpeg_timeout_callback() was run.  This will flush
 * pixels to the screen during a long pause in a bursty data source.
 */
#if 0
static void
jpeg_idle_callback(void *closure)
{
    il_container *ic = (il_container *)closure;
    il_jpeg_write(ic, NULL, 0);
}
#endif

int
il_jpeg_write(il_container *ic, const unsigned char *buf, int32 len)
{
    int row_stride, status;
    int input_exhausted;
  int error_code;

    jpeg_struct *js = (jpeg_struct *)ic->ds;


  
     /* If this js == NULL, chances are the netlib 
       continued to send data after the image stream was closed. */
    if (!js) {
#ifdef DEBUG
        ILTRACE(1,("Netlib sent data after the image stream was closed\n"));
#endif
        return MK_IMAGE_LOSSAGE;
    }

    j_decompress_ptr jd = &js->jd;
    il_source_mgr *src = (il_source_mgr*) js->jd.src;
    NI_PixmapHeader *img_header = &ic->image->header;
#ifndef M12N                    /* XXXM12N Get rid of this */
    NI_PixmapHeader *src_header = ic->src_header;
    NI_ColorMap *cmap = &src_header->color_space->cmap;
#endif /* M12N */

    /* Return here if there is a fatal error. */
    if ((error_code = setjmp(js->jerr.setjmp_buffer)) != 0) {
        /* Free up all the data structures */
        il_jpeg_abort(ic);
        return error_code;
    }

    /* Register new buffer contents with data source manager. */
    src->netlib_buffer = (JOCTET*)buf;
    src->netlib_buflen = (PRUint32)len;

    input_exhausted = 0;
    while (! input_exhausted) {
        ILTRACE(5,("il:jpeg: write, state=%d, buf=0x%x, len=%d",
                   js->state, buf, len));

        switch (js->state) {
        case JPEG_HEADER:
            if (jpeg_read_header(jd, TRUE) != JPEG_SUSPENDED) {
#ifndef M12N                    /* XXXM12N Get rid of this */
                cmap->map = 0;
                cmap->num_colors = ic->cs->default_map_size;
#endif /* M12N */
                ic->src_header->width = jd->image_width;
                ic->src_header->height = jd->image_height;
                if ((status = ic->imgdcb->ImgDCBImageSize()) != 0) {
                    ILTRACE(1,("il:jpeg: MEM il_size"));
                    return status;
                }

                ic->imgdcb->ImgDCBSetupColorspaceConverter(); /* XXXM12N Should check
                                                       return code. */
                if(!img_header->widthBytes)
                    return JPEG_ERROR;
                else
                    js->rows_per_chunk =
                                JPEG_OUTPUT_CHUNK_SIZE / img_header->widthBytes;
                
                /* FIXME -- Should reset dct_method and dither mode
                 * for final pass of progressive JPEG
                 */
                jd->dct_method = JDCT_FASTEST;
                jd->dither_mode = JDITHER_ORDERED;
                jd->do_fancy_upsampling = FALSE;
                jd->enable_2pass_quant = FALSE;
                jd->do_block_smoothing = TRUE;
                
                /*
                 * Don't allocate a giant and superfluous memory buffer
                 * when the image is a sequential JPEG.
                 */
                jd->buffered_image = jpeg_has_multiple_scans(jd);

                /* Used to set up image size so arrays can be allocated */
                jpeg_calc_output_dimensions(jd);

                /*
                 * Make a one-row-high sample array that will go away
                 * when done with image. Always make it big enough to
                 * hold an RGB row.  Since this uses the IJG memory
                 * manager, it must be allocated before the call to
                 * jpeg_start_compress().
                 */
                row_stride = jd->output_width * jd->output_components;
                js->samples = (*jd->mem->alloc_sarray)((j_common_ptr) jd,
                                                       JPOOL_IMAGE,
                                                       row_stride, 1);

                /* Allocate RGB buffer for conversion from greyscale. */
                if (jd->output_components != 3) {
                    row_stride = jd->output_width * 3;
                    js->samples3 = (*jd->mem->alloc_sarray)((j_common_ptr) jd,
                                                            JPOOL_IMAGE,
                                                            row_stride, 1);
                }
                js->state = JPEG_START_DECOMPRESS;

            } else {
                ILTRACE(5,("il:jpeg: suspending header"));
                input_exhausted = TRUE;
            }
            break;

        case JPEG_START_DECOMPRESS:
            if (jpeg_start_decompress(jd)) {

                /* If this is a progressive JPEG ... */
                if (jd->buffered_image) {
                    js->state = JPEG_DECOMPRESS_PROGRESSIVE;
                } else {
                    js->state = JPEG_DECOMPRESS_SEQUENTIAL;
                }
            }
                   
            break;

        case JPEG_DECOMPRESS_SEQUENTIAL:
            input_exhausted = output_jpeg_scanlines(ic, -1);

            /* If we've completed image output ... */
            if (jd->output_scanline == jd->output_height)
                js->state = JPEG_DONE;

            break;
            
        case JPEG_DECOMPRESS_PROGRESSIVE:

            /*
             * Set a timeout to trigger display of the next progressive scan.
             * Any scans which arrive in the intervening time will be displayed
             * instead.  Thus, the decoder adapts to the data arrival rate.
             */
            if (js->timeout == NULL) {
                PRUint32 delay;

                /*
                 * First time around, display the scan a little
                 * quicker than in subsequent scans.
                 */
                if (jd->input_scan_number == 1)
                    delay = JPEG_TIMEOUT_INITIAL_DELAY;
                else {
                    delay = JPEG_TIMEOUT_DELAY;
                }
                
                js->timeout = ic->imgdcb->ImgDCBSetTimeout(jpeg_timeout_callback, js, delay);
            
            }

            /* Eat all the available input data in the netlib buffer. */
            do {
                status = jpeg_consume_input(jd);
            } while (!((status == JPEG_SUSPENDED) ||
                       (status == JPEG_REACHED_EOI)));

            /* If we've parsed the whole input, do final display immediately. */
            if (status == JPEG_REACHED_EOI) {
                js->state = JPEG_FINAL_PROGRESSIVE_SCAN_OUTPUT;
                break;
            }
            
            input_exhausted = TRUE;
            
            break;

        case JPEG_FINAL_PROGRESSIVE_SCAN_OUTPUT:

            if ((jd->input_scan_number == jd->output_scan_number) &&
                (js->pass_num == js->completed_output_passes + 1)) {
                output_jpeg_scanlines(ic, -1);
                jpeg_finish_output(jd);
            } else {

                /* Abort the last output scan.
                 * We need to redraw the whole image.
                 */
                if (js->pass_num == js->completed_output_passes + 1)
                    jpeg_finish_output(jd);

                jpeg_start_output(jd, jd->input_scan_number);
                output_jpeg_scanlines(ic, -1);
                jpeg_finish_output(jd);
            }

            js->state = JPEG_DONE;

            /* Fall through ... */

        case JPEG_DONE:
            status = jpeg_finish_decompress(jd);
            
            /* Clear any pending timeouts */
            if (js->timeout) {
                ic->imgdcb->ImgDCBClearTimeout(js->timeout);
                js->timeout = NULL;
            }

            input_exhausted = TRUE;
            js->state = JPEG_SINK_NON_JPEG_TRAILER;  /* Be prepared for     */
                                                     /* non-JPEG data after */
                                                     /* EOI marker.         */
            break;

        case JPEG_SINK_NON_JPEG_TRAILER: /* Ignore non-JPEG trailer, if any */
            input_exhausted = TRUE;
            break;

        default:
            PR_ASSERT(0);
            break;
        }
    }

    return 0;
}

void
il_jpeg_abort(il_container *ic)
{
    jpeg_struct *js = (jpeg_struct *)ic->ds;
    
    if (js) {
        il_source_mgr *src = (il_source_mgr*) js->jd.src;

        /*
         * Free up the memory that we allocated ourselves (not memory we
         * allocated using the IJG memory manager - it will be freed in
         * a moment.)
         */
        if (src) {
            if (src->backtrack_buffer) {
                PR_FREEIF(src->backtrack_buffer);
                src->backtrack_buffer = NULL;
            }
            PR_FREEIF(src);
            js->jd.src = NULL;
        }

        /* Clear any pending timeouts */
        if (js->timeout) {
            ic->imgdcb->ImgDCBClearTimeout(js->timeout);
            js->timeout = NULL;
        }
        
        /*
         * Free all the ancillary memory used during JPEG decoding by the
         * IJG JPEG library. This has the side effect of freeing up js->samples
         * and js->samples3 which were allocated using the IJG memory manager.
         */
        jpeg_destroy_decompress(&js->jd);
        js->samples = NULL;
        js->samples3 = NULL;

        /* Finally, free up our private decoder structure. */
        PR_FREEIF(js);
        ic->ds = NULL;
    }
}

void
il_jpeg_complete(il_container *ic)
{
    il_jpeg_abort(ic);
    ic->imgdcb->ImgDCBHaveImageAll();
}

#ifdef PROFILE
#    pragma profile off
#endif

