summaryrefslogtreecommitdiff
path: root/gs/src/gsalphac.c
diff options
context:
space:
mode:
authorHenry Stiles <henry.stiles@artifex.com>1998-08-08 06:11:33 +0000
committerHenry Stiles <henry.stiles@artifex.com>1998-08-08 06:11:33 +0000
commit3305477b99710b8ce6223a0bdd5014ced4de6997 (patch)
tree2cd123878deab83af88cbfcbff04624712c5b46c /gs/src/gsalphac.c
parentb8cb922d73b866149ca3da2288f1edcf959c45c9 (diff)
Initial revision
git-svn-id: http://svn.ghostscript.com/ghostpcl/trunk/ghostpcl@277 06663e23-700e-0410-b217-a244a6096597
Diffstat (limited to 'gs/src/gsalphac.c')
-rw-r--r--gs/src/gsalphac.c830
1 files changed, 830 insertions, 0 deletions
diff --git a/gs/src/gsalphac.c b/gs/src/gsalphac.c
new file mode 100644
index 000000000..00045ad46
--- /dev/null
+++ b/gs/src/gsalphac.c
@@ -0,0 +1,830 @@
+/* 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: gsalphac.c */
+/* Alpha-compositing implementation */
+#include "memory_.h"
+#include "gx.h"
+#include "gserrors.h"
+#include "gsalphac.h"
+#include "gsiparam.h" /* for gs_image_alpha_t */
+#include "gsutil.h" /* for gs_next_ids */
+#include "gxalpha.h"
+#include "gxcomp.h"
+#include "gxdevice.h"
+#include "gxgetbit.h"
+#include "gxlum.h"
+
+/* ---------------- Internal definitions ---------------- */
+
+/* Define the parameters for a compositing operation. */
+typedef struct gs_composite_params_s {
+ gs_composite_op_t cop;
+ float delta; /* only for dissolve */
+ uint source_alpha; /* only if !psource->alpha */
+ uint source_values[4]; /* only if !psource->data */
+} gs_composite_params_t;
+
+/* Define the source or destination for a compositing operation. */
+#define pixel_row_fields(elt_type)\
+ elt_type *data;\
+ int bits_per_value; /* 1, 2, 4, 8, 12, 16 */\
+ int initial_x;\
+ gs_image_alpha_t alpha
+typedef struct pixel_row_s {
+ pixel_row_fields(byte);
+} pixel_row_t;
+typedef struct const_pixel_row_s {
+ pixel_row_fields(const byte);
+} const_pixel_row_t;
+
+/*
+ * Composite two arrays of (premultiplied) pixel values. Legal values of
+ * values_per_pixel are 1-4, not including alpha. Note that if pdest->alpha
+ * is "none", the alpha value for all destination pixels will be taken as
+ * unity, and any operation that could generate alpha values other than
+ * unity will return an error. "Could generate" means that there are
+ * possible values of the source and destination alpha values for which the
+ * result has non-unity alpha: the error check does not scan the actual
+ * alpha data to test whether there are any actual values that would
+ * generate a non-unity alpha result.
+ */
+int composite_values(P5(const pixel_row_t * pdest,
+ const const_pixel_row_t * psource,
+ int values_per_pixel, uint num_pixels,
+ const gs_composite_params_t * pcp));
+
+/* ---------------- Alpha-compositing objects ---------------- */
+
+/*
+ * Define which operations can generate non-unity alpha values in 3 of the 4
+ * cases of source and destination not having unity alphas. (This is always
+ * possible in the fourth case, both S & D non-unity, except for CLEAR.) We
+ * do this with a bit mask indexed by the operation, counting from the LSB.
+ * The name indicates whether S and/or D has non-unity alphas.
+ */
+#define alpha_out_notS_notD\
+ (1<<composite_Dissolve)
+#define _alpha_out_either\
+ (alpha_out_notS_notD|(1<<composite_Satop)|(1<<composite_Datop)|\
+ (1<<composite_Xor)|(1<<composite_PlusD)|(1<<composite_PlusL))
+#define alpha_out_S_notD\
+ (_alpha_out_either|(1<<composite_Copy)|(1<<composite_Sover)|\
+ (1<<composite_Din)|(1<<composite_Dout))
+#define alpha_out_notS_D\
+ (_alpha_out_either|(1<<composite_Sin)|(1<<composite_Sout)|\
+ (1<<composite_Dover)|(1<<composite_Highlight))
+
+/* ------ Object definition and creation ------ */
+
+/* Define alpha-compositing objects. */
+private composite_create_default_compositor_proc(c_alpha_create_default_compositor);
+private composite_equal_proc(c_alpha_equal);
+private composite_write_proc(c_alpha_write);
+private composite_read_proc(c_alpha_read);
+private const gs_composite_type_t gs_composite_alpha_type =
+{
+ {
+ c_alpha_create_default_compositor,
+ c_alpha_equal,
+ c_alpha_write,
+ c_alpha_read
+ }
+};
+typedef struct gs_composite_alpha_s {
+ gs_composite_common;
+ gs_composite_alpha_params_t params;
+} gs_composite_alpha_t;
+
+gs_private_st_simple(st_composite_alpha, gs_composite_alpha_t,
+ "gs_composite_alpha_t");
+
+/* Create an alpha-compositing object. */
+int
+gs_create_composite_alpha(gs_composite_t ** ppcte,
+ const gs_composite_alpha_params_t * params, gs_memory_t * mem)
+{
+ gs_composite_alpha_t *pcte;
+
+ rc_alloc_struct_0(pcte, gs_composite_alpha_t, &st_composite_alpha,
+ mem, return_error(gs_error_VMerror),
+ "gs_create_composite_alpha");
+ pcte->type = &gs_composite_alpha_type;
+ pcte->id = gs_next_ids(1);
+ pcte->params = *params;
+ *ppcte = (gs_composite_t *) pcte;
+ return 0;
+}
+
+/* ------ Object implementation ------ */
+
+#define pacte ((const gs_composite_alpha_t *)pcte)
+
+private bool
+c_alpha_equal(const gs_composite_t * pcte, const gs_composite_t * pcte2)
+{
+ return (pcte2->type == pcte->type &&
+#define pacte2 ((const gs_composite_alpha_t *)pcte2)
+ pacte2->params.op == pacte->params.op &&
+ (pacte->params.op != composite_Dissolve ||
+ pacte2->params.delta == pacte->params.delta));
+#undef pacte2
+}
+
+private int
+c_alpha_write(const gs_composite_t * pcte, byte * data, uint * psize)
+{
+ uint size = *psize;
+ uint used;
+
+ if (pacte->params.op == composite_Dissolve) {
+ used = 1 + sizeof(pacte->params.delta);
+ if (size < used) {
+ *psize = used;
+ return_error(gs_error_rangecheck);
+ }
+ memcpy(data + 1, &pacte->params.delta, sizeof(pacte->params.delta));
+ } else {
+ used = 1;
+ if (size < used) {
+ *psize = used;
+ return_error(gs_error_rangecheck);
+ }
+ }
+ *data = (byte) pacte->params.op;
+ *psize = used;
+ return 0;
+}
+
+private int
+c_alpha_read(gs_composite_t ** ppcte, const byte * data, uint size,
+ gs_memory_t * mem)
+{
+ gs_composite_alpha_params_t params;
+
+ if (size < 1 || *data > composite_op_last)
+ return_error(gs_error_rangecheck);
+ params.op = *data;
+ if (params.op == composite_Dissolve) {
+ if (size != 1 + sizeof(params.delta))
+ return_error(gs_error_rangecheck);
+ memcpy(&params.delta, data + 1, sizeof(params.delta));
+ } else {
+ if (size != 1)
+ return_error(gs_error_rangecheck);
+ }
+ return gs_create_composite_alpha(ppcte, &params, mem);
+}
+
+/* ---------------- Alpha-compositing device ---------------- */
+
+/* Define the default alpha-compositing device. */
+typedef struct gx_device_composite_alpha_s {
+ gx_device_forward_common;
+ gs_composite_alpha_params_t params;
+} gx_device_composite_alpha;
+
+gs_private_st_suffix_add0_final(st_device_composite_alpha,
+ gx_device_composite_alpha, "gx_device_composite_alpha",
+ device_c_alpha_enum_ptrs, device_c_alpha_reloc_ptrs, gx_device_finalize,
+ st_device_forward);
+/* The device descriptor. */
+private dev_proc_close_device(dca_close);
+private dev_proc_fill_rectangle(dca_fill_rectangle);
+private dev_proc_map_rgb_color(dca_map_rgb_color);
+private dev_proc_map_color_rgb(dca_map_color_rgb);
+private dev_proc_copy_mono(dca_copy_mono);
+private dev_proc_copy_color(dca_copy_color);
+private dev_proc_map_rgb_alpha_color(dca_map_rgb_alpha_color);
+private dev_proc_map_color_rgb_alpha(dca_map_color_rgb_alpha);
+private dev_proc_copy_alpha(dca_copy_alpha);
+private const gx_device_composite_alpha gs_composite_alpha_device =
+{std_device_std_body_open(gx_device_composite_alpha, 0,
+ "alpha compositor", 0, 0, 1, 1),
+ {gx_default_open_device,
+ gx_forward_get_initial_matrix,
+ gx_default_sync_output,
+ gx_default_output_page,
+ dca_close,
+ dca_map_rgb_color,
+ dca_map_color_rgb,
+ dca_fill_rectangle,
+ gx_default_tile_rectangle,
+ dca_copy_mono,
+ dca_copy_color,
+ gx_default_draw_line,
+ gx_default_get_bits,
+ gx_forward_get_params,
+ gx_forward_put_params,
+ gx_default_cmyk_map_cmyk_color, /* only called for CMYK */
+ gx_forward_get_xfont_procs,
+ gx_forward_get_xfont_device,
+ dca_map_rgb_alpha_color,
+ gx_forward_get_page_device,
+ gx_forward_get_alpha_bits,
+ dca_copy_alpha,
+ gx_forward_get_band,
+ gx_default_copy_rop,
+ gx_default_fill_path,
+ gx_default_stroke_path,
+ gx_default_fill_mask,
+ gx_default_fill_trapezoid,
+ gx_default_fill_parallelogram,
+ gx_default_fill_triangle,
+ gx_default_draw_thin_line,
+ gx_default_begin_image,
+ gx_default_image_data,
+ gx_default_end_image,
+ gx_default_strip_tile_rectangle,
+ gx_default_strip_copy_rop,
+ gx_forward_get_clipping_box,
+ gx_default_begin_typed_image,
+ gx_forward_get_bits_rectangle,
+ dca_map_color_rgb_alpha,
+ gx_no_create_compositor
+ }
+};
+
+/* Create an alpha compositor. */
+int
+c_alpha_create_default_compositor(const gs_composite_t * pcte,
+ gx_device ** pcdev, gx_device * dev, const gs_imager_state * pis,
+ gs_memory_t * mem)
+{
+ gx_device_composite_alpha *cdev;
+
+ if (pacte->params.op == composite_Copy) {
+ /* Just use the original device. */
+ *pcdev = dev;
+ return 0;
+ }
+ cdev =
+ gs_alloc_struct_immovable(mem, gx_device_composite_alpha,
+ &st_device_composite_alpha,
+ "create default alpha compositor");
+ *pcdev = (gx_device *) cdev;
+ if (cdev == 0)
+ return_error(gs_error_VMerror);
+ *(gx_device *) cdev = *dev;
+ cdev->dname = gs_composite_alpha_device.dname;
+ cdev->memory = mem;
+ cdev->stype = &st_device_composite_alpha;
+ /*
+ * Set the color_info and depth to be compatible with the target,
+ * but using standard chunky color storage, including alpha.
+ ****** CURRENTLY ALWAYS USE 8-BIT COLOR ******
+ */
+ cdev->color_info.depth =
+ (dev->color_info.num_components == 4 ? 32 /* CMYK, no alpha */ :
+ (dev->color_info.num_components + 1) * 8);
+ cdev->color_info.max_gray = cdev->color_info.max_color = 255;
+ /* No halftoning will occur, but we fill these in anyway.... */
+ cdev->color_info.dither_grays = cdev->color_info.dither_colors = 256;
+ assign_dev_procs(cdev, &gs_composite_alpha_device);
+ /*
+ * We could speed things up a little by tailoring the procedures in
+ * the device to the specific num_components, but for simplicity,
+ * we'll defer considering that until there is a demonstrated need.
+ */
+ cdev->target = dev;
+ cdev->params = pacte->params;
+ return 0;
+}
+
+/* Close the device and free its storage. */
+private int
+dca_close(gx_device * dev)
+{ /*
+ * Finalization will call close again: avoid a recursion loop.
+ */
+ set_dev_proc(dev, close_device, gx_default_close_device);
+ gs_free_object(dev->memory, dev, "dca_close");
+ return 0;
+}
+
+/* ------ (RGB) color mapping ------ */
+
+private gx_color_index
+dca_map_rgb_color(gx_device * dev,
+ gx_color_value r, gx_color_value g, gx_color_value b)
+{
+ return dca_map_rgb_alpha_color(dev, r, g, b, gx_max_color_value);
+}
+private gx_color_index
+dca_map_rgb_alpha_color(gx_device * dev,
+ gx_color_value red, gx_color_value green, gx_color_value blue,
+ gx_color_value alpha)
+{ /*
+ * We work exclusively with premultiplied color values, so we
+ * have to premultiply the color components by alpha here.
+ */
+ byte a = gx_color_value_to_byte(alpha);
+
+#define premult_(c)\
+ (((c) * a + gx_max_color_value / 2) / gx_max_color_value)
+#ifdef PREMULTIPLY_TOWARDS_WHITE
+ byte bias = ~a;
+
+# define premult(c) (premult_(c) + bias)
+#else
+# define premult(c) premult_(c)
+#endif
+ gx_color_index color;
+
+ if (dev->color_info.num_components == 1) {
+ uint lum =
+ (red * lum_red_weight + green * lum_green_weight +
+ blue * lum_blue_weight + lum_all_weights / 2) /
+ lum_all_weights;
+
+ if (a == 0xff)
+ color = gx_color_value_to_byte(lum);
+ else /* Premultiplication is necessary. */
+ color = premult(lum);
+ } else {
+ if (a == 0xff)
+ color =
+ ((uint) gx_color_value_to_byte(red) << 16) +
+ ((uint) gx_color_value_to_byte(green) << 8) +
+ gx_color_value_to_byte(blue);
+ else /* Premultiplication is necessary. */
+ color =
+ (premult(red) << 16) + (premult(green) << 8) + premult(blue);
+ }
+#undef premult
+ return (color << 8) + a;
+}
+private int
+dca_map_color_rgb(gx_device * dev, gx_color_index color,
+ gx_color_value prgb[3])
+{
+ gx_color_value red = gx_color_value_from_byte((byte) (color >> 24));
+ byte a = (byte) color;
+
+#define postdiv_(c)\
+ (((c) * 0xff + a / 2) / a)
+#ifdef PREMULTIPLY_TOWARDS_WHITE
+ byte bias = ~a;
+
+# define postdiv(c) postdiv_(c - bias)
+#else
+# define postdiv(c) postdiv_(c)
+#endif
+
+ if (dev->color_info.num_components == 1) {
+ if (a != 0xff) {
+ /* Undo premultiplication. */
+ if (a == 0)
+ red = 0;
+ else
+ red = postdiv(red);
+ }
+ prgb[0] = prgb[1] = prgb[2] = red;
+ } else {
+ gx_color_value
+ green = gx_color_value_from_byte((byte) (color >> 16)),
+ blue = gx_color_value_from_byte((byte) (color >> 8));
+
+ if (a != 0xff) {
+ /* Undo premultiplication. */
+/****** WHAT TO DO ABOUT BIG LOSS OF PRECISION? ******/
+ if (a == 0)
+ red = green = blue = 0;
+ else {
+ red = postdiv(red);
+ green = postdiv(green);
+ blue = postdiv(blue);
+ }
+ }
+ prgb[0] = red, prgb[1] = green, prgb[2] = blue;
+ }
+#undef postdiv
+ return 0;
+}
+private int
+dca_map_color_rgb_alpha(gx_device * dev, gx_color_index color,
+ gx_color_value prgba[4])
+{
+ prgba[3] = gx_color_value_from_byte((byte) color);
+ return dca_map_color_rgb(dev, color, prgba);
+}
+
+/* ------ Imaging ------ */
+
+private int
+dca_fill_rectangle(gx_device * dev, int x, int y, int w, int h,
+ gx_color_index color)
+{ /* This is where all the real work gets done! */
+ gx_device_composite_alpha *adev = (gx_device_composite_alpha *) dev;
+ gx_device *target = adev->target;
+ byte *std_row;
+ byte *native_row;
+ gs_int_rect rect;
+ gs_get_bits_params_t std_params, native_params;
+ int code = 0;
+ int yi;
+ gs_composite_params_t cp;
+ const_pixel_row_t source;
+ pixel_row_t dest;
+
+ fit_fill(dev, x, y, w, h);
+ std_row = gs_alloc_bytes(dev->memory,
+ (dev->color_info.depth * w + 7) >> 3,
+ "dca_fill_rectangle(std)");
+ native_row = gs_alloc_bytes(dev->memory,
+ (target->color_info.depth * w + 7) >> 3,
+ "dca_fill_rectangle(native)");
+ if (std_row == 0 || native_row == 0) {
+ code = gs_note_error(gs_error_VMerror);
+ goto out;
+ }
+ rect.p.x = x, rect.q.x = x + w;
+ std_params.options =
+ gb_colors_for_device(dev) |
+ (GB_ALPHA_LAST | GB_DEPTH_8 | GB_PACKING_CHUNKY |
+ GB_RETURN_COPY | GB_RETURN_POINTER | GB_ALIGN_ANY |
+ GB_OFFSET_0 | GB_OFFSET_ANY | GB_RASTER_STANDARD |
+ GB_RASTER_ANY);
+ cp.cop = adev->params.op;
+ if (cp.cop == composite_Dissolve)
+ cp.delta = adev->params.delta;
+ {
+ gx_color_value rgba[4];
+
+/****** DOESN'T HANDLE CMYK ******/
+ (*dev_proc(dev, map_color_rgb_alpha)) (dev, color, rgba);
+ cp.source_values[0] = gx_color_value_to_byte(rgba[0]);
+ cp.source_values[1] = gx_color_value_to_byte(rgba[1]);
+ cp.source_values[2] = gx_color_value_to_byte(rgba[2]);
+ cp.source_alpha = gx_color_value_to_byte(rgba[3]);
+ }
+ source.data = 0;
+ source.bits_per_value = 8;
+ source.alpha = gs_image_alpha_none;
+ for (yi = y; yi < y + h; ++yi) {
+ /* Read a row in standard representation. */
+ rect.p.y = yi, rect.q.y = yi + 1;
+ std_params.data[0] = std_row;
+ code = (*dev_proc(target, get_bits_rectangle))
+ (target, &rect, &std_params, NULL);
+ if (code < 0)
+ break;
+ /* Do the work. */
+ dest.data = std_params.data[0];
+ dest.bits_per_value = 8;
+ dest.initial_x =
+ (std_params.options & GB_OFFSET_ANY ? std_params.x_offset : 0);
+ dest.alpha =
+ (std_params.options & GB_ALPHA_FIRST ? gs_image_alpha_first :
+ std_params.options & GB_ALPHA_LAST ? gs_image_alpha_last :
+ gs_image_alpha_none);
+ code = composite_values(&dest, &source,
+ dev->color_info.num_components, w, &cp);
+ if (code < 0)
+ break;
+ if (std_params.data[0] == std_row) {
+ /* Convert the row back to native representation. */
+ /* (Otherwise, we had a direct pointer to device data.) */
+ native_params.options =
+ (GB_COLORS_NATIVE | GB_PACKING_CHUNKY | GB_RETURN_COPY |
+ GB_OFFSET_0 | GB_RASTER_ALL | GB_ALIGN_STANDARD);
+ native_params.data[0] = native_row;
+ code = gx_get_bits_copy(target, 0, w, 1, &native_params,
+ std_params.options, std_row,
+ 0 /* raster is irrelevant */ );
+ if (code < 0)
+ break;
+ code = (*dev_proc(target, copy_color))
+ (target, native_row, 0, 0 /* raster is irrelevant */ ,
+ gx_no_bitmap_id, x, yi, w, 1);
+ if (code < 0)
+ break;
+ }
+ }
+ out:gs_free_object(dev->memory, native_row, "dca_fill_rectangle(native)");
+ gs_free_object(dev->memory, std_row, "dca_fill_rectangle(std)");
+ return code;
+}
+
+private int
+dca_copy_mono(gx_device * dev, const byte * data,
+ int dx, int raster, gx_bitmap_id id, int x, int y, int w, int h,
+ gx_color_index zero, gx_color_index one)
+{
+/****** TEMPORARY ******/
+ return gx_default_copy_mono(dev, data, dx, raster, id, x, y, w, h,
+ zero, one);
+}
+
+private int
+dca_copy_color(gx_device * dev, const byte * data,
+ int dx, int raster, gx_bitmap_id id,
+ int x, int y, int w, int h)
+{
+/****** TEMPORARY ******/
+ return gx_default_copy_color(dev, data, dx, raster, id, x, y, w, h);
+}
+
+private int
+dca_copy_alpha(gx_device * dev, const byte * data, int data_x,
+ int raster, gx_bitmap_id id, int x, int y, int width, int height,
+ gx_color_index color, int depth)
+{
+/****** TEMPORARY ******/
+ return gx_default_copy_alpha(dev, data, data_x, raster, id, x, y,
+ width, height, color, depth);
+}
+
+/*
+ * Composite two arrays of (premultiplied) pixel values.
+ * See gsdpnext.h for the specification.
+ *
+ * The current implementation is simple but inefficient. We'll speed it up
+ * later if necessary.
+ */
+int
+composite_values(const pixel_row_t * pdest, const const_pixel_row_t * psource,
+ int values_per_pixel, uint num_pixels, const gs_composite_params_t * pcp)
+{
+ int dest_bpv = pdest->bits_per_value;
+ int source_bpv = psource->bits_per_value;
+
+ /*
+ * source_alpha_j gives the source component index for the alpha value,
+ * if the source has alpha.
+ */
+ int source_alpha_j =
+ (psource->alpha == gs_image_alpha_last ? values_per_pixel :
+ psource->alpha == gs_image_alpha_first ? 0 : -1);
+
+ /* dest_alpha_j does the same for the destination. */
+ int dest_alpha_j =
+ (pdest->alpha == gs_image_alpha_last ? values_per_pixel :
+ pdest->alpha == gs_image_alpha_first ? 0 : -1);
+
+ /* dest_vpp is the number of stored destination values. */
+ int dest_vpp = values_per_pixel + (dest_alpha_j >= 0);
+
+ /* source_vpp is the number of stored source values. */
+ int source_vpp = values_per_pixel + (source_alpha_j >= 0);
+
+ bool constant_colors = psource->data == 0;
+ uint highlight_value = (1 << dest_bpv) - 1;
+
+ sample_load_declare(sptr, sbit);
+ sample_store_declare(dptr, dbit, dbyte);
+
+ {
+ uint xbit = pdest->initial_x * dest_bpv * dest_vpp;
+
+ sample_store_setup(dbit, xbit & 7, dest_bpv);
+ dptr = pdest->data + (xbit >> 3);
+ }
+ {
+ uint xbit = psource->initial_x * source_bpv * source_vpp;
+
+ sbit = xbit & 7;
+ sptr = psource->data + (xbit >> 3);
+ }
+ {
+ uint source_max = (1 << source_bpv) - 1;
+ uint dest_max = (1 << dest_bpv) - 1;
+
+ /*
+ * We could save a little work by only setting up source_delta
+ * and dest_delta if the operation is Dissolve.
+ */
+ float source_delta = pcp->delta * dest_max / source_max;
+ float dest_delta = 1.0 - pcp->delta;
+ uint source_alpha = pcp->source_alpha;
+ uint dest_alpha = dest_max;
+
+#ifdef PREMULTIPLY_TOWARDS_WHITE
+ uint source_bias = source_max - source_alpha;
+ uint dest_bias = 0;
+ uint result_bias = 0;
+
+#endif
+ uint x;
+
+ if (!pdest->alpha) {
+ uint mask =
+ (psource->alpha || source_alpha != source_max ?
+ alpha_out_S_notD : alpha_out_notS_notD);
+
+ if ((mask >> pcp->cop) & 1) {
+ /*
+ * The operation could produce non-unity alpha values, but
+ * the destination can't store them. Return an error.
+ */
+ return_error(gs_error_rangecheck);
+ }
+ }
+ /* Preload the output byte buffer if necessary. */
+ sample_store_preload(dbyte, dptr, dbit, dest_bpv);
+
+ for (x = 0; x < num_pixels; ++x) {
+ int j;
+ uint result_alpha = dest_alpha;
+
+/* get_value does not increment the source pointer. */
+#define get_value(v, ptr, bit, bpv, vmax)\
+ sample_load16(v, ptr, bit, bpv)
+
+/* put_value increments the destination pointer. */
+#define put_value(v, ptr, bit, bpv, bbyte)\
+ sample_store_next16(v, ptr, bit, bpv, bbyte)
+
+#define advance(ptr, bit, bpv)\
+ sample_next(ptr, bit, bpv)
+
+ /* Get destination alpha value. */
+ if (dest_alpha_j >= 0) {
+ int dabit = dbit + dest_bpv * dest_alpha_j;
+ const byte *daptr = dptr + (dabit >> 3);
+
+ get_value(dest_alpha, daptr, dabit & 7, dest_bpv, dest_max);
+#ifdef PREMULTIPLY_TOWARDS_WHITE
+ dest_bias = dest_max - dest_alpha;
+#endif
+ }
+ /* Get source alpha value. */
+ if (source_alpha_j >= 0) {
+ int sabit = sbit;
+ const byte *saptr = sptr;
+
+ if (source_alpha_j == 0)
+ advance(sptr, sbit, source_bpv);
+ else
+ advance(saptr, sabit, source_bpv * source_alpha_j);
+ get_value(source_alpha, saptr, sabit, source_bpv, source_max);
+#ifdef PREMULTIPLY_TOWARDS_WHITE
+ source_bias = source_max - source_alpha;
+#endif
+ }
+/*
+ * We are always multiplying a dest value by a source value to compute a
+ * dest value, so the denominator is always source_max. (Dissolve is the
+ * one exception.)
+ */
+#define fr(v, a) ((v) * (a) / source_max)
+#define nfr(v, a, maxv) ((v) * (maxv - (a)) / source_max)
+
+ /*
+ * Iterate over the components of a single pixel.
+ * j = 0 for alpha, 1 .. values_per_pixel for color
+ * components, regardless of the actual storage order;
+ * we arrange things so that sptr/sbit and dptr/dbit
+ * always point to the right place.
+ */
+ for (j = 0; j <= values_per_pixel; ++j) {
+ uint dest_v, source_v, result;
+
+#define set_clamped(r, v)\
+ BEGIN if ( (r = (v)) > dest_max ) r = dest_max; END
+
+ if (j == 0) {
+ source_v = source_alpha;
+ dest_v = dest_alpha;
+ } else {
+ if (constant_colors)
+ source_v = pcp->source_values[j - 1];
+ else {
+ get_value(source_v, sptr, sbit, source_bpv, source_max);
+ advance(sptr, sbit, source_bpv);
+ }
+ get_value(dest_v, dptr, dbit, dest_bpv, dest_max);
+#ifdef PREMULTIPLY_TOWARDS_WHITE
+ source_v -= source_bias;
+ dest_v -= dest_bias;
+#endif
+ }
+
+ switch (pcp->cop) {
+ case composite_Clear:
+ /*
+ * The NeXT documentation doesn't say this, but the CLEAR
+ * operation sets not only alpha but also all the color
+ * values to 0.
+ */
+ result = 0;
+ break;
+ case composite_Copy:
+ result = source_v;
+ break;
+ case composite_PlusD:
+ /*
+ * This is the only case where we have to worry about
+ * clamping a possibly negative result.
+ */
+ result = source_v + dest_v;
+ result = (result < dest_max ? 0 : result - dest_max);
+ break;
+ case composite_PlusL:
+ set_clamped(result, source_v + dest_v);
+ break;
+ case composite_Sover:
+ set_clamped(result, source_v + nfr(dest_v, source_alpha, source_max));
+ break;
+ case composite_Dover:
+ set_clamped(result, nfr(source_v, dest_alpha, dest_max) + dest_v);
+ break;
+ case composite_Sin:
+ result = fr(source_v, dest_alpha);
+ break;
+ case composite_Din:
+ result = fr(dest_v, source_alpha);
+ break;
+ case composite_Sout:
+ result = nfr(source_v, dest_alpha, dest_max);
+ break;
+ case composite_Dout:
+ result = nfr(dest_v, source_alpha, source_max);
+ break;
+ case composite_Satop:
+ set_clamped(result, fr(source_v, dest_alpha) +
+ nfr(dest_v, source_alpha, source_max));
+ break;
+ case composite_Datop:
+ set_clamped(result, nfr(source_v, dest_alpha, dest_max) +
+ fr(dest_v, source_alpha));
+ break;
+ case composite_Xor:
+ set_clamped(result, nfr(source_v, dest_alpha, dest_max) +
+ nfr(dest_v, source_alpha, source_max));
+ break;
+ case composite_Highlight:
+ /*
+ * Bizarre but true: this operation converts white and
+ * light gray into each other, and leaves all other values
+ * unchanged. We only implement it properly for gray-scale
+ * devices.
+ */
+ if (j != 0 && !((source_v ^ highlight_value) & ~1))
+ result = source_v ^ 1;
+ else
+ result = source_v;
+ break;
+ case composite_Dissolve:
+ /*
+ * In this case, and only this case, we need to worry about
+ * source and dest having different bpv values. For the
+ * moment, we wimp out and do everything in floating point.
+ */
+ result = (uint) (source_v * source_delta + dest_v * dest_delta);
+ break;
+ default:
+ return_error(gs_error_rangecheck);
+ }
+ /*
+ * Store the result. We don't have to worry about
+ * destinations that don't store alpha, because we don't
+ * even compute an alpha value in that case.
+ */
+#ifdef PREMULTIPLY_TOWARDS_WHITE
+ if (j == 0) {
+ result_alpha = result;
+ result_bias = dest_max - result_alpha;
+ if (dest_alpha_j != 0)
+ continue;
+ } else {
+ result += result_bias;
+ }
+#else
+ if (j == 0 && dest_alpha_j != 0) {
+ result_alpha = result;
+ continue;
+ }
+#endif
+ put_value(result, dptr, dbit, dest_bpv, dbyte);
+ }
+ /* Skip a trailing source alpha value. */
+ if (source_alpha_j > 0)
+ advance(sptr, sbit, source_bpv);
+ /* Store a trailing destination alpha value. */
+ if (dest_alpha_j > 0)
+ put_value(result_alpha, dptr, dbit, dest_bpv, dbyte);
+#undef get_value
+#undef put_value
+#undef advance
+ }
+ /* Store any partial output byte. */
+ sample_store_flush(dptr, dbit, dest_bpv, dbyte);
+ }
+ return 0;
+}