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(&parameters);
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, &parameters, 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