/* Copyright (C) 1996, 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: gxiscale.c,v 1.2.6.2.2.1 2003/01/17 00:49:04 giles Exp $ */
/* Interpolated image procedures */
#include "gx.h"
#include "math_.h"
#include "memory_.h"
#include "gpcheck.h"
#include "gserrors.h"
#include "gxfixed.h"
#include "gxfrac.h"
#include "gxarith.h"
#include "gxmatrix.h"
#include "gsccolor.h"
#include "gspaint.h"
#include "gxdevice.h"
#include "gxcmap.h"
#include "gxdcolor.h"
#include "gxistate.h"
#include "gxdevmem.h"
#include "gxcpath.h"
#include "gximage.h"
#include "stream.h"		/* for s_alloc_state */
#include "siinterp.h"		/* for spatial interpolation */
#include "siscale.h"		/* for Mitchell filtering */

/*
 * Define whether we are using Mitchell filtering or spatial
 * interpolation to implement Interpolate.  (The latter doesn't work yet.)
 */
#define USE_MITCHELL_FILTER

/* ------ Strategy procedure ------ */

/* Check the prototype. */
iclass_proc(gs_image_class_0_interpolate);

/* If we're interpolating, use special logic. */
private irender_proc(image_render_interpolate);
irender_proc_t
gs_image_class_0_interpolate(gx_image_enum * penum)
{
    const gs_imager_state *pis = penum->pis;
    gs_memory_t *mem = penum->memory;
    stream_image_scale_params_t iss;
    stream_image_scale_state *pss;
    const stream_template *template;
    byte *line;
    const gs_color_space *pcs = penum->pcs;
    const gs_color_space *pccs;
    gs_point dst_xy;
    uint in_size;

    if (!penum->interpolate) 
	return 0;
    if (penum->use_mask_color || penum->posture != image_portrait ||
    	penum->masked || penum->alpha) {
	/* We can't handle these cases yet.  Punt. */
	penum->interpolate = false;
	return 0;
    }
    /*
     * We interpolate using a digital filter, rather than Adobe's
     * spatial interpolation algorithm: this produces very bad-looking
     * results if the input resolution is close to the output resolution,
     * especially if the input has low color resolution, so we resort to
     * some hack tests on the input color resolution and scale to suppress
     * interpolation if we think the result would look especially bad.
     * If we used Adobe's spatial interpolation approach, we wouldn't need
     * to do this, but the spatial interpolation filter doesn't work yet.
     */
#ifdef USE_MITCHELL_FILTERX
    if (penum->bps < 4 || penum->bps * penum->spp < 8 ||
	(fabs(penum->matrix.xx) <= 5 && fabs(penum->matrix.yy <= 5))
	) {
	penum->interpolate = false;
	return 0;
    }
#endif
    /* Non-ANSI compilers require the following casts: */
    gs_distance_transform((float)penum->rect.w, (float)penum->rect.h,
			  &penum->matrix, &dst_xy);
    iss.BitsPerComponentOut = sizeof(frac) * 8;
    iss.MaxValueOut = frac_1;
    iss.WidthOut = (int)ceil(fabs(dst_xy.x));
    iss.HeightOut = (int)ceil(fabs(dst_xy.y));
    iss.WidthIn = penum->rect.w;
    iss.HeightIn = penum->rect.h;
    pccs = cs_concrete_space(pcs, pis);
    iss.Colors = cs_num_components(pccs);
    if (penum->bps <= 8 && penum->device_color) {
	iss.BitsPerComponentIn = 8;
	iss.MaxValueIn = 0xff;
	in_size =
	    (penum->matrix.xx < 0 ?
	     /* We need a buffer for reversing each scan line. */
	     iss.WidthIn * iss.Colors : 0);
    } else {
	iss.BitsPerComponentIn = sizeof(frac) * 8;
	iss.MaxValueIn = frac_1;
	in_size = round_up(iss.WidthIn * iss.Colors * sizeof(frac),
			   align_bitmap_mod);
    }
    /* Allocate a buffer for one source/destination line. */
    {
	uint out_size =
	    iss.WidthOut * max(iss.Colors * (iss.BitsPerComponentOut / 8),
			       sizeof(gx_color_index));

	line = gs_alloc_bytes(mem, in_size + out_size,
			      "image scale src+dst line");
    }
#ifdef USE_MITCHELL_FILTER
    template = &s_IScale_template;
#else
    template = &s_IIEncode_template;
#endif
    pss = (stream_image_scale_state *)
	s_alloc_state(mem, template->stype, "image scale state");
    if (line == 0 || pss == 0 ||
	(pss->params = iss, pss->template = template,
	 (*pss->template->init) ((stream_state *) pss) < 0)
	) {
	gs_free_object(mem, pss, "image scale state");
	gs_free_object(mem, line, "image scale src+dst line");
	/* Try again without interpolation. */
	penum->interpolate = false;
	return 0;
    }
    penum->line = line;
    penum->scaler = pss;
    penum->line_xy = 0;
    {
	gx_dda_fixed x0;

	x0 = penum->dda.pixel0.x;
	if (penum->matrix.xx < 0)
	    dda_advance(x0, penum->rect.w);
	penum->xyi.x = fixed2int_pixround(dda_current(x0));
    }
    penum->xyi.y = fixed2int_pixround(dda_current(penum->dda.pixel0.y));
    if_debug0('b', "[b]render=interpolate\n");
    return image_render_interpolate;
}

/* ------ Rendering for interpolated images ------ */

