/* Copyright (C) 1997, 1998 Aladdin Enterprises.  All rights reserved.

   This file is part of Aladdin Ghostscript.

   Aladdin Ghostscript is distributed with NO WARRANTY OF ANY KIND.  No author
   or distributor accepts any responsibility for the consequences of using it,
   or for whether it serves any particular purpose or works at all, unless he
   or she says so in writing.  Refer to the Aladdin Ghostscript Free Public
   License (the "License") for full details.

   Every copy of Aladdin Ghostscript must include a copy of the License,
   normally in a plain ASCII text file named PUBLIC.  The License grants you
   the right to copy, modify and redistribute Aladdin Ghostscript, but only
   under certain conditions described in the License.  Among other things, the
   License requires that the copyright notice and this notice be preserved on
   all copies.
 */

/*$Id: gximage4.c $ */
/* ImageType 4 image implementation */
#include "memory_.h"
#include "gx.h"
#include "gserrors.h"
#include "gscspace.h"
#include "gsiparm3.h"
#include "gsiparm4.h"
#include "gxiparam.h"

/* Forward references */
private dev_proc_begin_typed_image(gx_begin_image4);
private image_enum_proc_plane_data(gx_image4_plane_data);
private image_enum_proc_end_image(gx_image4_end_image);

/* Define the image type for ImageType 4 images. */
private const gx_image_type_t image4_type = {
    gx_begin_image4, gx_data_image_source_size, 4
};
private const gx_image_enum_procs_t image4_enum_procs = {
    gx_image4_plane_data, gx_image4_end_image
};

/* Initialize an ImageType 4 image. */
void
gs_image4_t_init(gs_image4_t * pim, const gs_color_space * color_space)
{
    gs_pixel_image_t_init((gs_pixel_image_t *) pim, color_space);
    pim->type = &image4_type;
    pim->MaskColor_is_range = false;
}

/*
 * We implement ImageType 4 using ImageType 3 (or, if the image is known
 * to be completely opaque, ImageType 1).
 */
typedef struct gx_image4_enum_s {
    gx_image_enum_common;
    int num_components;
    int bpc;			/* BitsPerComponent */
    uint values[gs_image_max_components * 2];
    gs_memory_t *memory;
    gx_image_enum_common_t *info;	/* info for image3 or image1 */
    byte *mask;			/* one scan line of mask data, 0 if image1 */
    uint mask_size;
    int width;
    int y;
    int height;
} gx_image4_enum_t;

gs_private_st_ptrs2(st_image4_enum, gx_image4_enum_t, "gx_image4_enum_t",
		 image4_enum_enum_ptrs, image4_enum_reloc_ptrs, info, mask);

/* Begin an ImageType 4 image. */
private int
gx_begin_image4(gx_device * dev,
		const gs_imager_state * pis, const gs_matrix * pmat,
		const gs_image_common_t * pic, const gs_int_rect * prect,
	      const gx_drawing_color * pdcolor, const gx_clip_path * pcpath,
		gs_memory_t * mem, gx_image_enum_common_t ** pinfo)
{
    const gs_image4_t *pim = (const gs_image4_t *)pic;
    int num_components = gs_color_space_num_components(pim->ColorSpace);
    bool opaque = false;
    gx_image4_enum_t *penum;
    uint mask_size = (pim->Width + 7) >> 3;
    byte *mask = 0;
    int code;

    penum = gs_alloc_struct(mem, gx_image4_enum_t, &st_image4_enum,
			    "gx_begin_image4");
    if (penum == 0)
	return_error(gs_error_VMerror);
    gx_image_enum_common_init((gx_image_enum_common_t *) penum,
			      pic, &image4_enum_procs, dev,
			      pim->BitsPerComponent,
			      num_components, pim->format);
    penum->memory = mem;
    {
	uint max_value = (1 << pim->BitsPerComponent) - 1;
	int i;

	for (i = 0; i < num_components * 2; i += 2) {
	    uint c0, c1;

	    if (pim->MaskColor_is_range)
		c0 = pim->MaskColor[i], c1 = pim->MaskColor[i + 1];
	    else
		c0 = c1 = pim->MaskColor[i >> 1];

	    if (c1 > max_value)
		c1 = max_value;
	    if (c0 > c1) {
		opaque = true;
		break;
	    }
	    penum->values[i] = c0;
	    penum->values[i + 1] = c1;
	}
    }
    if (opaque) {
	/*
	 * This image doesn't need masking at all, since at least one of
	 * the transparency keys can never be matched.  Process it as an
	 * ImageType 1 image.
	 */
	const gx_image_type_t *type;
	gs_image1_t image1;

	gs_image_t_init(&image1, pim->ColorSpace);
	type = image1.type;
	*(gs_pixel_image_t *)&image1 =
	    *(const gs_pixel_image_t *)pim;
	image1.type = type;
	penum->mask = 0;	/* indicates opaque image */
	code = gx_device_begin_typed_image(dev, pis, pmat,
					   (gs_image_common_t *)&image1,
				 prect, pdcolor, pcpath, mem, &penum->info);
    } else if ((mask = gs_alloc_bytes(mem, mask_size,
				      "gx_begin_image4(mask)")) == 0
	) {
	code = gs_note_error(gs_error_VMerror);
    } else {
	gs_image3_t image3;

	penum->num_components = num_components;
	gs_image3_t_init(&image3, pim->ColorSpace, interleave_scan_lines);
	{
	    const gx_image_type_t *type = image3.type;

	    *(gs_pixel_image_t *)&image3 =
		*(const gs_pixel_image_t *)pim;
	    image3.type = type;
	}
	*(gs_data_image_t *)&image3.MaskDict =
	    *(const gs_data_image_t *)pim;
	image3.MaskDict.BitsPerComponent = 1;
	/*
	 * The interpretation of Decode is backwards from the sensible
	 * one, but it's an Adobe convention that is now too hard to
	 * change in the code.
	 */
	image3.MaskDict.Decode[0] = 1;
	image3.MaskDict.Decode[1] = 0;
	image3.MaskDict.Interpolate = false;
	penum->bpc = pim->BitsPerComponent;
	penum->mask = mask;
	penum->mask_size = mask_size;
	if (prect)
	    penum->width = prect->q.x - prect->p.x,
		penum->y = prect->p.y, penum->height = prect->q.y - prect->p.y;
	else
	    penum->width = pim->Width,
		penum->y = 0, penum->height = pim->Height;
	code = gx_device_begin_typed_image(dev, pis, pmat,
					 (const gs_image_common_t *)&image3,
				 prect, pdcolor, pcpath, mem, &penum->info);
    }
    if (code < 0) {
	gs_free_object(mem, mask, "gx_begin_image4(mask)");
	gs_free_object(mem, penum, "gx_begin_image4");
    } else
	*pinfo = (gx_image_enum_common_t *) penum;
    return code;
}

