summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKen Sharp <ken.sharp@artifex.com>2021-05-29 19:18:29 +0100
committerKen Sharp <ken.sharp@artifex.com>2021-05-29 19:18:29 +0100
commit9b9fd74e5adae9913a07423518c1452b135d3c00 (patch)
tree406d54f8892b830bed4481cbb28637feeb844666
parent36e09729adad6b9dceca9ebfaca30d3980d1df40 (diff)
downloadghostpdl-9b9fd74e5adae9913a07423518c1452b135d3c00.tar.gz
pdfwrite - Add a PassThrough for JPX encoded images
Bug #703891 "Add PassThrough for JPX images to pdfwrite" Behaves in exactly the same way as the existing pass through for JPEG images, and has the same limitations. See vectorDevices.htm. Several test suite files show (mostly relatively minor) progressions with this enhancement.
-rw-r--r--Resource/Init/gs_pdfwr.ps2
-rw-r--r--base/gxdevsop.h7
-rw-r--r--base/sjpx_openjpeg.c16
-rw-r--r--base/sjpx_openjpeg.h11
-rw-r--r--devices/vector/gdevpdfi.c41
-rw-r--r--devices/vector/gdevpdfj.c3
-rw-r--r--devices/vector/gdevpsdf.h6
-rw-r--r--devices/vector/gdevpsdi.c8
-rw-r--r--devices/vector/gdevpsdp.c1
-rw-r--r--doc/VectorDevices.htm13
-rw-r--r--psi/zfjpx.c31
11 files changed, 133 insertions, 6 deletions
diff --git a/Resource/Init/gs_pdfwr.ps b/Resource/Init/gs_pdfwr.ps
index 6117d4d25..ef468522c 100644
--- a/Resource/Init/gs_pdfwr.ps
+++ b/Resource/Init/gs_pdfwr.ps
@@ -102,6 +102,7 @@ languagelevel 2 .setlanguagelevel
/UseFlateCompression //true
/UsePrologue //false
/PassThroughJPEGImages //true
+ /PassThroughJPXImages //true
.dicttomark readonly def
/.distillersettings mark
@@ -823,6 +824,7 @@ currentdict /.pdf_hook_DSC_Creator undef
/SubsetFonts { }
/DSCEncodingToUnicode { }
/PassThroughJPEGImages { }
+ /PassThroughJPXImages { }
/PSDocOptions { }
/PSPageOptions { }
.dicttomark readonly def
diff --git a/base/gxdevsop.h b/base/gxdevsop.h
index 479d97798..b7a63ccdf 100644
--- a/base/gxdevsop.h
+++ b/base/gxdevsop.h
@@ -328,6 +328,13 @@ enum {
gxdso_JPEG_passthrough_begin,
gxdso_JPEG_passthrough_data,
gxdso_JPEG_passthrough_end,
+ /* And similarly JPX passthrough requests/control. Currently used for the pdfwrite family only.
+ */
+ gxdso_JPX_passthrough_query,
+ gxdso_JPX_passthrough_begin,
+ gxdso_JPX_passthrough_data,
+ gxdso_JPX_passthrough_end,
+
gxdso_supports_iccpostrender,
/* Retrieve the last device in a device chain
(either forwarding or subclass devices).
diff --git a/base/sjpx_openjpeg.c b/base/sjpx_openjpeg.c
index 8276cedd7..deaccd80a 100644
--- a/base/sjpx_openjpeg.c
+++ b/base/sjpx_openjpeg.c
@@ -701,6 +701,14 @@ s_opjd_process(stream_state * ss, stream_cursor_read * pr,
if (in_size > 0)
{
+ if (state->PassThrough && state->PassThroughfn) {
+ if (state->PassThrough && state->PassThroughfn && !state->StartedPassThrough) {
+ state->StartedPassThrough = 1;
+ (state->PassThroughfn)(state->device, NULL, 1);
+ }
+ (state->PassThroughfn)(state->device, (byte *)pr->ptr + 1, (byte *)pr->limit - (byte *)pr->ptr);
+ }
+
/* buffer available data */
code = opj_lock(ss->memory);
if (code < 0) return code;
@@ -776,6 +784,10 @@ s_opjd_set_defaults(stream_state * ss) {
state->alpha = false;
state->colorspace = gs_jpx_cs_rgb;
+ state->StartedPassThrough = 0;
+ state->PassThrough = 0;
+ state->PassThroughfn = NULL;
+ state->device = (void *)NULL;
}
/* stream release.
@@ -786,6 +798,10 @@ s_opjd_release(stream_state *ss)
{
stream_jpxd_state *const state = (stream_jpxd_state *) ss;
+ if (state->PassThrough && state->PassThroughfn && state->StartedPassThrough) {
+ state->StartedPassThrough = 0;
+ (state->PassThroughfn)(state->device, NULL, 0);
+ }
/* empty stream or failed to accumulate */
if (state->codec == NULL)
return;
diff --git a/base/sjpx_openjpeg.h b/base/sjpx_openjpeg.h
index 0ee57b64f..aa8ddee7a 100644
--- a/base/sjpx_openjpeg.h
+++ b/base/sjpx_openjpeg.h
@@ -41,6 +41,9 @@ typedef struct stream_block_s
unsigned long fill;
} stream_block;
+#define JPXD_PassThrough(proc)\
+ int proc(void *d, byte *Buffer, int Size)
+
/* Stream state for the jpx codec using openjpeg
* We rely on our finalization call to free the
* associated handle and pointers.
@@ -68,6 +71,14 @@ typedef struct stream_jpxd_state_s
int *sign_comps; /* compensate for signed data (signed => unsigned) */
unsigned char *row_data;
+
+ int PassThrough; /* 0 or 1 */
+ bool StartedPassThrough; /* Don't signal multiple starts for the same decode */
+ JPXD_PassThrough((*PassThroughfn)); /* We don't want the stream code or
+ * JPEG code to have to handle devices
+ * so we use a function at the interpreter level
+ */
+ void *device; /* The device we need to send PassThrough data to */
} stream_jpxd_state;
extern const stream_template s_jpxd_template;
diff --git a/devices/vector/gdevpdfi.c b/devices/vector/gdevpdfi.c
index d1363a69f..2ce3bf2c2 100644
--- a/devices/vector/gdevpdfi.c
+++ b/devices/vector/gdevpdfi.c
@@ -80,6 +80,7 @@ typedef struct pdf_image_enum_s {
gs_matrix mat;
gs_color_space_index initial_colorspace;
int JPEG_PassThrough;
+ int JPX_PassThrough;
} 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);
@@ -1115,6 +1116,7 @@ pdf_begin_typed_image(gx_device_pdf *pdev, const gs_gstate * pgs,
* should get the data. But lets get the simple code working first.
*/
pdev->JPEG_PassThrough = 0;
+ pdev->JPX_PassThrough = 0;
pdev->image_mask_is_SMask = false;
if (pdev->CompatibilityLevel < 1.2 ||
(prect && !(prect->p.x == 0 && prect->p.y == 0 &&
@@ -1128,6 +1130,7 @@ pdf_begin_typed_image(gx_device_pdf *pdev, const gs_gstate * pgs,
case IMAGE3X_IMAGETYPE:
pdev->JPEG_PassThrough = 0;
+ pdev->JPX_PassThrough = 0;
if (pdev->CompatibilityLevel < 1.4 ||
(prect && !(prect->p.x == 0 && prect->p.y == 0 &&
prect->q.x == ((const gs_image3x_t *)pic)->Width &&
@@ -1195,6 +1198,7 @@ pdf_begin_typed_image(gx_device_pdf *pdev, const gs_gstate * pgs,
* the image.
*/
pdev->JPEG_PassThrough = 0;
+ pdev->JPX_PassThrough = 0;
use_fallback = 0;
code = convert_type4_to_masked_image(pdev, pgs, pic, prect, pdcolor,
pcpath, mem,pinfo);
@@ -1207,6 +1211,7 @@ pdf_begin_typed_image(gx_device_pdf *pdev, const gs_gstate * pgs,
}
}
pdev->JPEG_PassThrough = 0;
+ pdev->JPX_PassThrough = 0;
code = convert_type4_image(pdev, pgs, pmat, pic, prect, pdcolor,
pcpath, mem, pinfo, context, image, pnamed);
if (code < 0)
@@ -1447,12 +1452,16 @@ pdf_begin_typed_image(gx_device_pdf *pdev, const gs_gstate * pgs,
if (code < 0)
goto fail_and_fallback;
+ if (pdev->CompatibilityLevel < 1.5)
+ pdev->JPX_PassThrough = 0;
+
if (!convert_to_process_colors)
{
gs_color_space_index csi;
if (pdev->params.TransferFunctionInfo == tfi_Apply && pdev->transfer_not_identity && !is_mask) {
pdev->JPEG_PassThrough = 0;
+ pdev->JPX_PassThrough = 0;
csi = gs_color_space_get_index(image[0].pixel.ColorSpace);
if (csi == gs_color_space_index_Indexed) {
csi = gs_color_space_get_index(image[0].pixel.ColorSpace->base_space);
@@ -1503,7 +1512,7 @@ pdf_begin_typed_image(gx_device_pdf *pdev, const gs_gstate * pgs,
}
/* If we are not preserving the colour space unchanged, then we can't pass through JPEG */
else
- pdev->JPEG_PassThrough = 0;
+ pdev->JPEG_PassThrough = pdev->JPX_PassThrough = 0;
/* Code below here deals with setting up the multiple data stream writing.
* We can have up to 4 stream writers, which we keep in an array. We must
@@ -1518,6 +1527,7 @@ pdf_begin_typed_image(gx_device_pdf *pdev, const gs_gstate * pgs,
*/
if (in_line) {
pdev->JPEG_PassThrough = 0;
+ pdev->JPX_PassThrough = 0;
code = new_setup_lossless_filters((gx_device_psdf *) pdev,
&pie->writer.binary[0],
&image[0].pixel, in_line, convert_to_process_colors, (gs_matrix *)pmat, (gs_gstate *)pgs);
@@ -1559,7 +1569,7 @@ pdf_begin_typed_image(gx_device_pdf *pdev, const gs_gstate * pgs,
image[0].pixel.ColorSpace = pcs_device;
}
- if (pdev->JPEG_PassThrough) {
+ if (pdev->JPEG_PassThrough || pdev->JPX_PassThrough) {
/* if (pie->writer.alt_writer_count > 1) {
s_close_filters(&pie->writer.binary[0].strm, uncompressed);
memset(pie->writer.binary + 1, 0, sizeof(pie->writer.binary[1]));
@@ -1569,6 +1579,7 @@ pdf_begin_typed_image(gx_device_pdf *pdev, const gs_gstate * pgs,
pie->writer.alt_writer_count = 1;
}
pie->JPEG_PassThrough = pdev->JPEG_PassThrough;
+ pie->JPX_PassThrough = pdev->JPX_PassThrough;
if (pie->writer.alt_writer_count > 1) {
code = pdf_make_alt_stream(pdev, &pie->writer.binary[1]);
@@ -1681,6 +1692,7 @@ pdf_begin_typed_image(gx_device_pdf *pdev, const gs_gstate * pgs,
/* Do the fallback */
if (use_fallback) {
pdev->JPEG_PassThrough = 0;
+ pdev->JPX_PassThrough = 0;
code = gx_default_begin_typed_image
((gx_device *)pdev, pgs, pmat, pic, prect, pdcolor, pcpath, mem, pinfo);
}
@@ -1789,7 +1801,7 @@ pdf_image_plane_data(gx_image_enum_common_t * info,
pdf_image_enum *pie = (pdf_image_enum *) info;
int i;
- if (pie->JPEG_PassThrough) {
+ if (pie->JPEG_PassThrough || pie->JPX_PassThrough) {
pie->rows_left -= height;
*rows_used = height;
return !pie->rows_left;
@@ -2871,6 +2883,29 @@ gdev_pdf_dev_spec_op(gx_device *pdev1, int dev_spec_op, void *data, int size)
pdev->PassThroughWriter = 0;
return 0;
break;
+ case gxdso_JPX_passthrough_query:
+ pdev->JPX_PassThrough = pdev->params.PassThroughJPXImages;
+ return 1;
+ break;
+ case gxdso_JPX_passthrough_begin:
+ return 0;
+ break;
+ case gxdso_JPX_passthrough_data:
+ if (pdev->JPX_PassThrough && pdev->PassThroughWriter)
+ {
+ uint ignore;
+ if (sputs(pdev->PassThroughWriter,
+ data, size,
+ &ignore) < 0)
+ return_error(gs_error_ioerror);
+ }
+ return 0;
+ break;
+ case gxdso_JPX_passthrough_end:
+ pdev->JPX_PassThrough = 0;
+ pdev->PassThroughWriter = 0;
+ return 0;
+ break;
case gxdso_event_info:
{
dev_param_req_t *request = (dev_param_req_t *)data;
diff --git a/devices/vector/gdevpdfj.c b/devices/vector/gdevpdfj.c
index 6140d4d68..026925101 100644
--- a/devices/vector/gdevpdfj.c
+++ b/devices/vector/gdevpdfj.c
@@ -412,6 +412,9 @@ pdf_begin_image_data(gx_device_pdf * pdev, pdf_image_writer * piw,
if (pdev->JPEG_PassThrough) {
CHECK(cos_dict_put_c_strings(pcd, "/Filter", "/DCTDecode"));
}
+ if (pdev->JPX_PassThrough) {
+ CHECK(cos_dict_put_c_strings(pcd, "/Filter", "/JPXDecode"));
+ }
return code;
}
diff --git a/devices/vector/gdevpsdf.h b/devices/vector/gdevpsdf.h
index d20b0bb2f..8f5d6ad8d 100644
--- a/devices/vector/gdevpsdf.h
+++ b/devices/vector/gdevpsdf.h
@@ -174,6 +174,7 @@ typedef struct psdf_distiller_params_s {
int MaxSubsetPct;
bool SubsetFonts;
bool PassThroughJPEGImages;
+ bool PassThroughJPXImages;
gs_param_string PSDocOptions;
gs_param_string_array PSPageOptions;
} psdf_distiller_params;
@@ -267,6 +268,8 @@ extern const stream_template s_zlibE_template;
#define psdf_JPEGPassThrough_param_defaults\
1 /* PassThroughJPEGImages */
+#define psdf_JPXPassThrough_param_defaults\
+ 1 /* PassThroughJPXImages */
#define psdf_PSOption_param_defaults\
{0}, /* PSDocOptions */\
@@ -292,6 +295,7 @@ typedef enum {
bool HaveCIDSystem;\
double ParamCompatibilityLevel;\
bool JPEG_PassThrough;\
+ bool JPX_PassThrough;\
psdf_distiller_params params
typedef struct gx_device_psdf_s {
@@ -307,12 +311,14 @@ typedef struct gx_device_psdf_s {
false,\
1.3,\
0,\
+ 0,\
{ psdf_general_param_defaults(ascii),\
psdf_color_image_param_defaults,\
psdf_gray_image_param_defaults,\
psdf_mono_image_param_defaults,\
psdf_font_param_defaults,\
psdf_JPEGPassThrough_param_defaults,\
+ psdf_JPXPassThrough_param_defaults,\
psdf_PSOption_param_defaults\
}
/* st_device_psdf is never instantiated per se, but we still need to */
diff --git a/devices/vector/gdevpsdi.c b/devices/vector/gdevpsdi.c
index 8ac9735a7..955e06b2b 100644
--- a/devices/vector/gdevpsdi.c
+++ b/devices/vector/gdevpsdi.c
@@ -269,7 +269,7 @@ setup_image_compression(psdf_binary_writer *pbw, const psdf_image_params *pdip,
templat = lossless_template;
if (dict != NULL) /* Some interpreters don't supply filter parameters. */
gs_c_param_list_read(dict); /* ensure param list is in read mode */
- if (templat == 0 || pdev->JPEG_PassThrough) /* no compression */
+ if (templat == 0 || pdev->JPEG_PassThrough || pdev->JPX_PassThrough) /* no compression */
return 0;
if (pim->Width < 200 && pim->Height < 200) /* Prevent a fixed overflow. */
if (pim->Width * pim->Height * Colors * pim->BitsPerComponent <= 160)
@@ -886,8 +886,10 @@ new_setup_image_filters(gx_device_psdf * pdev, psdf_binary_writer * pbw,
resolution = resolutiony;
}
- if (bpc != bpc_out)
+ if (bpc != bpc_out) {
pdev->JPEG_PassThrough = 0;
+ pdev->JPX_PassThrough = 0;
+ }
if (ncomp == 1 && pim->ColorSpace && pim->ColorSpace->type->index != gs_color_space_index_Indexed) {
/* Monochrome, gray, or mask */
@@ -906,6 +908,7 @@ new_setup_image_filters(gx_device_psdf * pdev, psdf_binary_writer * pbw,
params.Dict = pdev->params.GrayImage.Dict;
}
pdev->JPEG_PassThrough = 0;
+ pdev->JPX_PassThrough = 0;
code = setup_downsampling(pbw, &params, pim, pgs, resolution, lossless);
} else {
code = setup_image_compression(pbw, &params, pim, pgs, lossless);
@@ -919,6 +922,7 @@ new_setup_image_filters(gx_device_psdf * pdev, psdf_binary_writer * pbw,
params.Depth = (colour_conversion ? 8 : bpc_out);
if (do_downsample(&params, pim, resolution)) {
pdev->JPEG_PassThrough = 0;
+ pdev->JPX_PassThrough = 0;
code = setup_downsampling(pbw, &params, pim, pgs, resolution, lossless);
} else {
code = setup_image_compression(pbw, &params, pim, pgs, lossless);
diff --git a/devices/vector/gdevpsdp.c b/devices/vector/gdevpsdp.c
index d995dc5c1..e69b0921e 100644
--- a/devices/vector/gdevpsdp.c
+++ b/devices/vector/gdevpsdp.c
@@ -243,6 +243,7 @@ static const gs_param_item_t psdf_param_items[] = {
pi("MaxSubsetPct", gs_param_type_int, MaxSubsetPct),
pi("SubsetFonts", gs_param_type_bool, SubsetFonts),
pi("PassThroughJPEGImages", gs_param_type_bool, PassThroughJPEGImages),
+ pi("PassThroughJPXImages", gs_param_type_bool, PassThroughJPXImages),
#undef pi
gs_param_item_end
diff --git a/doc/VectorDevices.htm b/doc/VectorDevices.htm
index c457150ea..abf663681 100644
--- a/doc/VectorDevices.htm
+++ b/doc/VectorDevices.htm
@@ -416,6 +416,7 @@ Cells in the table below containing '=' mean that the value of the parameter is
<tr valign=top><td><code>UseFlateCompression</code><td><td><a href="#note_2">(2)</a><td><td>true<td><td>=<td><td>=<td><td>=<td><td>=
<tr valign=top><td><code>UsePrologue</code><td><td><a href="#note_0">(0)</a><td><td>false<td><td>=<td><td>=<td><td>=<td><td>=
<tr valign=top><td><code>PassThroughJPEGImages</code><td><td><a href="#note_15">(15)</a><td><td>true<td><td>=<td><td>=<td><td>=<td><td>=
+<tr valign=top><td><code>PassThroughJPXImages</code><td><td><a href="#note_16">(16)</a><td><td>true<td><td>=<td><td>=<td><td>=<td><td>=
</table></blockquote>
<p>
@@ -555,12 +556,22 @@ The default value of <code>CompressPages</code> is <code>false</code> for ps2wri
<a name="note_15">(note 15)</a>
When <code>true</code> image data in the source which is encoded using the DCT (JPEG) filter will not be decompressed
and then recompressed on output. This prevents the multiplication of JPEG artefacts caused by lossy compression.
-</code><code>PassThroughJPEGImages</code> currently only affects simple JPEG images. It has no effect on JPX (JPEG2000) encoded images,
+</code><code>PassThroughJPEGImages</code> currently only affects simple JPEG images. It has no effect on JPX (JPEG2000) encoded images (see below)
or masked images. In addition this parameter will be ignored if the pdfwrite device needs to modify the source data. This can happen if the image
is being downsampled, changing colour space or having transfer functions applied. Note that this parameter essentially overrides
the 'EncodeColorImages' and 'EncodeGrayImages' parameters if they are false, the image will still be written with a DCTDecode filter. NB this
feature currently only works with PostScript or PDF input, it does not work with PCL, PXL or XPS input.
+<p>
+<a name="note_16">(note 16)</a>
+When <code>true</code> image data in the source which is encoded using the JPX (JPEG 2000) filter will not be decompressed
+and then recompressed on output. This prevents the multiplication of JPEG artefacts caused by lossy compression.
+</code><code>PassThroughJPXImages</code> currently only affects simple JPX encoded images. It has no effect on JPEG encoded images (see above)
+or masked images. In addition this parameter will be ignored if the pdfwrite device needs to modify the source data. This can happen if the image
+is being downsampled, changing colour space or having transfer functions applied. Note that this parameter essentially overrides
+the 'EncodeColorImages' and 'EncodeGrayImages' parameters if they are false, the image will still be written with a JPXDecode filter. NB this
+feature currently only works with PostScript or PDF input, it does not work with PCL, PXL or XPS input.
+
<h4><a name="Color_Conversion_and_Management"></a>Color Conversion and Management</h4>
<p>
As of the 9.11 pre-release, the color management in the pdfwrite family has been substantially
diff --git a/psi/zfjpx.c b/psi/zfjpx.c
index 0428c3ae2..174eb59b9 100644
--- a/psi/zfjpx.c
+++ b/psi/zfjpx.c
@@ -33,6 +33,10 @@
#include "iname.h"
#include "gdebug.h"
+#include "igstate.h" /* For igs macro */
+#include "gxdevcli.h" /* for dev_spec_op */
+#include "gxdevsop.h" /* For spec_op enumerated types */
+
#if defined(USE_OPENJPEG_JP2)
# include "sjpx_openjpeg.h"
#else
@@ -43,6 +47,21 @@
# define ISTRCMP(ref, string) (memcmp((ref)->value.const_bytes, string, \
min(strlen(string), r_size(ref))))
+static int PS_JPXD_PassThrough(void *d, byte *Buffer, int Size)
+{
+ gx_device *dev = (gx_device *)d;
+
+ if (Buffer == NULL) {
+ if (Size == 0)
+ dev_proc(dev, dev_spec_op)(dev, gxdso_JPX_passthrough_end, NULL, 0);
+ else
+ dev_proc(dev, dev_spec_op)(dev, gxdso_JPX_passthrough_begin, NULL, 0);
+ } else {
+ dev_proc(dev, dev_spec_op)(dev, gxdso_JPX_passthrough_data, Buffer, Size);
+ }
+ return 0;
+}
+
/* <source> /JPXDecode <file> */
/* <source> <dict> /JPXDecode <file> */
static int
@@ -52,6 +71,7 @@ z_jpx_decode(i_ctx_t * i_ctx_p)
ref *sop = NULL;
ref *csname = NULL;
stream_jpxd_state state;
+ gx_device *dev = gs_currentdevice(igs);
/* it's our responsibility to call set_defaults() */
state.memory = imemory->non_gc_memory;
@@ -132,6 +152,17 @@ z_jpx_decode(i_ctx_t * i_ctx_p)
}
}
+ if (dev_proc(dev, dev_spec_op)(dev, gxdso_JPX_passthrough_query, NULL, 0) > 0) {
+ state.StartedPassThrough = 0;
+ state.PassThrough = 1;
+ state.PassThroughfn = (PS_JPXD_PassThrough);
+ state.device = (void *)dev;
+ }
+ else {
+ state.PassThrough = 0;
+ state.device = (void *)NULL;
+ }
+
/* we pass npop=0, since we've no arguments left to consume */
/* we pass 0 instead of the usual rspace(sop) which will allocate storage
for filter state from the same memory pool as the stream it's coding.