mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-08 04:27:37 +00:00
993 lines
29 KiB
C++
993 lines
29 KiB
C++
/* -*- 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.0 (the "NPL"); you may not use this file except in
|
||
* compliance with the NPL. You may obtain a copy of the NPL at
|
||
* http://www.mozilla.org/NPL/
|
||
*
|
||
* Software distributed under the NPL is distributed on an "AS IS" basis,
|
||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
|
||
* for the specific language governing rights and limitations under the
|
||
* NPL.
|
||
*
|
||
* The Initial Developer of this code under the NPL is Netscape
|
||
* Communications Corporation. Portions created by Netscape are
|
||
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
|
||
* Reserved.
|
||
*/
|
||
|
||
/*
|
||
* jpeg.c --- Glue code to Independent JPEG Group decoder library
|
||
* $Id: jpeg.cpp,v 1.5 1999/08/10 22:39:49 dp%netscape.com Exp $
|
||
*/
|
||
|
||
|
||
|
||
#include "nsIImgDecoder.h" // include if_struct.h Needs to be first
|
||
#include "nsIImgDCallbk.h"
|
||
#include "dllcompat.h"
|
||
#include "nsJPGDecoder.h"
|
||
#include "jpeg.h"
|
||
#include "merrors.h"
|
||
#include "il.h"
|
||
|
||
#include <ctype.h> /* to declare isprint() */
|
||
#ifndef XP_MAC
|
||
# include <sys/types.h>
|
||
#endif
|
||
|
||
PR_BEGIN_EXTERN_C
|
||
#include "jpeglib.h"
|
||
#include "jerror.h"
|
||
|
||
extern int MK_OUT_OF_MEMORY;
|
||
PR_END_EXTERN_C
|
||
|
||
#ifdef XP_OS2_HACK
|
||
/* 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 (((uint32)1 << 16) - 1)
|
||
|
||
/*
|
||
* 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 */
|
||
uint32 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 data_source_state = src->state;
|
||
uint32 bytesToSkip, new_backtrack_buflen, new_buflen, roundup_buflen;
|
||
unsigned char *new_buffer;
|
||
|
||
ILTRACE(5,("il:jpeg: fill, state=%d, nib=0x%x, bib=%d", data_source_state,
|
||
src->pub.next_input_byte, src->pub.bytes_in_buffer));
|
||
|
||
switch (data_source_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. */
|
||
XP_BCOPY (src->pub.next_input_byte,
|
||
src->backtrack_buffer + src->backtrack_buflen,
|
||
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.
|
||
*---------------------------------------------------------------------------*/
|
||
static int
|
||
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 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 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)
|
||
{
|
||
uint 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);
|
||
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;
|
||
}
|
||
|
||
int 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 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 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 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 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
|
||
uint 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 50000
|
||
|
||
/* Timeout durations, in milliseconds */
|
||
|
||
/* Delay between displaying chunks of pixels for the first scan. */
|
||
#define JPEG_TIMEOUT_INITIAL_DELAY 100
|
||
|
||
/* Delay between displaying chunks of pixels for subsequent scans */
|
||
#define JPEG_TIMEOUT_DELAY 200
|
||
|
||
static void
|
||
jpeg_timeout_callback(void *closure)
|
||
{
|
||
uint32 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 = (uint32)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) {
|
||
uint32 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();
|
||
ic->imgdcb->ImgDCBHaveImageFrame();
|
||
}
|
||
|
||
#ifdef PROFILE
|
||
# pragma profile off
|
||
#endif
|
||
|