/* Copyright (C) 1996, 2000 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: gdevpdfi.c,v 1.24.2.1.2.1 2003/01/17 00:49:01 giles Exp $ */ /* Image handling for PDF-writing driver */ #include "gx.h" #include "gserrors.h" #include "gsdevice.h" #include "gsflip.h" #include "gdevpdfx.h" #include "gdevpdfg.h" #include "gdevpdfo.h" /* for data stream */ #include "gxcspace.h" #include "gximage3.h" #include "gximag3x.h" #include "gsiparm4.h" /* Forward references */ private image_enum_proc_plane_data(pdf_image_plane_data); private image_enum_proc_end_image(pdf_image_end_image); private image_enum_proc_end_image(pdf_image_end_image_object); private IMAGE3_MAKE_MID_PROC(pdf_image3_make_mid); private IMAGE3_MAKE_MCDE_PROC(pdf_image3_make_mcde); private IMAGE3X_MAKE_MID_PROC(pdf_image3x_make_mid); private IMAGE3X_MAKE_MCDE_PROC(pdf_image3x_make_mcde); private const gx_image_enum_procs_t pdf_image_enum_procs = { pdf_image_plane_data, pdf_image_end_image }; private const gx_image_enum_procs_t pdf_image_object_enum_procs = { pdf_image_plane_data, pdf_image_end_image_object }; /* ---------------- Driver procedures ---------------- */ /* Define the structure for keeping track of progress through an image. */ typedef struct pdf_image_enum_s { gx_image_enum_common; gs_memory_t *memory; int width; int bits_per_pixel; /* bits per pixel (per plane) */ int rows_left; pdf_image_writer writer; gs_matrix mat; } pdf_image_enum; gs_private_st_composite(st_pdf_image_enum, pdf_image_enum, "pdf_image_enum", pdf_image_enum_enum_ptrs, pdf_image_enum_reloc_ptrs); /* GC procedures */ private ENUM_PTRS_WITH(pdf_image_enum_enum_ptrs, pdf_image_enum *pie) if (index < pdf_image_writer_max_ptrs) { gs_ptr_type_t ret = ENUM_USING(st_pdf_image_writer, &pie->writer, sizeof(pie->writer), index); if (ret == 0) /* don't stop early */ ENUM_RETURN(0); return ret; } return ENUM_USING_PREFIX(st_gx_image_enum_common, pdf_image_writer_max_ptrs); ENUM_PTRS_END private RELOC_PTRS_WITH(pdf_image_enum_reloc_ptrs, pdf_image_enum *pie) { RELOC_USING(st_pdf_image_writer, &pie->writer, sizeof(pie->writer)); RELOC_USING(st_gx_image_enum_common, vptr, size); } RELOC_PTRS_END /* * Test whether we can write an image in-line. Before PDF 1.2, this is only * allowed for masks, images in built-in color spaces, and images in Indexed * color spaces based on these with a string lookup table. */ private bool can_write_image_in_line(const gx_device_pdf *pdev, const gs_image_t *pim) { const gs_color_space *pcs; if (pim->ImageMask) return true; if (pdev->CompatibilityLevel >= 1.2) return true; pcs = pim->ColorSpace; cs: switch (gs_color_space_get_index(pcs)) { case gs_color_space_index_DeviceGray: case gs_color_space_index_DeviceRGB: case gs_color_space_index_DeviceCMYK: return true; case gs_color_space_index_Indexed: if (pcs->params.indexed.use_proc) return false; pcs = (const gs_color_space *)&pcs->params.indexed.base_space; goto cs; default: return false; } } /* * Start processing an image. This procedure takes extra arguments because * it has to do something slightly different for the parts of an ImageType 3 * image. */ typedef enum { PDF_IMAGE_DEFAULT, PDF_IMAGE_TYPE3_MASK, /* no in-line, don't render */ PDF_IMAGE_TYPE3_DATA /* no in-line */ } pdf_typed_image_context_t; private int pdf_begin_typed_image(gx_device_pdf *pdev, 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, pdf_typed_image_context_t context) { const gs_pixel_image_t *pim; int code; pdf_image_enum *pie; gs_image_format_t format; const gs_color_space *pcs; gs_color_space cs_gray_temp; cos_value_t cs_value; int num_components; bool is_mask = false, in_line = false; gs_int_rect rect; /* * We define this union because psdf_setup_image_filters may alter the * gs_pixel_image_t part, but pdf_begin_image_data must also have access * to the type-specific parameters. */ union iu_ { gs_pixel_image_t pixel; /* we may change some components */ gs_image1_t type1; gs_image3_t type3; gs_image3x_t type3x; gs_image4_t type4; } image; ulong nbytes; int width, height; /* Check for the image types we can handle. */ switch (pic->type->index) { case 1: { const gs_image_t *pim1 = (const gs_image_t *)pic; if (pim1->Alpha != gs_image_alpha_none) goto nyi; is_mask = pim1->ImageMask; in_line = context == PDF_IMAGE_DEFAULT && can_write_image_in_line(pdev, pim1); image.type1 = *pim1; break; } case 3: { const gs_image3_t *pim3 = (const gs_image3_t *)pic; if (pdev->CompatibilityLevel < 1.3) goto nyi; if (prect && !(prect->p.x == 0 && prect->p.y == 0 && prect->q.x == pim3->Width && prect->q.y == pim3->Height)) goto nyi; /* * We handle ImageType 3 images in a completely different way: * the default implementation sets up the enumerator. */ return gx_begin_image3_generic((gx_device *)pdev, pis, pmat, pic, prect, pdcolor, pcpath, mem, pdf_image3_make_mid, pdf_image3_make_mcde, pinfo); } case IMAGE3X_IMAGETYPE: { /* See ImageType3 above for more information. */ const gs_image3x_t *pim3x = (const gs_image3x_t *)pic; if (pdev->CompatibilityLevel < 1.4) goto nyi; if (prect && !(prect->p.x == 0 && prect->p.y == 0 && prect->q.x == pim3x->Width && prect->q.y == pim3x->Height)) goto nyi; return gx_begin_image3x_generic((gx_device *)pdev, pis, pmat, pic, prect, pdcolor, pcpath, mem, pdf_image3x_make_mid, pdf_image3x_make_mcde, pinfo); } case 4: if (pdev->CompatibilityLevel < 1.3) goto nyi; image.type4 = *(const gs_image4_t *)pic; break; default: goto nyi; } pim = (const gs_pixel_image_t *)pic; format = pim->format; switch (format) { case gs_image_format_chunky: case gs_image_format_component_planar: break; default: goto nyi; } pcs = pim->ColorSpace; num_components = (is_mask ? 1 : gs_color_space_num_components(pcs)); code = pdf_open_page(pdev, PDF_IN_STREAM); if (code < 0) return code; pdf_put_clip_path(pdev, pcpath); if (context == PDF_IMAGE_TYPE3_MASK) { /* * The soft mask for an ImageType 3x image uses a DevicePixel * color space, which pdf_color_space() can't handle. Patch it * to DeviceGray here. */ gs_cspace_init_DeviceGray(&cs_gray_temp); pcs = &cs_gray_temp; } else if (is_mask) code = pdf_prepare_imagemask(pdev, pis, pdcolor); else code = pdf_prepare_image(pdev, pis); if (code < 0) goto nyi; if (prect) rect = *prect; else { rect.p.x = rect.p.y = 0; rect.q.x = pim->Width, rect.q.y = pim->Height; } pie = gs_alloc_struct(mem, pdf_image_enum, &st_pdf_image_enum, "pdf_begin_image"); if (pie == 0) return_error(gs_error_VMerror); *pinfo = (gx_image_enum_common_t *) pie; gx_image_enum_common_init(*pinfo, (const gs_data_image_t *) pim, (context == PDF_IMAGE_TYPE3_MASK ? &pdf_image_object_enum_procs : &pdf_image_enum_procs), (gx_device *)pdev, num_components, format); pie->memory = mem; width = rect.q.x - rect.p.x; pie->width = width; height = rect.q.y - rect.p.y; pie->bits_per_pixel = pim->BitsPerComponent * num_components / pie->num_planes; pie->rows_left = height; nbytes = (((ulong) pie->width * pie->bits_per_pixel + 7) >> 3) * pie->num_planes * pie->rows_left; in_line &= nbytes <= MAX_INLINE_IMAGE_BYTES; if (rect.p.x != 0 || rect.p.y != 0 || rect.q.x != pim->Width || rect.q.y != pim->Height || (is_mask && pim->CombineWithColor) /* Color space setup used to be done here: see SRZB comment below. */ ) { gs_free_object(mem, pie, "pdf_begin_image"); goto nyi; } if (pmat == 0) pmat = &ctm_only(pis); { gs_matrix mat; gs_matrix bmat; int code; pdf_make_bitmap_matrix(&bmat, -rect.p.x, -rect.p.y, pim->Width, pim->Height, height); if ((code = gs_matrix_invert(&pim->ImageMatrix, &mat)) < 0 || (code = gs_matrix_multiply(&bmat, &mat, &mat)) < 0 || (code = gs_matrix_multiply(&mat, pmat, &pie->mat)) < 0 ) { gs_free_object(mem, pie, "pdf_begin_image"); return code; } } if ((code = pdf_begin_write_image(pdev, &pie->writer, gs_no_id, width, height, NULL, in_line)) < 0 || /* * Some regrettable PostScript code (such as LanguageLevel 1 output * from Microsoft's PSCRIPT.DLL driver) misuses the transfer * function to accomplish the equivalent of indexed color. * Downsampling (well, only averaging) or JPEG compression are not * compatible with this. Play it safe by using only lossless * filters if the transfer function(s) is/are other than the * identity. */ (code = (pdev->transfer_not_identity ? psdf_setup_lossless_filters((gx_device_psdf *) pdev, &pie->writer.binary, &image.pixel) : psdf_setup_image_filters((gx_device_psdf *) pdev, &pie->writer.binary, &image.pixel, pmat, pis))) < 0 || /* SRZB 2001-04-25/Bl * Since psdf_setup_image_filters may change the color space * (in case of pdev->params.ConvertCMYKImagesToRGB == true), * we postpone the selection of the PDF color space to here: */ (!is_mask && (code = pdf_color_space(pdev, &cs_value, image.pixel.ColorSpace, (in_line ? &pdf_color_space_names_short : &pdf_color_space_names), in_line)) < 0) || (code = pdf_begin_image_data(pdev, &pie->writer, (const gs_pixel_image_t *)&image, &cs_value)) < 0 ) { /****** SHOULD FREE STRUCTURES AND CLEAN UP HERE ******/ goto nyi; /* fall back to default implementation */ } return 0; nyi: return gx_default_begin_typed_image ((gx_device *)pdev, pis, pmat, pic, prect, pdcolor, pcpath, mem, pinfo); } int gdev_pdf_begin_typed_image(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) { return pdf_begin_typed_image((gx_device_pdf *)dev, pis, pmat, pic, prect, pdcolor, pcpath, mem, pinfo, PDF_IMAGE_DEFAULT); } /* ---------------- All images ---------------- */ /* Process the next piece of an image. */ private int pdf_image_plane_data(gx_image_enum_common_t * info, const gx_image_plane_t * planes, int height, int *rows_used) { gx_device_pdf *pdev = (gx_device_pdf *)info->dev; pdf_image_enum *pie = (pdf_image_enum *) info; int h = height; int y; /****** DOESN'T HANDLE IMAGES WITH VARYING WIDTH PER PLANE ******/ uint width_bits = pie->width * pie->plane_depths[0]; /****** DOESN'T HANDLE NON-ZERO data_x CORRECTLY ******/ uint bcount = (width_bits + 7) >> 3; uint ignore; int nplanes = pie->num_planes; stream *s = pdev->streams.strm; long pos = stell(s); int code; int status = 0; if (h > pie->rows_left) h = pie->rows_left; pie->rows_left -= h; for (y = 0; y < h; ++y) { if (nplanes > 1) { /* * We flip images in blocks, and each block except the last one * must contain an integral number of pixels. The easiest way * to meet this condition is for all blocks except the last to * be a multiple of 3 source bytes (guaranteeing an integral * number of 1/2/4/8/12-bit samples), i.e., 3*nplanes flipped * bytes. This requires a buffer of at least * 3*GS_IMAGE_MAX_COMPONENTS bytes. */ int pi; uint count = bcount; uint offset = 0; #define ROW_BYTES max(200 /*arbitrary*/, 3 * GS_IMAGE_MAX_COMPONENTS) const byte *bit_planes[GS_IMAGE_MAX_COMPONENTS]; int block_bytes = ROW_BYTES / (3 * nplanes) * 3; byte row[ROW_BYTES]; for (pi = 0; pi < nplanes; ++pi) bit_planes[pi] = planes[pi].data + planes[pi].raster * y; while (count) { uint flip_count; uint flipped_count; if (count >= block_bytes) { flip_count = block_bytes; flipped_count = block_bytes * nplanes; } else { flip_count = count; flipped_count = (width_bits % (block_bytes * 8) * nplanes + 7) >> 3; } image_flip_planes(row, bit_planes, offset, flip_count, nplanes, pie->plane_depths[0]); status = sputs(pie->writer.binary.strm, row, flipped_count, &ignore); if (status < 0) break; offset += flip_count; count -= flip_count; } } else { status = sputs(pie->writer.binary.strm, planes[0].data + planes[0].raster * y, bcount, &ignore); } if (status < 0) break; } *rows_used = h; if (status < 0) return_error(gs_error_ioerror); code = cos_stream_add_since(pie->writer.data, pos); return (code < 0 ? code : !pie->rows_left); #undef ROW_BYTES } /* Clean up by releasing the buffers. */ private int pdf_image_end_image_data(gx_image_enum_common_t * info, bool draw_last, bool do_image) { gx_device_pdf *pdev = (gx_device_pdf *)info->dev; pdf_image_enum *pie = (pdf_image_enum *)info; int height = pie->writer.height; int data_height = height - pie->rows_left; int code; if (pie->writer.pres) ((pdf_x_object_t *)pie->writer.pres)->data_height = data_height; else pdf_put_image_matrix(pdev, &pie->mat, (height == 0 || data_height == 0 ? 1.0 : (double)data_height / height)); code = pdf_end_image_binary(pdev, &pie->writer, data_height); if (code < 0) return code; code = pdf_end_write_image(pdev, &pie->writer); switch (code) { default: return code; /* error */ case 1: code = 0; break; case 0: if (do_image) code = pdf_do_image(pdev, pie->writer.pres, &pie->mat, true); } gs_free_object(pie->memory, pie, "pdf_end_image"); return code; } /* End a normal image, drawing it. */ private int pdf_image_end_image(gx_image_enum_common_t * info, bool draw_last) { return pdf_image_end_image_data(info, draw_last, true); } /* ---------------- Type 3/3x images ---------------- */ /* * For both types of masked images, we create temporary dummy (null) devices * that forward the begin_typed_image call to the implementation above. */ private int pdf_make_mxd(gx_device **pmxdev, gx_device *tdev, gs_memory_t *mem) { gx_device *fdev; int code = gs_copydevice(&fdev, (const gx_device *)&gs_null_device, mem); if (code < 0) return code; gx_device_set_target((gx_device_forward *)fdev, tdev); *pmxdev = fdev; return 0; } /* End the mask of an ImageType 3 image, not drawing it. */ private int pdf_image_end_image_object(gx_image_enum_common_t * info, bool draw_last) { return pdf_image_end_image_data(info, draw_last, false); } /* ---------------- Type 3 images ---------------- */ /* Implement the mask image device. */ private dev_proc_begin_typed_image(pdf_mid_begin_typed_image); private int pdf_image3_make_mid(gx_device **pmidev, gx_device *dev, int width, int height, gs_memory_t *mem) { int code = pdf_make_mxd(pmidev, dev, mem); if (code < 0) return code; set_dev_proc(*pmidev, begin_typed_image, pdf_mid_begin_typed_image); return 0; } private int pdf_mid_begin_typed_image(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) { /* The target of the null device is the pdfwrite device. */ gx_device_pdf *const pdev = (gx_device_pdf *) ((gx_device_null *)dev)->target; int code = pdf_begin_typed_image (pdev, pis, pmat, pic, prect, pdcolor, pcpath, mem, pinfo, PDF_IMAGE_TYPE3_MASK); if (code < 0) return code; if ((*pinfo)->procs != &pdf_image_object_enum_procs) { /* We couldn't handle the mask image. Bail out. */ /* (This is never supposed to happen.) */ return_error(gs_error_rangecheck); } return code; } /* Implement the mask clip device. */ private int pdf_image3_make_mcde(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, gx_device **pmcdev, gx_device *midev, gx_image_enum_common_t *pminfo, const gs_int_point *origin) { int code = pdf_make_mxd(pmcdev, midev, mem); pdf_image_enum *pmie; pdf_image_enum *pmce; cos_stream_t *pmcs; if (code < 0) return code; code = pdf_begin_typed_image ((gx_device_pdf *)dev, pis, pmat, pic, prect, pdcolor, pcpath, mem, pinfo, PDF_IMAGE_TYPE3_DATA); if (code < 0) return code; /* Add the /Mask entry to the image dictionary. */ if ((*pinfo)->procs != &pdf_image_enum_procs) { /* We couldn't handle the image. Bail out. */ gx_image_end(*pinfo, false); gs_free_object(mem, *pmcdev, "pdf_image3_make_mcde"); return_error(gs_error_rangecheck); } pmie = (pdf_image_enum *)pminfo; pmce = (pdf_image_enum *)(*pinfo); pmcs = (cos_stream_t *)pmce->writer.pres->object; return cos_dict_put_c_key_object(cos_stream_dict(pmcs), "/Mask", pmie->writer.pres->object); } /* ---------------- Type 3x images ---------------- */ /* Implement the mask image device. */ private int pdf_image3x_make_mid(gx_device **pmidev, gx_device *dev, int width, int height, int depth, gs_memory_t *mem) { int code = pdf_make_mxd(pmidev, dev, mem); if (code < 0) return code; set_dev_proc(*pmidev, begin_typed_image, pdf_mid_begin_typed_image); return 0; } /* Implement the mask clip device. */ private int pdf_image3x_make_mcde(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, gx_device **pmcdev, gx_device *midev[2], gx_image_enum_common_t *pminfo[2], const gs_int_point origin[2], const gs_image3x_t *pim) { int code; pdf_image_enum *pmie; pdf_image_enum *pmce; cos_stream_t *pmcs; int i; const gs_image3x_mask_t *pixm; if (midev[0]) { if (midev[1]) return_error(gs_error_rangecheck); i = 0, pixm = &pim->Opacity; } else if (midev[1]) i = 1, pixm = &pim->Shape; else return_error(gs_error_rangecheck); code = pdf_make_mxd(pmcdev, midev[i], mem); if (code < 0) return code; code = pdf_begin_typed_image ((gx_device_pdf *)dev, pis, pmat, pic, prect, pdcolor, pcpath, mem, pinfo, PDF_IMAGE_TYPE3_DATA); if (code < 0) return code; if ((*pinfo)->procs != &pdf_image_enum_procs) { /* We couldn't handle the image. Bail out. */ gx_image_end(*pinfo, false); gs_free_object(mem, *pmcdev, "pdf_image3x_make_mcde"); return_error(gs_error_rangecheck); } pmie = (pdf_image_enum *)pminfo[i]; pmce = (pdf_image_enum *)(*pinfo); pmcs = (cos_stream_t *)pmce->writer.pres->object; /* * Add the SMask entry to the image dictionary, and, if needed, * the Matte entry to the mask dictionary. */ if (pixm->has_Matte) { int num_components = gs_color_space_num_components(pim->ColorSpace); code = cos_dict_put_c_key_floats( (cos_dict_t *)pmie->writer.pres->object, "/Matte", pixm->Matte, num_components); if (code < 0) return code; } return cos_dict_put_c_key_object(cos_stream_dict(pmcs), "/SMask", pmie->writer.pres->object); }