Mercurial > hgrepos > Python2 > PyMuPDF
comparison mupdf-source/source/fitz/encode-jpx.c @ 2:b50eed0cc0ef upstream
ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4.
The directory name has changed: no version number in the expanded directory now.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:43:07 +0200 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 1:1d09e1dec1d9 | 2:b50eed0cc0ef |
|---|---|
| 1 // Copyright (C) 2004-2025 Artifex Software, Inc. | |
| 2 // | |
| 3 // This file is part of MuPDF. | |
| 4 // | |
| 5 // MuPDF is free software: you can redistribute it and/or modify it under the | |
| 6 // terms of the GNU Affero General Public License as published by the Free | |
| 7 // Software Foundation, either version 3 of the License, or (at your option) | |
| 8 // any later version. | |
| 9 // | |
| 10 // MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY | |
| 11 // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
| 12 // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more | |
| 13 // details. | |
| 14 // | |
| 15 // You should have received a copy of the GNU Affero General Public License | |
| 16 // along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html> | |
| 17 // | |
| 18 // Alternative licensing terms are available from the licensor. | |
| 19 // For commercial licensing, see <https://www.artifex.com/> or contact | |
| 20 // Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, | |
| 21 // CA 94129, USA, for further information. | |
| 22 | |
| 23 #include "mupdf/fitz.h" | |
| 24 | |
| 25 #include <openjpeg.h> | |
| 26 | |
| 27 #if FZ_ENABLE_JPX | |
| 28 | |
| 29 static opj_image_t * | |
| 30 image_from_pixmap(fz_context *ctx, fz_pixmap *pix) | |
| 31 { | |
| 32 opj_image_cmptparm_t cmptparm[FZ_MAX_COLORS] = { 0 }; | |
| 33 OPJ_INT32 *data[FZ_MAX_COLORS]; | |
| 34 int i; | |
| 35 opj_image_t *image; | |
| 36 OPJ_COLOR_SPACE cs; | |
| 37 | |
| 38 if (pix->alpha || pix->s) | |
| 39 fz_throw(ctx, FZ_ERROR_ARGUMENT, "No spots/alpha for JPX encode"); | |
| 40 | |
| 41 if (fz_colorspace_is_cmyk(ctx, pix->colorspace)) | |
| 42 cs = OPJ_CLRSPC_CMYK; | |
| 43 else if (fz_colorspace_is_rgb(ctx, pix->colorspace)) | |
| 44 cs = OPJ_CLRSPC_SRGB; | |
| 45 else if (fz_colorspace_is_gray(ctx, pix->colorspace)) | |
| 46 cs = OPJ_CLRSPC_GRAY; | |
| 47 else | |
| 48 fz_throw(ctx, FZ_ERROR_ARGUMENT, "Invalid colorspace for JPX encode"); | |
| 49 | |
| 50 /* Create image */ | |
| 51 for (i = 0; i < pix->n; ++i) | |
| 52 { | |
| 53 cmptparm[i].prec = 8; | |
| 54 cmptparm[i].sgnd = 0; | |
| 55 cmptparm[i].dx = 1; | |
| 56 cmptparm[i].dy = 1; | |
| 57 cmptparm[i].w = (OPJ_UINT32)pix->w; | |
| 58 cmptparm[i].h = (OPJ_UINT32)pix->h; | |
| 59 } | |
| 60 | |
| 61 image = opj_image_create(pix->n, &cmptparm[0], cs); | |
| 62 if (image == NULL) | |
| 63 fz_throw(ctx, FZ_ERROR_LIBRARY, "OPJ image creation failed"); | |
| 64 | |
| 65 image->x0 = 0; | |
| 66 image->y0 = 0; | |
| 67 image->x1 = pix->w; | |
| 68 image->y1 = pix->h; | |
| 69 | |
| 70 for (i = 0; i < pix->n; ++i) | |
| 71 data[i] = image->comps[i].data; | |
| 72 | |
| 73 { | |
| 74 int w = pix->w; | |
| 75 int stride = pix->stride; | |
| 76 int n = pix->n; | |
| 77 int x, y, k; | |
| 78 unsigned char *s = pix->samples; | |
| 79 for (y = pix->h; y > 0; y--) | |
| 80 { | |
| 81 unsigned char *s2 = s; | |
| 82 s += stride; | |
| 83 for (k = 0; k < n; k++) | |
| 84 { | |
| 85 unsigned char *s3 = s2++; | |
| 86 OPJ_INT32 *d = data[k]; | |
| 87 data[k] += w; | |
| 88 for (x = w; x > 0; x--) | |
| 89 { | |
| 90 *d++ = (*s3); | |
| 91 s3 += n; | |
| 92 } | |
| 93 } | |
| 94 } | |
| 95 } | |
| 96 | |
| 97 return image; | |
| 98 } | |
| 99 | |
| 100 typedef struct | |
| 101 { | |
| 102 fz_context *ctx; /* Safe */ | |
| 103 fz_output *out; | |
| 104 } my_stream; | |
| 105 | |
| 106 static void | |
| 107 close_stm(void *user_data) | |
| 108 { | |
| 109 my_stream *stm = (my_stream *)user_data; | |
| 110 | |
| 111 /* Nothing to see here. */ | |
| 112 fz_close_output(stm->ctx, stm->out); | |
| 113 } | |
| 114 | |
| 115 static OPJ_SIZE_T | |
| 116 write_stm(void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data) | |
| 117 { | |
| 118 my_stream *stm = (my_stream *)p_user_data; | |
| 119 | |
| 120 fz_try(stm->ctx) | |
| 121 fz_write_data(stm->ctx, stm->out, p_buffer, p_nb_bytes); | |
| 122 fz_catch(stm->ctx) | |
| 123 return (OPJ_SIZE_T)-1; | |
| 124 | |
| 125 return p_nb_bytes; | |
| 126 } | |
| 127 | |
| 128 static OPJ_OFF_T skip_stm(OPJ_OFF_T p_nb_bytes, void *p_user_data) | |
| 129 { | |
| 130 my_stream *stm = (my_stream *)p_user_data; | |
| 131 | |
| 132 (void)stm; | |
| 133 | |
| 134 return -1; | |
| 135 } | |
| 136 | |
| 137 static OPJ_BOOL seek_stm(OPJ_OFF_T p_nb_bytes, void *p_user_data) | |
| 138 { | |
| 139 my_stream *stm = (my_stream *)p_user_data; | |
| 140 | |
| 141 (void)stm; | |
| 142 | |
| 143 return 0; | |
| 144 } | |
| 145 | |
| 146 static void | |
| 147 info_callback(const char *msg, void *client_data) | |
| 148 { | |
| 149 fz_context *ctx = (fz_context *)client_data; | |
| 150 | |
| 151 fz_warn(ctx, "INFO: %s", msg); | |
| 152 } | |
| 153 | |
| 154 static void | |
| 155 warning_callback(const char *msg, void *client_data) | |
| 156 { | |
| 157 fz_context *ctx = (fz_context *)client_data; | |
| 158 | |
| 159 fz_warn(ctx, "WARNING: %s", msg); | |
| 160 } | |
| 161 | |
| 162 static void | |
| 163 error_callback(const char *msg, void *client_data) | |
| 164 { | |
| 165 fz_context *ctx = (fz_context *)client_data; | |
| 166 | |
| 167 fz_warn(ctx, "ERROR: %s", msg); | |
| 168 } | |
| 169 | |
| 170 void | |
| 171 fz_write_pixmap_as_jpx(fz_context *ctx, fz_output *out, fz_pixmap *pix, int q) | |
| 172 { | |
| 173 opj_cparameters_t parameters; /* compression parameters */ | |
| 174 | |
| 175 opj_stream_t *l_stream = 00; | |
| 176 opj_codec_t* l_codec = 00; | |
| 177 opj_image_t *image = NULL; | |
| 178 OPJ_BOOL bSuccess; | |
| 179 | |
| 180 my_stream stm; | |
| 181 | |
| 182 fz_var(image); | |
| 183 | |
| 184 opj_lock(ctx); | |
| 185 fz_try(ctx) | |
| 186 { | |
| 187 image = image_from_pixmap(ctx, pix); | |
| 188 stm.ctx = ctx; | |
| 189 stm.out = out; | |
| 190 | |
| 191 /* set encoding parameters to default values */ | |
| 192 opj_set_default_encoder_parameters(¶meters); | |
| 193 | |
| 194 /* Decide if MCT should be used */ | |
| 195 /* mct = 1 -> rgb data should be converted to ycc */ | |
| 196 parameters.tcp_mct = (pix->n >= 3) ? 1 : 0; | |
| 197 | |
| 198 parameters.irreversible = 1; | |
| 199 | |
| 200 /* JPEG-2000 codestream */ | |
| 201 l_codec = opj_create_compress(OPJ_CODEC_J2K); | |
| 202 /* Use OPJ_CODEC_JP2 for JPEG 2000 compressed image data, but that requires seeking. */ | |
| 203 | |
| 204 /* catch events using our callbacks and give a local context */ | |
| 205 opj_set_info_handler(l_codec, info_callback, ctx); | |
| 206 opj_set_warning_handler(l_codec, warning_callback, ctx); | |
| 207 opj_set_error_handler(l_codec, error_callback, ctx); | |
| 208 | |
| 209 /* We encode using tiles. */ | |
| 210 parameters.cp_tx0 = 0; | |
| 211 parameters.cp_ty0 = 0; | |
| 212 parameters.tile_size_on = OPJ_TRUE; | |
| 213 parameters.cp_tdx = 256; | |
| 214 parameters.cp_tdy = 256; | |
| 215 | |
| 216 /* Shrink the tile so it's not more than twice the width/height of the image. */ | |
| 217 while (parameters.cp_tdx>>1 >= pix->w) | |
| 218 parameters.cp_tdx >>= 1; | |
| 219 while (parameters.cp_tdy>>1 >= pix->h) | |
| 220 parameters.cp_tdy >>= 1; | |
| 221 | |
| 222 /* The tile size must not be smaller than that given by numresolution. */ | |
| 223 if (parameters.cp_tdx < 1<<(parameters.numresolution-1)) | |
| 224 parameters.cp_tdx = 1<<(parameters.numresolution-1); | |
| 225 if (parameters.cp_tdy < 1<<(parameters.numresolution-1)) | |
| 226 parameters.cp_tdy = 1<<(parameters.numresolution-1); | |
| 227 | |
| 228 /* FIXME: Calculate layers here? */ | |
| 229 /* My understanding of the suggestion that I've been given, is that we should pick | |
| 230 * layers to be the largest integer, such that (1<<layers) * tile_size >= w */ | |
| 231 { | |
| 232 int layers = 0; | |
| 233 while (pix->w>>(layers+1) >= parameters.cp_tdx && | |
| 234 pix->h>>(layers+1) >= parameters.cp_tdy) | |
| 235 layers++; | |
| 236 /* But putting layers into parameters.tcp_numlayers causes a crash... */ | |
| 237 } | |
| 238 | |
| 239 if (q == 100) | |
| 240 { | |
| 241 /* Lossless compression requested! */ | |
| 242 } | |
| 243 else if (pix->w < 2*parameters.cp_tdx && pix->h < 2*parameters.cp_tdy) | |
| 244 { | |
| 245 /* We only compress lossily if the image is larger than the tilesize, otherwise work losslessly. */ | |
| 246 } | |
| 247 else | |
| 248 { | |
| 249 /* 20:1 compression is reasonable */ | |
| 250 parameters.tcp_numlayers = 1; | |
| 251 parameters.tcp_rates[0] = (100-q); | |
| 252 parameters.cp_disto_alloc = 1; | |
| 253 } | |
| 254 | |
| 255 if (! opj_setup_encoder(l_codec, ¶meters, image)) | |
| 256 { | |
| 257 opj_destroy_codec(l_codec); | |
| 258 opj_image_destroy(image); | |
| 259 fz_throw(ctx, FZ_ERROR_LIBRARY, "OpenJPEG encoder setup failed"); | |
| 260 } | |
| 261 | |
| 262 /* open a byte stream for writing and allocate memory for all tiles */ | |
| 263 l_stream = opj_stream_create(OPJ_J2K_STREAM_CHUNK_SIZE, OPJ_FALSE); | |
| 264 if (!l_stream) | |
| 265 { | |
| 266 opj_destroy_codec(l_codec); | |
| 267 opj_image_destroy(image); | |
| 268 fz_throw(ctx, FZ_ERROR_LIBRARY, "OpenJPEG encoder setup failed (stream creation)"); | |
| 269 } | |
| 270 | |
| 271 opj_stream_set_user_data(l_stream, &stm, close_stm); | |
| 272 opj_stream_set_user_data_length(l_stream, 0); | |
| 273 //opj_stream_set_read_function(l_stream, opj_read_from_file); | |
| 274 opj_stream_set_write_function(l_stream, write_stm); | |
| 275 opj_stream_set_skip_function(l_stream, skip_stm); | |
| 276 opj_stream_set_seek_function(l_stream, seek_stm); | |
| 277 | |
| 278 /* encode the image */ | |
| 279 bSuccess = opj_start_compress(l_codec, image, l_stream); | |
| 280 if (!bSuccess) | |
| 281 { | |
| 282 opj_destroy_codec(l_codec); | |
| 283 opj_image_destroy(image); | |
| 284 fz_throw(ctx, FZ_ERROR_LIBRARY, "OpenJPEG encode failed"); | |
| 285 } | |
| 286 | |
| 287 bSuccess = bSuccess && opj_encode(l_codec, l_stream); | |
| 288 bSuccess = bSuccess && opj_end_compress(l_codec, l_stream); | |
| 289 | |
| 290 opj_stream_destroy(l_stream); | |
| 291 | |
| 292 /* free remaining compression structures */ | |
| 293 opj_destroy_codec(l_codec); | |
| 294 | |
| 295 /* free image data */ | |
| 296 opj_image_destroy(image); | |
| 297 | |
| 298 if (!bSuccess) | |
| 299 fz_throw(ctx, FZ_ERROR_LIBRARY, "Encoding failed"); | |
| 300 } | |
| 301 fz_always(ctx) | |
| 302 opj_unlock(ctx); | |
| 303 fz_catch(ctx) | |
| 304 fz_rethrow(ctx); | |
| 305 } | |
| 306 | |
| 307 void | |
| 308 fz_save_pixmap_as_jpx(fz_context *ctx, fz_pixmap *pixmap, const char *filename, int q) | |
| 309 { | |
| 310 fz_output *out = fz_new_output_with_path(ctx, filename, 0); | |
| 311 fz_try(ctx) | |
| 312 { | |
| 313 fz_write_pixmap_as_jpx(ctx, out, pixmap, q); | |
| 314 fz_close_output(ctx, out); | |
| 315 } | |
| 316 fz_always(ctx) | |
| 317 { | |
| 318 fz_drop_output(ctx, out); | |
| 319 } | |
| 320 fz_catch(ctx) | |
| 321 { | |
| 322 fz_rethrow(ctx); | |
| 323 } | |
| 324 } | |
| 325 | |
| 326 static fz_buffer * | |
| 327 jpx_from_pixmap(fz_context *ctx, fz_pixmap *pix, fz_color_params color_params, int quality, int drop) | |
| 328 { | |
| 329 fz_buffer *buf = NULL; | |
| 330 fz_output *out = NULL; | |
| 331 | |
| 332 fz_var(buf); | |
| 333 fz_var(out); | |
| 334 | |
| 335 fz_try(ctx) | |
| 336 { | |
| 337 buf = fz_new_buffer(ctx, 1024); | |
| 338 out = fz_new_output_with_buffer(ctx, buf); | |
| 339 fz_write_pixmap_as_jpx(ctx, out, pix, quality); | |
| 340 fz_close_output(ctx, out); | |
| 341 } | |
| 342 fz_always(ctx) | |
| 343 { | |
| 344 if (drop) | |
| 345 fz_drop_pixmap(ctx, pix); | |
| 346 fz_drop_output(ctx, out); | |
| 347 } | |
| 348 fz_catch(ctx) | |
| 349 { | |
| 350 fz_drop_buffer(ctx, buf); | |
| 351 fz_rethrow(ctx); | |
| 352 } | |
| 353 return buf; | |
| 354 } | |
| 355 | |
| 356 fz_buffer * | |
| 357 fz_new_buffer_from_image_as_jpx(fz_context *ctx, fz_image *image, fz_color_params color_params, int quality) | |
| 358 { | |
| 359 fz_pixmap *pix = fz_get_pixmap_from_image(ctx, image, NULL, NULL, NULL, NULL); | |
| 360 return jpx_from_pixmap(ctx, pix, color_params, quality, 1); | |
| 361 } | |
| 362 | |
| 363 fz_buffer * | |
| 364 fz_new_buffer_from_pixmap_as_jpx(fz_context *ctx, fz_pixmap *pix, fz_color_params color_params, int quality) | |
| 365 { | |
| 366 return jpx_from_pixmap(ctx, pix, color_params, quality, 0); | |
| 367 } | |
| 368 | |
| 369 #else | |
| 370 | |
| 371 void | |
| 372 fz_write_pixmap_as_jpx(fz_context *ctx, fz_output *out, fz_pixmap *pix, int q) | |
| 373 { | |
| 374 fz_throw(ctx, FZ_ERROR_UNSUPPORTED, "JPX support disabled"); | |
| 375 } | |
| 376 | |
| 377 #endif |
