/* Copyright (C) 1994, 1995, 1997, 1998, 1999 artofcode LLC.  All rights reserved.
  
  This program is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published by the
  Free Software Foundation; either version 2 of the License, or (at your
  option) any later version.

  This program is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  General Public License for more details.

  You should have received a copy of the GNU General Public License along
  with this program; if not, write to the Free Software Foundation, Inc.,
  59 Temple Place, Suite 330, Boston, MA, 02111-1307.

*/

/*$Id: sdctd.c,v 1.2.6.1.2.1 2003/01/17 00:49:05 giles Exp $ */
/* DCT decoding filter stream */
#include "memory_.h"
#include "stdio_.h"
#include "jpeglib_.h"
#include "jerror_.h"
#include "gdebug.h"
#include "gsmemory.h"		/* for gsmalloc.h */
#include "gsmalloc.h"		/* for gs_memory_default */
#include "strimpl.h"
#include "sdct.h"
#include "sjpeg.h"

/* ------ DCTDecode ------ */

/* JPEG source manager procedures */
private void
dctd_init_source(j_decompress_ptr dinfo)
{
}
static const JOCTET fake_eoi[2] =
{0xFF, JPEG_EOI};
private boolean
dctd_fill_input_buffer(j_decompress_ptr dinfo)
{
    jpeg_decompress_data *jddp =
    (jpeg_decompress_data *) ((char *)dinfo -
			      offset_of(jpeg_decompress_data, dinfo));

    if (!jddp->input_eod)
	return FALSE;		/* normal case: suspend processing */
    /* Reached end of source data without finding EOI */
    WARNMS(dinfo, JWRN_JPEG_EOF);
    /* Insert a fake EOI marker */
    dinfo->src->next_input_byte = fake_eoi;
    dinfo->src->bytes_in_buffer = 2;
    jddp->faked_eoi = true;	/* so process routine doesn't use next_input_byte */
    return TRUE;
}
private void
dctd_skip_input_data(j_decompress_ptr dinfo, long num_bytes)
{
    struct jpeg_source_mgr *src = dinfo->src;
    jpeg_decompress_data *jddp =
    (jpeg_decompress_data *) ((char *)dinfo -
			      offset_of(jpeg_decompress_data, dinfo));

    if (num_bytes > 0) {
	if (num_bytes > src->bytes_in_buffer) {
	    jddp->skip += num_bytes - src->bytes_in_buffer;
	    src->next_input_byte += src->bytes_in_buffer;
	    src->bytes_in_buffer = 0;
	    return;
	}
	src->next_input_byte += num_bytes;
	src->bytes_in_buffer -= num_bytes;
    }
}
private void
dctd_term_source(j_decompress_ptr dinfo)
{
}

/* Set the defaults for the DCTDecode filter. */
private void
s_DCTD_set_defaults(stream_state * st)
{
    s_DCT_set_defaults(st);
}

/* Initialize DCTDecode filter */
private int
s_DCTD_init(stream_state * st)
{
    stream_DCT_state *const ss = (stream_DCT_state *) st;
    struct jpeg_source_mgr *src = &ss->data.decompress->source;

    src->init_source = dctd_init_source;
    src->fill_input_buffer = dctd_fill_input_buffer;
    src->skip_input_data = dctd_skip_input_data;
    src->term_source = dctd_term_source;
    src->resync_to_restart = jpeg_resync_to_restart;	/* use default method */
    ss->data.common->memory = ss->jpeg_memory;
    ss->data.decompress->dinfo.src = src;
    ss->data.decompress->skip = 0;
    ss->data.decompress->input_eod = false;
    ss->data.decompress->faked_eoi = false;
    ss->phase = 0;
    return 0;
}

