diff --git a/include/mupdf/fitz/compress.h b/include/mupdf/fitz/compress.h
index 82d9af391c..9acf1cfd72 100644
--- a/include/mupdf/fitz/compress.h
+++ b/include/mupdf/fitz/compress.h
@@ -25,6 +25,7 @@
#include "mupdf/fitz/system.h"
#include "mupdf/fitz/buffer.h"
+#include "mupdf/fitz/pixmap.h"
typedef enum
{
diff --git a/include/mupdf/fitz/image.h b/include/mupdf/fitz/image.h
index f9e7931840..e57b0f9ef4 100644
--- a/include/mupdf/fitz/image.h
+++ b/include/mupdf/fitz/image.h
@@ -421,6 +421,13 @@ void fz_set_pixmap_image_tile(fz_context *ctx, fz_pixmap_image *cimg, fz_pixmap
*/
fz_pixmap *fz_load_jpx(fz_context *ctx, const unsigned char *data, size_t size, fz_colorspace *cs);
+/**
+ Exposed because compression and decompression need to share this.
+*/
+void opj_lock(fz_context *ctx);
+void opj_unlock(fz_context *ctx);
+
+
/**
Exposed for CBZ.
*/
diff --git a/include/mupdf/fitz/write-pixmap.h b/include/mupdf/fitz/write-pixmap.h
index 7fc78c789c..10bb40f7cf 100644
--- a/include/mupdf/fitz/write-pixmap.h
+++ b/include/mupdf/fitz/write-pixmap.h
@@ -235,6 +235,22 @@ void fz_save_pixmap_as_jpeg(fz_context *ctx, fz_pixmap *pixmap, const char *file
*/
void fz_write_pixmap_as_png(fz_context *ctx, fz_output *out, const fz_pixmap *pixmap);
+/**
+ Pixmap data as JP2K with no subsampling.
+
+ quality = 100 = lossless
+ otherwise for a factor of x compression use 100-x. (so 80 is 1:20 compression)
+*/
+void fz_write_pixmap_as_jpx(fz_context *ctx, fz_output *out, fz_pixmap *pix, int quality);
+
+/**
+ Save pixmap data as JP2K with no subsampling.
+
+ quality = 100 = lossless
+ otherwise for a factor of x compression use 100-x. (so 80 is 1:20 compression)
+*/
+void fz_save_pixmap_as_jpx(fz_context *ctx, fz_pixmap *pixmap, const char *filename, int q);
+
/**
Create a new png band writer (greyscale or RGB, with or without
alpha).
@@ -251,6 +267,7 @@ fz_buffer *fz_new_buffer_from_image_as_pnm(fz_context *ctx, fz_image *image, fz_
fz_buffer *fz_new_buffer_from_image_as_pam(fz_context *ctx, fz_image *image, fz_color_params color_params);
fz_buffer *fz_new_buffer_from_image_as_psd(fz_context *ctx, fz_image *image, fz_color_params color_params);
fz_buffer *fz_new_buffer_from_image_as_jpeg(fz_context *ctx, fz_image *image, fz_color_params color_params, int quality, int invert_cmyk);
+fz_buffer *fz_new_buffer_from_image_as_jpx(fz_context *ctx, fz_image *image, fz_color_params color_params, int quality);
/**
Reencode a given pixmap as a PNG into a buffer.
@@ -262,6 +279,7 @@ fz_buffer *fz_new_buffer_from_pixmap_as_pnm(fz_context *ctx, fz_pixmap *pixmap,
fz_buffer *fz_new_buffer_from_pixmap_as_pam(fz_context *ctx, fz_pixmap *pixmap, fz_color_params color_params);
fz_buffer *fz_new_buffer_from_pixmap_as_psd(fz_context *ctx, fz_pixmap *pix, fz_color_params color_params);
fz_buffer *fz_new_buffer_from_pixmap_as_jpeg(fz_context *ctx, fz_pixmap *pixmap, fz_color_params color_params, int quality, int invert_cmyk);
+fz_buffer *fz_new_buffer_from_pixmap_as_jpx(fz_context *ctx, fz_pixmap *pix, fz_color_params color_params, int quality);
/**
Save a pixmap as a pnm (greyscale or rgb, no alpha).
diff --git a/platform/win32/libmupdf.vcxproj b/platform/win32/libmupdf.vcxproj
index 5bd249418f..8d83b0831d 100644
--- a/platform/win32/libmupdf.vcxproj
+++ b/platform/win32/libmupdf.vcxproj
@@ -1064,6 +1064,7 @@
+
diff --git a/platform/win32/libmupdf.vcxproj.filters b/platform/win32/libmupdf.vcxproj.filters
index d5a27d92c1..a68ba7ce3b 100644
--- a/platform/win32/libmupdf.vcxproj.filters
+++ b/platform/win32/libmupdf.vcxproj.filters
@@ -636,6 +636,9 @@
pdf
+
+ fitz
+
diff --git a/source/fitz/encode-jpx.c b/source/fitz/encode-jpx.c
new file mode 100644
index 0000000000..b6c79925a6
--- /dev/null
+++ b/source/fitz/encode-jpx.c
@@ -0,0 +1,345 @@
+// Copyright (C) 2004-2021 Artifex Software, Inc.
+//
+// This file is part of MuPDF.
+//
+// MuPDF is free software: you can redistribute it and/or modify it under the
+// terms of the GNU Affero General Public License as published by the Free
+// Software Foundation, either version 3 of the License, or (at your option)
+// any later version.
+//
+// MuPDF 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 Affero General Public License for more
+// details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with MuPDF. If not, see
+//
+// Alternative licensing terms are available from the licensor.
+// For commercial licensing, see or contact
+// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco,
+// CA 94129, USA, for further information.
+
+#include "mupdf/fitz.h"
+
+#include
+
+
+static opj_image_t *
+image_from_pixmap(fz_context *ctx, fz_pixmap *pix)
+{
+ opj_image_cmptparm_t cmptparm[FZ_MAX_COLORS] = { 0 };
+ OPJ_INT32 *data[FZ_MAX_COLORS];
+ int i;
+ opj_image_t *image;
+ OPJ_COLOR_SPACE cs;
+
+ if (pix->alpha || pix->s)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "No spots/alpha for JPX encode");
+
+ if (fz_colorspace_is_cmyk(ctx, pix->colorspace))
+ cs = OPJ_CLRSPC_CMYK;
+ else if (fz_colorspace_is_rgb(ctx, pix->colorspace))
+ cs = OPJ_CLRSPC_SRGB;
+ else if (fz_colorspace_is_gray(ctx, pix->colorspace))
+ cs = OPJ_CLRSPC_GRAY;
+ else
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Invalid colorspace for JPX encode");
+
+ /* Create image */
+ for (i = 0; i < pix->n; ++i)
+ {
+ cmptparm[i].prec = 8;
+ cmptparm[i].sgnd = 0;
+ cmptparm[i].dx = 1;
+ cmptparm[i].dy = 1;
+ cmptparm[i].w = (OPJ_UINT32)pix->w;
+ cmptparm[i].h = (OPJ_UINT32)pix->h;
+ }
+
+ image = opj_image_create(pix->n, &cmptparm[0], cs);
+ if (image == NULL)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "OPJ image creation failed");
+
+ image->x0 = 0;
+ image->y0 = 0;
+ image->x1 = pix->w;
+ image->y1 = pix->h;
+
+ for (i = 0; i < pix->n; ++i)
+ data[i] = image->comps[i].data;
+
+ {
+ int w = pix->w;
+ int stride = pix->stride;
+ int n = pix->n;
+ int x, y, k;
+ unsigned char *s = pix->samples;
+ for (y = pix->h; y > 0; y--)
+ {
+ unsigned char *s2 = s;
+ s += stride;
+ for (k = 0; k < n; k++)
+ {
+ unsigned char *s3 = s2++;
+ OPJ_INT32 *d = data[k];
+ data[k] += w;
+ for (x = w; x > 0; x--)
+ {
+ *d++ = (*s3);
+ s3 += n;
+ }
+ }
+ }
+ }
+
+ return image;
+}
+
+typedef struct
+{
+ fz_context *ctx; /* Safe */
+ fz_output *out;
+} my_stream;
+
+static void
+close_stm(void *user_data)
+{
+ my_stream *stm = (my_stream *)user_data;
+
+ /* Nothing to see here. */
+ fz_close_output(stm->ctx, stm->out);
+}
+
+static OPJ_SIZE_T
+write_stm(void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data)
+{
+ my_stream *stm = (my_stream *)p_user_data;
+
+ fz_try(stm->ctx)
+ fz_write_data(stm->ctx, stm->out, p_buffer, p_nb_bytes);
+ fz_catch(stm->ctx)
+ return -1;
+
+ return p_nb_bytes;
+}
+
+static OPJ_OFF_T skip_stm(OPJ_OFF_T p_nb_bytes, void *p_user_data)
+{
+ my_stream *stm = (my_stream *)p_user_data;
+
+ stm = stm;
+
+ return -1;
+}
+
+static OPJ_BOOL seek_stm(OPJ_OFF_T p_nb_bytes, void *p_user_data)
+{
+ my_stream *stm = (my_stream *)p_user_data;
+
+ stm = stm;
+
+ return 0;
+}
+
+static void
+info_callback(const char *msg, void *client_data)
+{
+ fz_context *ctx = (fz_context *)client_data;
+
+ fz_warn(ctx, "INFO: %s", msg);
+}
+
+static void
+warning_callback(const char *msg, void *client_data)
+{
+ fz_context *ctx = (fz_context *)client_data;
+
+ fz_warn(ctx, "WARNING: %s", msg);
+}
+
+static void
+error_callback(const char *msg, void *client_data)
+{
+ fz_context *ctx = (fz_context *)client_data;
+
+ fz_warn(ctx, "ERROR: %s", msg);
+}
+
+void
+fz_write_pixmap_as_jpx(fz_context *ctx, fz_output *out, fz_pixmap *pix, int q)
+{
+ opj_cparameters_t parameters; /* compression parameters */
+
+ opj_stream_t *l_stream = 00;
+ opj_codec_t* l_codec = 00;
+ opj_image_t *image = NULL;
+ OPJ_BOOL bSuccess;
+
+ my_stream stm;
+
+ fz_var(image);
+
+ opj_lock(ctx);
+ fz_try(ctx)
+ {
+ image = image_from_pixmap(ctx, pix);
+ stm.ctx = ctx;
+ stm.out = out;
+
+ /* set encoding parameters to default values */
+ opj_set_default_encoder_parameters(¶meters);
+
+ /* Decide if MCT should be used */
+ /* mct = 1 -> rgb data should be converted to ycc */
+ parameters.tcp_mct = (pix->n >= 3) ? 1 : 0;
+
+ parameters.irreversible = 1;
+
+ /* JPEG-2000 codestream */
+ l_codec = opj_create_compress(OPJ_CODEC_J2K);
+ /* Use OPJ_CODEC_JP2 for JPEG 2000 compressed image data, but that requires seeking. */
+
+ /* catch events using our callbacks and give a local context */
+ opj_set_info_handler(l_codec, info_callback, ctx);
+ opj_set_warning_handler(l_codec, warning_callback, ctx);
+ opj_set_error_handler(l_codec, error_callback, ctx);
+
+ if (q == 100)
+ {
+ /* Lossless compression */
+ }
+ else
+ {
+ /* 20:1 compression is reasonable */
+ parameters.tcp_numlayers = 1;
+ parameters.tcp_rates[0] = (100-q);
+ parameters.cp_disto_alloc = 1;
+ }
+
+ /* The compressor will refuse to encode if the following condition
+ * is true. We work around this by enabling tiling for such images.
+ * An alternative would be to reduce numresolution. Not sure which
+ * is better. */
+ if (fz_mini(pix->w, pix->h) < 1<<(parameters.numresolution-1))
+ {
+ parameters.cp_tx0 = 0;
+ parameters.cp_ty0 = 0;
+ parameters.tile_size_on = OPJ_TRUE;
+ parameters.cp_tdx = 1<<(parameters.numresolution-1);
+ parameters.cp_tdy = 1<<(parameters.numresolution-1);
+ }
+
+ if (! opj_setup_encoder(l_codec, ¶meters, image))
+ {
+ opj_destroy_codec(l_codec);
+ opj_image_destroy(image);
+ fz_throw(ctx, FZ_ERROR_GENERIC, "OpenJPEG encoder setup failed");
+ }
+
+ /* open a byte stream for writing and allocate memory for all tiles */
+ l_stream = opj_stream_create(OPJ_J2K_STREAM_CHUNK_SIZE, OPJ_FALSE);
+ if (!l_stream)
+ {
+ opj_destroy_codec(l_codec);
+ opj_image_destroy(image);
+ fz_throw(ctx, FZ_ERROR_GENERIC, "OpenJPEG encoder setup failed (stream creation)");
+ }
+
+ opj_stream_set_user_data(l_stream, &stm, close_stm);
+ opj_stream_set_user_data_length(l_stream, 0);
+ //opj_stream_set_read_function(l_stream, opj_read_from_file);
+ opj_stream_set_write_function(l_stream, write_stm);
+ opj_stream_set_skip_function(l_stream, skip_stm);
+ opj_stream_set_seek_function(l_stream, seek_stm);
+
+ /* encode the image */
+ bSuccess = opj_start_compress(l_codec, image, l_stream);
+ if (!bSuccess)
+ {
+ opj_destroy_codec(l_codec);
+ opj_image_destroy(image);
+ fz_throw(ctx, FZ_ERROR_GENERIC, "OpenJPEG encode failed");
+ }
+
+ bSuccess = bSuccess && opj_encode(l_codec, l_stream);
+ bSuccess = bSuccess && opj_end_compress(l_codec, l_stream);
+
+ opj_stream_destroy(l_stream);
+
+ /* free remaining compression structures */
+ opj_destroy_codec(l_codec);
+
+ /* free image data */
+ opj_image_destroy(image);
+
+ if (!bSuccess)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Encoding failed");
+ }
+ fz_always(ctx)
+ opj_unlock(ctx);
+ fz_catch(ctx)
+ fz_rethrow(ctx);
+}
+
+void
+fz_save_pixmap_as_jpx(fz_context *ctx, fz_pixmap *pixmap, const char *filename, int q)
+{
+ fz_output *out = fz_new_output_with_path(ctx, filename, 0);
+ fz_try(ctx)
+ {
+ fz_write_pixmap_as_jpx(ctx, out, pixmap, q);
+ fz_close_output(ctx, out);
+ }
+ fz_always(ctx)
+ {
+ fz_drop_output(ctx, out);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+static fz_buffer *
+jpx_from_pixmap(fz_context *ctx, fz_pixmap *pix, fz_color_params color_params, int quality, int drop)
+{
+ fz_buffer *buf = NULL;
+ fz_output *out = NULL;
+
+ fz_var(buf);
+ fz_var(out);
+
+ fz_try(ctx)
+ {
+ buf = fz_new_buffer(ctx, 1024);
+ out = fz_new_output_with_buffer(ctx, buf);
+ fz_write_pixmap_as_jpx(ctx, out, pix, quality);
+ fz_close_output(ctx, out);
+ }
+ fz_always(ctx)
+ {
+ if (drop)
+ fz_drop_pixmap(ctx, pix);
+ fz_drop_output(ctx, out);
+ }
+ fz_catch(ctx)
+ {
+ fz_drop_buffer(ctx, buf);
+ fz_rethrow(ctx);
+ }
+ return buf;
+}
+
+fz_buffer *
+fz_new_buffer_from_image_as_jpx(fz_context *ctx, fz_image *image, fz_color_params color_params, int quality)
+{
+ fz_pixmap *pix = fz_get_pixmap_from_image(ctx, image, NULL, NULL, NULL, NULL);
+ return jpx_from_pixmap(ctx, pix, color_params, quality, 1);
+}
+
+fz_buffer *
+fz_new_buffer_from_pixmap_as_jpx(fz_context *ctx, fz_pixmap *pix, fz_color_params color_params, int quality)
+{
+ return jpx_from_pixmap(ctx, pix, color_params, quality, 0);
+}
diff --git a/source/pdf/pdf-image-rewriter.c b/source/pdf/pdf-image-rewriter.c
index b625a955fc..66d2f29c9d 100644
--- a/source/pdf/pdf-image-rewriter.c
+++ b/source/pdf/pdf-image-rewriter.c
@@ -489,7 +489,37 @@ fz_recompress_image_as_jpeg(fz_context *ctx, fz_pixmap *pix, const char *quality
static fz_compressed_buffer *
fz_recompress_image_as_j2k(fz_context *ctx, fz_pixmap *pix, const char *quality)
{
- return NULL;
+ fz_compressed_buffer *cbuf = fz_new_compressed_buffer(ctx);
+ fz_output *out = NULL;
+ int q = fz_atoi(quality);
+
+ if (q <= 0)
+ q = 80; /* Default 1:20 compression */
+ if (q > 100)
+ q = 100;
+
+ fz_var(out);
+
+ fz_try(ctx)
+ {
+ cbuf->buffer = fz_new_buffer(ctx, 1024);
+ out = fz_new_output_with_buffer(ctx, cbuf->buffer);
+
+ fz_write_pixmap_as_jpx(ctx, out, pix, q);
+ cbuf->params.type = FZ_IMAGE_JPX;
+ cbuf->params.u.jpx.smask_in_data = 0;
+ }
+ fz_always(ctx)
+ {
+ fz_drop_output(ctx, out);
+ }
+ fz_catch(ctx)
+ {
+ fz_drop_compressed_buffer(ctx, cbuf);
+ fz_rethrow(ctx);
+ }
+
+ return cbuf;
}
static fz_compressed_buffer *