/* Process the next piece of an ImageType 4 image. */
/* We disregard the depth in the image planes: BitsPerComponent prevails. */
private int
gx_image4_plane_data(gx_device * dev,
 gx_image_enum_common_t * info, const gx_image_plane_t * planes, int height)
{
    gx_image4_enum_t *penum = (gx_image4_enum_t *) info;
    int num_planes = penum->num_planes;
    int bpc = penum->bpc;
    int spp = (num_planes > 1 ? 1 : penum->num_components);
    byte *mask = penum->mask;
    uint mask_size = (penum->width + 7) >> 3;
    gx_image_plane_t sources[gs_image_max_components];
    int h = min(height, penum->height - penum->y);

    if (penum->mask == 0)	/* opaque image */
	return gx_image_plane_data(penum->info, planes, height);
    if (mask_size > penum->mask_size) {
	mask = gs_resize_object(penum->memory, mask, mask_size,
				"gx_image4_data(resize mask)");
	if (mask == 0)
	    return_error(gs_error_VMerror);
	penum->mask = mask;
	penum->mask_size = mask_size;
    }
    sources[0].data = mask;
    sources[0].data_x = 0;
    sources[0].raster = mask_size;
    memcpy(sources + 1, planes, num_planes * sizeof(planes[0]));
    for (; h > 0; ++(penum->y), --h) {
	int pi;
	int code;

	memset(mask, 0, (penum->width + 7) >> 3);
	for (pi = 0; pi < num_planes; ++pi) {
	    byte *mptr = mask;
	    byte mbit = 0x80;
	    uint sx_bit = sources[pi + 1].data_x * bpc;
	    const byte *sptr = sources[pi + 1].data + (sx_bit >> 3);
	    uint sx_shift = sx_bit & 7;
	    int ix;

#define advance_sx()\
  BEGIN\
    if ( (sx_shift += bpc) >= 8 ) {\
      sptr += sx_shift >> 3;\
      sx_shift &= 7;\
    }\
  END

	    for (ix = 0; ix < penum->width; ++ix) {
		int ci;

		for (ci = 0; ci < spp; ++ci) {
		    /*
		     * The following odd-looking computation is, in fact,
		     * correct both for chunky (pi = 0) and planar (ci = 0)
		     * data formats.
		     */
		    int vi = (ci + pi) * 2;
		    uint sample;

		    if (bpc <= 8) {
			sample = (*sptr >> (8 - sx_shift - bpc)) &
			    ((1 << bpc) - 1);
		    } else {
			/* bpc == 12 */
			if (sx_shift /* == 4 */ )
			    sample = ((*sptr & 0xf) << 8) + sptr[1];
			else
			    sample = (*sptr << 8) + (sptr[1] >> 4);
		    }
		    if (sample < penum->values[vi] ||
			sample > penum->values[vi + 1]
			)
			*mptr |= mbit;
		    advance_sx();
		}
		if ((mbit >>= 1) == 0)
		    mbit = 0x80, ++mptr;
	    }
	}
	code = gx_image_plane_data(penum->info, sources, 1);
	if (code < 0)
	    return code;
	for (pi = 1; pi <= num_planes; ++pi)
	    sources[pi].data += sources[pi].raster;
    }
#undef advance_sx
    return penum->y >= penum->height;
}

/* Clean up after processing an ImageType 4 image. */
private int
gx_image4_end_image(gx_device * dev, gx_image_enum_common_t * info,
		    bool draw_last)
{
    gx_image4_enum_t *penum = (gx_image4_enum_t *) info;
    gs_memory_t *mem = penum->memory;

    /* Finish processing the ImageType 3 (or 1) image. */
    int code = gx_image_end(penum->info, draw_last);

    gs_free_object(mem, penum->mask, "gx_image4_end_image(mask)");
    gs_free_object(mem, penum, "gx_image4_end_image");
    return code;
}
