| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377 |
- // Copyright (C) 2004-2025 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 <https://www.gnu.org/licenses/agpl-3.0.en.html>
- //
- // Alternative licensing terms are available from the licensor.
- // For commercial licensing, see <https://www.artifex.com/> or contact
- // Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco,
- // CA 94129, USA, for further information.
- #include "mupdf/fitz.h"
- #include <openjpeg.h>
- #if FZ_ENABLE_JPX
- 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_ARGUMENT, "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_ARGUMENT, "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_LIBRARY, "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 (OPJ_SIZE_T)-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;
- (void)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;
- (void)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);
- /* We encode using tiles. */
- parameters.cp_tx0 = 0;
- parameters.cp_ty0 = 0;
- parameters.tile_size_on = OPJ_TRUE;
- parameters.cp_tdx = 256;
- parameters.cp_tdy = 256;
- /* Shrink the tile so it's not more than twice the width/height of the image. */
- while (parameters.cp_tdx>>1 >= pix->w)
- parameters.cp_tdx >>= 1;
- while (parameters.cp_tdy>>1 >= pix->h)
- parameters.cp_tdy >>= 1;
- /* The tile size must not be smaller than that given by numresolution. */
- if (parameters.cp_tdx < 1<<(parameters.numresolution-1))
- parameters.cp_tdx = 1<<(parameters.numresolution-1);
- if (parameters.cp_tdy < 1<<(parameters.numresolution-1))
- parameters.cp_tdy = 1<<(parameters.numresolution-1);
- /* FIXME: Calculate layers here? */
- /* My understanding of the suggestion that I've been given, is that we should pick
- * layers to be the largest integer, such that (1<<layers) * tile_size >= w */
- {
- int layers = 0;
- while (pix->w>>(layers+1) >= parameters.cp_tdx &&
- pix->h>>(layers+1) >= parameters.cp_tdy)
- layers++;
- /* But putting layers into parameters.tcp_numlayers causes a crash... */
- }
- if (q == 100)
- {
- /* Lossless compression requested! */
- }
- else if (pix->w < 2*parameters.cp_tdx && pix->h < 2*parameters.cp_tdy)
- {
- /* We only compress lossily if the image is larger than the tilesize, otherwise work losslessly. */
- }
- else
- {
- /* 20:1 compression is reasonable */
- parameters.tcp_numlayers = 1;
- parameters.tcp_rates[0] = (100-q);
- parameters.cp_disto_alloc = 1;
- }
- if (! opj_setup_encoder(l_codec, ¶meters, image))
- {
- opj_destroy_codec(l_codec);
- opj_image_destroy(image);
- fz_throw(ctx, FZ_ERROR_LIBRARY, "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_LIBRARY, "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_LIBRARY, "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_LIBRARY, "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);
- }
- #else
- void
- fz_write_pixmap_as_jpx(fz_context *ctx, fz_output *out, fz_pixmap *pix, int q)
- {
- fz_throw(ctx, FZ_ERROR_UNSUPPORTED, "JPX support disabled");
- }
- #endif
|