private int
image_render_interpolate(gx_image_enum * penum, const byte * buffer,
			 int data_x, uint iw, int h, gx_device * dev)
{
    stream_image_scale_state *pss = penum->scaler;
    const gs_imager_state *pis = penum->pis;
    const gs_color_space *pcs = penum->pcs;
    gs_logical_operation_t lop = penum->log_op;
    int c = pss->params.Colors;
    stream_cursor_read r;
    stream_cursor_write w;
    byte *out = penum->line;

    if (h != 0) {
	/* Convert the unpacked data to concrete values in */
	/* the source buffer. */
	int sizeofPixelIn = pss->params.BitsPerComponentIn / 8;
	uint row_size = pss->params.WidthIn * c * sizeofPixelIn;
	const byte *bdata = buffer + data_x * c * sizeofPixelIn;

	if (sizeofPixelIn == 1) {
	    /* Easy case: 8-bit device color values. */
	    if (penum->matrix.xx >= 0) {
		/* Use the input data directly. */
		r.ptr = bdata - 1;
	    } else {
		/* Mirror the data in X. */
		const byte *p = bdata + row_size - c;
		byte *q = out;
		int i;

		for (i = 0; i < pss->params.WidthIn; p -= c, q += c, ++i)
		    memcpy(q, p, c);
		r.ptr = out - 1;
		out = q;
	    }
	} else {
	    /* Messy case: concretize each sample. */
	    int bps = penum->bps;
	    int dc = penum->spp;
	    const byte *pdata = bdata;
	    frac *psrc = (frac *) penum->line;
	    gs_client_color cc;
	    int i;

	    r.ptr = (byte *) psrc - 1;
	    if_debug0('B', "[B]Concrete row:\n[B]");
	    for (i = 0; i < pss->params.WidthIn; i++, psrc += c) {
		int j;

		if (bps <= 8)
		    for (j = 0; j < dc; ++pdata, ++j) {
			decode_sample(*pdata, cc, j);
		} else		/* bps == 12 */
		    for (j = 0; j < dc; pdata += sizeof(frac), ++j) {
			decode_frac(*(const frac *)pdata, cc, j);
		    }
		(*pcs->type->concretize_color) (&cc, pcs, psrc, pis);
#ifdef DEBUG
		if (gs_debug_c('B')) {
		    int ci;

		    for (ci = 0; ci < c; ++ci)
			dprintf2("%c%04x", (ci == 0 ? ' ' : ','), psrc[ci]);
		}
#endif
	    }
	    out += round_up(pss->params.WidthIn * c * sizeof(frac),
			    align_bitmap_mod);
	    if_debug0('B', "\n");
	}
	r.limit = r.ptr + row_size;
    } else			/* h == 0 */
	r.ptr = 0, r.limit = 0;

    /*
     * Process input and/or collect output.  By construction, the pixels are
     * 1-for-1 with the device, but the Y coordinate might be inverted.
     */

    {
	int xo = penum->xyi.x;
	int yo = penum->xyi.y;
	int width = pss->params.WidthOut;
	int sizeofPixelOut = pss->params.BitsPerComponentOut / 8;
	int dy;
	const gs_color_space *pconcs = cs_concrete_space(pcs, pis);
	int bpp = dev->color_info.depth;
	uint raster = bitmap_raster(width * bpp);

	if (penum->matrix.yy > 0)
	    dy = 1;
	else
	    dy = -1, yo--;
	for (;;) {
	    int ry = yo + penum->line_xy * dy;
	    int x;
	    const frac *psrc;
	    gx_device_color devc;
	    int status, code;

	    DECLARE_LINE_ACCUM_COPY(out, bpp, xo);

	    w.limit = out + width *
		max(c * sizeofPixelOut, sizeof(gx_color_index)) - 1;
	    w.ptr = w.limit - width * c * sizeofPixelOut;
	    psrc = (const frac *)(w.ptr + 1);
	    status = (*pss->template->process)
		((stream_state *) pss, &r, &w, h == 0);
	    if (status < 0 && status != EOFC)
		return_error(gs_error_ioerror);
	    if (w.ptr == w.limit) {
		int xe = xo + width;

		if_debug1('B', "[B]Interpolated row %d:\n[B]",
			  penum->line_xy);
		for (x = xo; x < xe;) {
#ifdef DEBUG
		    if (gs_debug_c('B')) {
			int ci;

			for (ci = 0; ci < c; ++ci)
			    dprintf2("%c%04x", (ci == 0 ? ' ' : ','),
				     psrc[ci]);
		    }
#endif
		    code = (*pconcs->type->remap_concrete_color)
			(psrc, &devc, pis, dev, gs_color_select_source);
		    if (code < 0)
			return code;
		    if (color_is_pure(&devc)) {
			/* Just pack colors into a scan line. */
			gx_color_index color = devc.colors.pure;

			/* Skip RGB runs quickly. */
			if (c == 3) {
			    do {
				LINE_ACCUM(color, bpp);
				x++, psrc += 3;
			    } while (x < xe && psrc[-3] == psrc[0] &&
				     psrc[-2] == psrc[1] &&
				     psrc[-1] == psrc[2]);
			} else {
			    LINE_ACCUM(color, bpp);
			    x++, psrc += c;
			}
		    } else {
			int rcode;

			LINE_ACCUM_COPY(dev, out, bpp, xo, x, raster, ry);
			rcode = gx_fill_rectangle_device_rop(x, ry,
						     1, 1, &devc, dev, lop);
			if (rcode < 0)
			    return rcode;
			LINE_ACCUM_SKIP(bpp);
			l_xprev = x + 1;
			x++, psrc += c;
		    }
		}
		LINE_ACCUM_COPY(dev, out, bpp, xo, x, raster, ry);
		penum->line_xy++;
		if_debug0('B', "\n");
	    }
	    if ((status == 0 && r.ptr == r.limit) || status == EOFC)
		break;
	}
    }

    return (h == 0 ? 0 : 1);
}