/* Process a buffer */
private int
s_DCTD_process(stream_state * st, stream_cursor_read * pr,
	       stream_cursor_write * pw, bool last)
{
    stream_DCT_state *const ss = (stream_DCT_state *) st;
    jpeg_decompress_data *jddp = ss->data.decompress;
    struct jpeg_source_mgr *src = jddp->dinfo.src;
    int code;

    if_debug3('w', "[wdd]process avail=%u, skip=%u, last=%d\n",
	      (uint) (pr->limit - pr->ptr), (uint) jddp->skip, last);
    if (jddp->skip != 0) {
	long avail = pr->limit - pr->ptr;

	if (avail < jddp->skip) {
	    jddp->skip -= avail;
	    pr->ptr = pr->limit;
	    if (!last)
		return 0;	/* need more data */
	    jddp->skip = 0;	/* don't skip past input EOD */
	}
	pr->ptr += jddp->skip;
	jddp->skip = 0;
    }
    src->next_input_byte = pr->ptr + 1;
    src->bytes_in_buffer = pr->limit - pr->ptr;
    jddp->input_eod = last;
    switch (ss->phase) {
	case 0:		/* not initialized yet */
	    /*
	     * Adobe implementations seem to ignore leading garbage bytes,
	     * even though neither the standard nor Adobe's own
	     * documentation mention this.
	     */
	    while (pr->ptr < pr->limit && pr->ptr[1] != 0xff)
		pr->ptr++;
	    if (pr->ptr == pr->limit)
		return 0;
	    src->next_input_byte = pr->ptr + 1;
	    src->bytes_in_buffer = pr->limit - pr->ptr;
	    ss->phase = 1;
	    /* falls through */
	case 1:		/* reading header markers */
	    if ((code = gs_jpeg_read_header(ss, TRUE)) < 0)
		return ERRC;
	    pr->ptr =
		(jddp->faked_eoi ? pr->limit : src->next_input_byte - 1);
	    switch (code) {
		case JPEG_SUSPENDED:
		    return 0;
		    /*case JPEG_HEADER_OK: */
	    }
	    /* If we have a ColorTransform parameter, and it's not
	     * overridden by an Adobe marker in the data, set colorspace.
	     */
	    if (ss->ColorTransform >= 0 &&
		!jddp->dinfo.saw_Adobe_marker) {
		switch (jddp->dinfo.num_components) {
		    case 3:
			jddp->dinfo.jpeg_color_space =
			    (ss->ColorTransform ? JCS_YCbCr : JCS_RGB);
			/* out_color_space will default to JCS_RGB */
			break;
		    case 4:
			jddp->dinfo.jpeg_color_space =
			    (ss->ColorTransform ? JCS_YCCK : JCS_CMYK);
			/* out_color_space will default to JCS_CMYK */
			break;
		}
	    }
	    ss->phase = 2;
	    /* falls through */
	case 2:		/* start_decompress */
	    if ((code = gs_jpeg_start_decompress(ss)) < 0)
		return ERRC;
	    pr->ptr =
		(jddp->faked_eoi ? pr->limit : src->next_input_byte - 1);
	    if (code == 0)
		return 0;
	    ss->scan_line_size =
		jddp->dinfo.output_width * jddp->dinfo.output_components;
	    if_debug4('w', "[wdd]width=%u, components=%d, scan_line_size=%u, min_out_size=%u\n",
		      jddp->dinfo.output_width,
		      jddp->dinfo.output_components,
		      ss->scan_line_size, jddp->template.min_out_size);
	    if (ss->scan_line_size > (uint) jddp->template.min_out_size) {
		/* Create a spare buffer for oversize scanline */
		jddp->scanline_buffer =
		    gs_alloc_bytes_immovable(jddp->memory,
					     ss->scan_line_size,
					 "s_DCTD_process(scanline_buffer)");
		if (jddp->scanline_buffer == NULL)
		    return ERRC;
	    }
	    jddp->bytes_in_scanline = 0;
	    ss->phase = 3;
	    /* falls through */
	case 3:		/* reading data */
	  dumpbuffer:
	    if (jddp->bytes_in_scanline != 0) {
		uint avail = pw->limit - pw->ptr;
		uint tomove = min(jddp->bytes_in_scanline,
				  avail);

		if_debug2('w', "[wdd]moving %u/%u\n",
			  tomove, avail);
		memcpy(pw->ptr + 1, jddp->scanline_buffer +
		       (ss->scan_line_size - jddp->bytes_in_scanline),
		       tomove);
		pw->ptr += tomove;
		jddp->bytes_in_scanline -= tomove;
		if (jddp->bytes_in_scanline != 0)
		    return 1;	/* need more room */
	    }
	    while (jddp->dinfo.output_height > jddp->dinfo.output_scanline) {
		int read;
		byte *samples;

		if (jddp->scanline_buffer != NULL)
		    samples = jddp->scanline_buffer;
		else {
		    if ((uint) (pw->limit - pw->ptr) < ss->scan_line_size)
			return 1;	/* need more room */
		    samples = pw->ptr + 1;
		}
		read = gs_jpeg_read_scanlines(ss, &samples, 1);
		if (read < 0)
		    return ERRC;
		if_debug3('w', "[wdd]read returns %d, used=%u, faked_eoi=%d\n",
			  read,
			  (uint) (src->next_input_byte - 1 - pr->ptr),
			  (int)jddp->faked_eoi);
		pr->ptr =
		    (jddp->faked_eoi ? pr->limit : src->next_input_byte - 1);
		if (!read)
		    return 0;	/* need more data */
		if (jddp->scanline_buffer != NULL) {
		    jddp->bytes_in_scanline = ss->scan_line_size;
		    goto dumpbuffer;
		}
		pw->ptr += ss->scan_line_size;
	    }
	    ss->phase = 4;
	    /* falls through */
	case 4:		/* end of image; scan for EOI */
	    if ((code = gs_jpeg_finish_decompress(ss)) < 0)
		return ERRC;
	    pr->ptr =
		(jddp->faked_eoi ? pr->limit : src->next_input_byte - 1);
	    if (code == 0)
		return 0;
	    ss->phase = 5;
	    /* falls through */
	case 5:		/* we are DONE */
	    return EOFC;
    }
    /* Default case can't happen.... */
    return ERRC;
}

/* Release the stream */
private void
s_DCTD_release(stream_state * st)
{
    stream_DCT_state *const ss = (stream_DCT_state *) st;

    gs_jpeg_destroy(ss);
    if (ss->data.decompress->scanline_buffer != NULL)
	gs_free_object(ss->data.common->memory,
		       ss->data.decompress->scanline_buffer,
		       "s_DCTD_release(scanline_buffer)");
    gs_free_object(ss->data.common->memory, ss->data.decompress,
		   "s_DCTD_release");
    /* Switch the template pointer back in case we still need it. */
    st->template = &s_DCTD_template;
}

/* Stream template */
const stream_template s_DCTD_template =
{&st_DCT_state, s_DCTD_init, s_DCTD_process, 2000, 4000, s_DCTD_release,
 s_DCTD_set_defaults
};
