diff options
Diffstat (limited to 'base/gsimage.c')
-rw-r--r-- | base/gsimage.c | 690 |
1 files changed, 690 insertions, 0 deletions
diff --git a/base/gsimage.c b/base/gsimage.c new file mode 100644 index 00000000..be7d2f2c --- /dev/null +++ b/base/gsimage.c @@ -0,0 +1,690 @@ +/* Copyright (C) 2001-2019 Artifex Software, Inc. + All Rights Reserved. + + This software is provided AS-IS with no warranty, either express or + implied. + + This software is distributed under license and may not be copied, + modified or distributed except as expressly authorized under the terms + of the license contained in the file LICENSE in this distribution. + + Refer to licensing information at http://www.artifex.com or contact + Artifex Software, Inc., 1305 Grant Avenue - Suite 200, Novato, + CA 94945, U.S.A., +1(415)492-9861, for further information. +*/ + + +/* Image setup procedures for Ghostscript library */ +#include "memory_.h" +#include "math_.h" +#include "gx.h" +#include "gserrors.h" +#include "gsstruct.h" +#include "gscspace.h" +#include "gsmatrix.h" /* for gsiparam.h */ +#include "gsimage.h" +#include "gxarith.h" /* for igcd */ +#include "gxdevice.h" +#include "gxiparam.h" +#include "gxpath.h" /* for gx_effective_clip_path */ +#include "gximask.h" +#include "gzstate.h" +#include "gsutil.h" +#include "gxdevsop.h" +#include "gximage.h" + +/* + The main internal invariant for the gs_image machinery is + straightforward. The state consists primarily of N plane buffers + (planes[]). +*/ +typedef struct image_enum_plane_s { +/* + The state of each plane consists of: + + - A row buffer, aligned and (logically) large enough to hold one scan line + for that plane. (It may have to be reallocated if the plane width or + depth changes.) A row buffer is "full" if it holds exactly a full scan + line. +*/ + gs_string row; +/* + - A position within the row buffer, indicating how many initial bytes are + occupied. +*/ + uint pos; +/* + - A (retained) source string, which may be empty (size = 0). +*/ + gs_const_string source; +} image_enum_plane_t; +/* + The possible states for each plane do not depend on the state of any other + plane. Either: + + - pos = 0, source.size = 0. + + - If the underlying image processor says the plane is currently wanted, + either: + + - pos = 0, source.size >= one full row of data for this plane. This + case allows us to avoid copying the data from the source string to the + row buffer if the client is providing data in blocks of at least one + scan line. + + - pos = full, source.size may have any value. + + - pos > 0, pos < full, source.size = 0; + + - If the underlying image processor says the plane is not currently + wanted: + + - pos = 0, source.size may have any value. + + This invariant holds at the beginning and end of each call on + gs_image_next_planes. Note that for each plane, the "plane wanted" status + and size of a full row may change after each call of plane_data. As + documented in gxiparam.h, we assume that a call of plane_data can only + change a plane's status from "wanted" to "not wanted", or change the width + or depth of a wanted plane, if data for that plane was actually supplied + (and used). +*/ + +/* Define the enumeration state for this interface layer. */ +/*typedef struct gs_image_enum_s gs_image_enum; *//* in gsimage.h */ +struct gs_image_enum_s { + /* The following are set at initialization time. */ + gs_memory_t *memory; + gx_device *dev; /* if 0, just skip over the data */ + gx_image_enum_common_t *info; /* driver bookkeeping structure */ + int num_planes; + int height; + bool wanted_varies; + /* The following are updated dynamically. */ + int plane_index; /* index of next plane of data, */ + /* only needed for gs_image_next */ + int y; + bool error; + byte wanted[GS_IMAGE_MAX_COMPONENTS]; /* cache gx_image_planes_wanted */ + byte client_wanted[GS_IMAGE_MAX_COMPONENTS]; /* see gsimage.h */ + image_enum_plane_t planes[GS_IMAGE_MAX_COMPONENTS]; /* see above */ + /* + * To reduce setup for transferring complete rows, we maintain a + * partially initialized parameter array for gx_image_plane_data_rows. + * The data member is always set just before calling + * gx_image_plane_data_rows; the data_x and raster members are reset + * when needed. + */ + gx_image_plane_t image_planes[GS_IMAGE_MAX_COMPONENTS]; +}; + +gs_private_st_composite(st_gs_image_enum, gs_image_enum, "gs_image_enum", + gs_image_enum_enum_ptrs, gs_image_enum_reloc_ptrs); +#define gs_image_enum_num_ptrs 2 + +/* GC procedures */ +static +ENUM_PTRS_WITH(gs_image_enum_enum_ptrs, gs_image_enum *eptr) +{ + /* Enumerate the data planes. */ + index -= gs_image_enum_num_ptrs; + if (index < eptr->num_planes) + ENUM_RETURN_STRING_PTR(gs_image_enum, planes[index].source); + index -= eptr->num_planes; + if (index < eptr->num_planes) + ENUM_RETURN_STRING_PTR(gs_image_enum, planes[index].row); + return 0; +} +ENUM_PTR(0, gs_image_enum, dev); +ENUM_PTR(1, gs_image_enum, info); +ENUM_PTRS_END +static RELOC_PTRS_WITH(gs_image_enum_reloc_ptrs, gs_image_enum *eptr) +{ + int i; + + RELOC_PTR(gs_image_enum, dev); + RELOC_PTR(gs_image_enum, info); + for (i = 0; i < eptr->num_planes; i++) + RELOC_CONST_STRING_PTR(gs_image_enum, planes[i].source); + for (i = 0; i < eptr->num_planes; i++) + RELOC_STRING_PTR(gs_image_enum, planes[i].row); +} +RELOC_PTRS_END + +static int +is_image_visible(const gs_image_common_t * pic, gs_gstate * pgs, gx_clip_path *pcpath) +{ + /* HACK : We need the source image size here, + but gs_image_common_t doesn't pass it. + We would like to move Width, Height to gs_image_common, + but gs_image2_t appears to have those fields of double type. + */ + if (pic->type->begin_typed_image == gx_begin_image1) { + gs_image1_t *pim = (gs_image1_t *) pic; + gs_rect image_rect = {{0, 0}, {0, 0}}; + gs_rect device_rect; + gs_int_rect device_int_rect; + gs_matrix mat; + int code; + + image_rect.q.x = pim->Width; + image_rect.q.y = pim->Height; + if (pic->ImageMatrix.xx == ctm_only(pgs).xx && + pic->ImageMatrix.xy == ctm_only(pgs).xy && + pic->ImageMatrix.yx == ctm_only(pgs).yx && + pic->ImageMatrix.yy == ctm_only(pgs).yy) { + /* Handle common special case separately to accept singular matrix */ + mat.xx = mat.yy = 1.; + mat.yx = mat.xy = 0.; + mat.tx = ctm_only(pgs).tx - pic->ImageMatrix.tx; + mat.ty = ctm_only(pgs).ty - pic->ImageMatrix.ty; + } else { + code = gs_matrix_invert(&pic->ImageMatrix, &mat); + if (code < 0) + return code; + code = gs_matrix_multiply(&mat, &ctm_only(pgs), &mat); + if (code < 0) + return code; + } + code = gs_bbox_transform(&image_rect, &mat, &device_rect); + if (code < 0) + return code; + device_int_rect.p.x = (int)floor(device_rect.p.x); + device_int_rect.p.y = (int)floor(device_rect.p.y); + device_int_rect.q.x = (int)ceil(device_rect.q.x); + device_int_rect.q.y = (int)ceil(device_rect.q.y); + if (!gx_cpath_rect_visible(pcpath, &device_int_rect)) + return 0; + } + return 1; +} + +/* Create an image enumerator given image parameters and a graphics state. */ +int +gs_image_begin_typed(const gs_image_common_t * pic, gs_gstate * pgs, + bool uses_color, bool image_is_text, gx_image_enum_common_t ** ppie) +{ + gx_device *dev = gs_currentdevice(pgs); + gx_clip_path *pcpath; + int code = gx_effective_clip_path(pgs, &pcpath); + gx_device *dev2 = dev; + gx_device_color dc_temp, *pdevc = gs_currentdevicecolor_inline(pgs); + + if (code < 0) + return code; + /* Processing an image object operation, but this may be for a text object */ + ensure_tag_is_set(pgs, pgs->device, image_is_text ? GS_TEXT_TAG : GS_IMAGE_TAG); /* NB: may unset_dev_color */ + + if (uses_color) { + code = gx_set_dev_color(pgs); + if (code != 0) + return code; + code = gs_gstate_color_load(pgs); + if (code < 0) + return code; + } + /* Imagemask with shading color needs a special optimization + with converting the image into a clipping. + Check for such case after gs_gstate_color_load is done, + because it can cause interpreter callout. + */ + if (pic->type->begin_typed_image == &gx_begin_image1) { + gs_image_t *image = (gs_image_t *)pic; + + if(image->ImageMask) { + bool transpose = false; + gs_matrix_double mat; + + if((code = gx_image_compute_mat(pgs, NULL, &(image->ImageMatrix), &mat)) < 0) + return code; + if ((any_abs(mat.xy) > any_abs(mat.xx)) && (any_abs(mat.yx) > any_abs(mat.yy))) + transpose = true; /* pure landscape */ + code = gx_image_fill_masked_start(dev, gs_currentdevicecolor_inline(pgs), transpose, + pcpath, pgs->memory, pgs->log_op, &dev2); + if (code < 0) + return code; + } + if (dev->interpolate_control < 0) { /* Force interpolation before begin_typed_image */ + ((gs_data_image_t *)pic)->Interpolate = true; + } + else if (dev->interpolate_control == 0) { + ((gs_data_image_t *)pic)->Interpolate = false; /* Suppress interpolation */ + } + if (dev2 != dev) { + set_nonclient_dev_color(&dc_temp, 1); + pdevc = &dc_temp; + } + } + code = gx_device_begin_typed_image(dev2, (const gs_gstate *)pgs, + NULL, pic, NULL, pdevc, pcpath, pgs->memory, ppie); + if (code < 0) + return code; + code = is_image_visible(pic, pgs, pcpath); + if (code < 0) + return code; + if (!code) + (*ppie)->skipping = true; + return 0; +} + +/* Allocate an image enumerator. */ +static void +image_enum_init(gs_image_enum * penum) +{ + /* Clean pointers for GC. */ + penum->info = 0; + penum->dev = 0; + penum->plane_index = 0; + penum->num_planes = 0; +} +gs_image_enum * +gs_image_enum_alloc(gs_memory_t * mem, client_name_t cname) +{ + gs_image_enum *penum = + gs_alloc_struct(mem, gs_image_enum, &st_gs_image_enum, cname); + + if (penum != 0) { + penum->memory = mem; + image_enum_init(penum); + } + return penum; +} + +/* Start processing an ImageType 1 image. */ +int +gs_image_init(gs_image_enum * penum, const gs_image_t * pim, bool multi, + bool image_is_text, gs_gstate * pgs) +{ + gs_image_t image; + gx_image_enum_common_t *pie; + int code; + + image = *pim; + if (image.ImageMask) { + image.ColorSpace = NULL; + if (pgs->in_cachedevice <= 1) + image.adjust = false; + } else { + if (pgs->in_cachedevice) + return_error(gs_error_undefined); + if (image.ColorSpace == NULL) { + /* + * Use of a non-current color space is potentially + * incorrect, but it appears this case doesn't arise. + */ + image.ColorSpace = gs_cspace_new_DeviceGray(pgs->memory); + if (image.ColorSpace == NULL) + return_error(gs_error_VMerror); + } + } + code = gs_image_begin_typed((const gs_image_common_t *)&image, pgs, + image.ImageMask | image.CombineWithColor, + image_is_text, &pie); + if (code < 0) + return code; + return gs_image_enum_init(penum, pie, (const gs_data_image_t *)&image, + pgs); +} + +/* + * Return the number of bytes of data per row for a given plane. + */ +inline uint +gs_image_bytes_per_plane_row(const gs_image_enum * penum, int plane) +{ + const gx_image_enum_common_t *pie = penum->info; + + return (pie->plane_widths[plane] * pie->plane_depths[plane] + 7) >> 3; +} + +/* Cache information when initializing, or after transferring plane data. */ +static void +cache_planes(gs_image_enum *penum) +{ + int i; + + if (penum->wanted_varies) { + penum->wanted_varies = + !gx_image_planes_wanted(penum->info, penum->wanted); + for (i = 0; i < penum->num_planes; ++i) + if (penum->wanted[i]) + penum->image_planes[i].raster = + gs_image_bytes_per_plane_row(penum, i); + else + penum->image_planes[i].data = 0; + } +} +/* Advance to the next wanted plane. */ +static void +next_plane(gs_image_enum *penum) +{ + int px = penum->plane_index; + + do { + if (++px == penum->num_planes) + px = 0; + } while (!penum->wanted[px]); + penum->plane_index = px; +} +/* + * Initialize plane_index and (if appropriate) wanted and + * wanted_varies at the beginning of a group of planes. + */ +static void +begin_planes(gs_image_enum *penum) +{ + cache_planes(penum); + penum->plane_index = -1; + next_plane(penum); +} + +int +gs_image_common_init(gs_image_enum * penum, gx_image_enum_common_t * pie, + const gs_data_image_t * pim, gx_device * dev) +{ + /* + * HACK : For a compatibility with gs_image_cleanup_and_free_enum, + * penum->memory must be initialized in advance + * with the memory heap that owns *penum. + */ + int i; + + if (pim->Width == 0 || pim->Height == 0) { + gx_image_end(pie, false); + return 1; + } + image_enum_init(penum); + penum->dev = dev; + penum->info = pie; + penum->num_planes = pie->num_planes; + /* + * Note that for ImageType 3 InterleaveType 2, penum->height (the + * expected number of data rows) differs from pim->Height (the height + * of the source image in scan lines). This doesn't normally cause + * any problems, because penum->height is not used to determine when + * all the data has been processed: that is up to the plane_data + * procedure for the specific image type. + */ + penum->height = pim->Height; + for (i = 0; i < pie->num_planes; ++i) { + penum->planes[i].pos = 0; + penum->planes[i].source.size = 0; /* for gs_image_next_planes */ + penum->planes[i].source.data = 0; /* for GC */ + penum->planes[i].row.data = 0; /* for GC */ + penum->planes[i].row.size = 0; /* ditto */ + penum->image_planes[i].data_x = 0; /* just init once, never changes */ + } + /* Initialize the dynamic part of the state. */ + penum->y = 0; + penum->error = false; + penum->wanted_varies = true; + begin_planes(penum); + return 0; +} + +/* Initialize an enumerator for a general image. + penum->memory must be initialized in advance. +*/ +int +gs_image_enum_init(gs_image_enum * penum, gx_image_enum_common_t * pie, + const gs_data_image_t * pim, gs_gstate *pgs) +{ + pgs->device->sgr.stroke_stored = false; + return gs_image_common_init(penum, pie, pim, + (pgs->in_charpath ? NULL : + gs_currentdevice_inline(pgs))); +} + +/* Return the set of planes wanted. */ +const byte * +gs_image_planes_wanted(gs_image_enum *penum) +{ + int i; + + /* + * A plane is wanted at this interface if it is wanted by the + * underlying machinery and has no buffered or retained data. + */ + for (i = 0; i < penum->num_planes; ++i) + penum->client_wanted[i] = + (penum->wanted[i] && + penum->planes[i].pos + penum->planes[i].source.size < + penum->image_planes[i].raster); + return penum->client_wanted; +} + +/* + * Return the enumerator memory used for allocating the row buffers. + * Because some PostScript files use save/restore within an image data + * reading procedure, this must be a stable allocator. + */ +static gs_memory_t * +gs_image_row_memory(const gs_image_enum *penum) +{ + return gs_memory_stable(penum->memory); +} + +/* Free the row buffers when cleaning up. */ +static void +free_row_buffers(gs_image_enum *penum, int num_planes, client_name_t cname) +{ + int i; + + for (i = num_planes - 1; i >= 0; --i) { + if_debug3m('b', penum->memory, "[b]free plane %d row (0x%lx,%u)\n", + i, (ulong)penum->planes[i].row.data, + penum->planes[i].row.size); + gs_free_string(gs_image_row_memory(penum), penum->planes[i].row.data, + penum->planes[i].row.size, cname); + penum->planes[i].row.data = 0; + penum->planes[i].row.size = 0; + } +} + +/* Process the next piece of an image. */ +int +gs_image_next(gs_image_enum * penum, const byte * dbytes, uint dsize, + uint * pused) +{ + int px = penum->plane_index; + int num_planes = penum->num_planes; + int i, code; + uint used[GS_IMAGE_MAX_COMPONENTS]; + gs_const_string plane_data[GS_IMAGE_MAX_COMPONENTS]; + + if (penum->planes[px].source.size != 0) + return_error(gs_error_rangecheck); + for (i = 0; i < num_planes; i++) + plane_data[i].size = 0; + plane_data[px].data = dbytes; + plane_data[px].size = dsize; + penum->error = false; + code = gs_image_next_planes(penum, plane_data, used); + *pused = used[px]; + if (code >= 0) + next_plane(penum); + return code; +} + +int +gs_image_next_planes(gs_image_enum * penum, + gs_const_string *plane_data /*[num_planes]*/, + uint *used /*[num_planes]*/) +{ + const int num_planes = penum->num_planes; + int i; + int code = 0; + +#ifdef DEBUG + if (gs_debug_c('b')) { + int pi; + + for (pi = 0; pi < num_planes; ++pi) + dmprintf6(penum->memory, "[b]plane %d source=0x%lx,%u pos=%u data=0x%lx,%u\n", + pi, (ulong)penum->planes[pi].source.data, + penum->planes[pi].source.size, penum->planes[pi].pos, + (ulong)plane_data[pi].data, plane_data[pi].size); + } +#endif + for (i = 0; i < num_planes; ++i) { + used[i] = 0; + if (penum->wanted[i] && plane_data[i].size != 0) { + penum->planes[i].source.size = plane_data[i].size; + penum->planes[i].source.data = plane_data[i].data; + } + } + for (;;) { + /* If wanted can vary, only transfer 1 row at a time. */ + int h = (penum->wanted_varies ? 1 : max_int); + + /* Move partial rows from source[] to row[]. */ + for (i = 0; i < num_planes; ++i) { + int pos, size; + uint raster; + + if (!penum->wanted[i]) + continue; /* skip unwanted planes */ + pos = penum->planes[i].pos; + size = penum->planes[i].source.size; + raster = penum->image_planes[i].raster; + if (size > 0) { + if (pos < raster && (pos != 0 || size < raster)) { + /* Buffer a partial row. */ + int copy = min(size, raster - pos); + uint old_size = penum->planes[i].row.size; + + /* Make sure the row buffer is fully allocated. */ + if (raster > old_size) { + gs_memory_t *mem = gs_image_row_memory(penum); + byte *old_data = penum->planes[i].row.data; + byte *row = + (old_data == 0 ? + gs_alloc_string(mem, raster, + "gs_image_next(row)") : + gs_resize_string(mem, old_data, old_size, raster, + "gs_image_next(row)")); + + if_debug5m('b', mem, "[b]plane %d row (0x%lx,%u) => (0x%lx,%u)\n", + i, (ulong)old_data, old_size, + (ulong)row, raster); + if (row == 0) { + code = gs_note_error(gs_error_VMerror); + free_row_buffers(penum, i, "gs_image_next(row)"); + break; + } + penum->planes[i].row.data = row; + penum->planes[i].row.size = raster; + } + memcpy(penum->planes[i].row.data + pos, + penum->planes[i].source.data, copy); + penum->planes[i].source.data += copy; + penum->planes[i].source.size = size -= copy; + penum->planes[i].pos = pos += copy; + used[i] += copy; + } + } + if (h == 0) + continue; /* can't transfer any data this cycle */ + if (pos == raster) { + /* + * This plane will be transferred from the row buffer, + * so we can only transfer one row. + */ + h = min(h, 1); + penum->image_planes[i].data = penum->planes[i].row.data; + } else if (pos == 0 && size >= raster) { + /* We can transfer 1 or more planes from the source. */ + if (raster) { + h = min(h, size / raster); + penum->image_planes[i].data = penum->planes[i].source.data; + } + else + h = 0; + } else + h = 0; /* not enough data in this plane */ + } + if (h == 0 || code != 0) + break; + /* Pass rows to the device. */ + if (penum->dev == 0) { + /* + * ****** NOTE: THE FOLLOWING IS NOT CORRECT FOR ImageType 3 + * ****** InterleaveType 2, SINCE MASK HEIGHT AND IMAGE HEIGHT + * ****** MAY DIFFER (BY AN INTEGER FACTOR). ALSO, plane_depths[0] + * ****** AND plane_widths[0] ARE NOT UPDATED. + */ + if (penum->y + h < penum->height) + code = 0; + else + h = penum->height - penum->y, code = 1; + } else { + code = gx_image_plane_data_rows(penum->info, penum->image_planes, + h, &h); + if_debug2m('b', penum->memory, "[b]used %d, code=%d\n", h, code); + penum->error = code < 0; + } + penum->y += h; + /* Update positions and sizes. */ + if (h == 0) + break; + for (i = 0; i < num_planes; ++i) { + int count; + + if (!penum->wanted[i]) + continue; + count = penum->image_planes[i].raster * h; + if (penum->planes[i].pos) { + /* We transferred the row from the row buffer. */ + penum->planes[i].pos = 0; + } else { + /* We transferred the row(s) from the source. */ + penum->planes[i].source.data += count; + penum->planes[i].source.size -= count; + used[i] += count; + } + } + cache_planes(penum); + if (code > 0) + break; + } + /* Return the retained data pointers. */ + for (i = 0; i < num_planes; ++i) + plane_data[i] = penum->planes[i].source; + return code; +} + +/* Clean up after processing an image. */ +/* Public for ghostpcl. */ +int +gs_image_cleanup(gs_image_enum * penum, gs_gstate *pgs) +{ + int code = 0, code1; + + free_row_buffers(penum, penum->num_planes, "gs_image_cleanup(row)"); + if (penum->info != 0) { + if (dev_proc(penum->info->dev, dev_spec_op)(penum->info->dev, + gxdso_pattern_is_cpath_accum, NULL, 0)) { + /* Performing a conversion of imagemask into a clipping path. */ + gx_device *cdev = penum->info->dev; + + code = gx_image_end(penum->info, !penum->error); /* Releases penum->info . */ + code1 = gx_image_fill_masked_end(cdev, penum->dev, gs_currentdevicecolor_inline(pgs)); + if (code == 0) + code = code1; + } else + code = gx_image_end(penum->info, !penum->error); + } + /* Don't free the local enumerator -- the client does that. */ + + return code; +} + +/* Clean up after processing an image and free the enumerator. */ +int +gs_image_cleanup_and_free_enum(gs_image_enum * penum, gs_gstate *pgs) +{ + int code = gs_image_cleanup(penum, pgs); + + gs_free_object(penum->memory, penum, "gs_image_cleanup_and_free_enum"); + return code; +} |