comparison src_classic/helper-pixmap.i @ 1:1d09e1dec1d9 upstream

ADD: PyMuPDF v1.26.4: the original sdist. It does not yet contain MuPDF. This normally will be downloaded when building PyMuPDF.
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:37:51 +0200
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 1:1d09e1dec1d9
1 %{
2 /*
3 # ------------------------------------------------------------------------
4 # Copyright 2020-2022, Harald Lieder, mailto:harald.lieder@outlook.com
5 # License: GNU AFFERO GPL 3.0, https://www.gnu.org/licenses/agpl-3.0.html
6 #
7 # Part of "PyMuPDF", a Python binding for "MuPDF" (http://mupdf.com), a
8 # lightweight PDF, XPS, and E-book viewer, renderer and toolkit which is
9 # maintained and developed by Artifex Software, Inc. https://artifex.com.
10 # ------------------------------------------------------------------------
11 */
12 //-----------------------------------------------------------------------------
13 // pixmap helper functions
14 //-----------------------------------------------------------------------------
15
16 //-----------------------------------------------------------------------------
17 // Clear a pixmap rectangle - my version also supports non-alpha pixmaps
18 //-----------------------------------------------------------------------------
19 int
20 JM_clear_pixmap_rect_with_value(fz_context *ctx, fz_pixmap *dest, int value, fz_irect b)
21 {
22 unsigned char *destp;
23 int x, y, w, k, destspan;
24
25 b = fz_intersect_irect(b, fz_pixmap_bbox(ctx, dest));
26 w = b.x1 - b.x0;
27 y = b.y1 - b.y0;
28 if (w <= 0 || y <= 0)
29 return 0;
30
31 destspan = dest->stride;
32 destp = dest->samples + (unsigned int)(destspan * (b.y0 - dest->y) + dest->n * (b.x0 - dest->x));
33
34 /* CMYK needs special handling (and potentially any other subtractive colorspaces) */
35 if (fz_colorspace_n(ctx, dest->colorspace) == 4) {
36 value = 255 - value;
37 do {
38 unsigned char *s = destp;
39 for (x = 0; x < w; x++) {
40 *s++ = 0;
41 *s++ = 0;
42 *s++ = 0;
43 *s++ = value;
44 if (dest->alpha) *s++ = 255;
45 }
46 destp += destspan;
47 } while (--y);
48 return 1;
49 }
50
51 do {
52 unsigned char *s = destp;
53 for (x = 0; x < w; x++) {
54 for (k = 0; k < dest->n - 1; k++)
55 *s++ = value;
56 if (dest->alpha) *s++ = 255;
57 else *s++ = value;
58 }
59 destp += destspan;
60 } while (--y);
61 return 1;
62 }
63
64 //-----------------------------------------------------------------------------
65 // fill a rect with a color tuple
66 //-----------------------------------------------------------------------------
67 int
68 JM_fill_pixmap_rect_with_color(fz_context *ctx, fz_pixmap *dest, unsigned char col[5], fz_irect b)
69 {
70 unsigned char *destp;
71 int x, y, w, i, destspan;
72
73 b = fz_intersect_irect(b, fz_pixmap_bbox(ctx, dest));
74 w = b.x1 - b.x0;
75 y = b.y1 - b.y0;
76 if (w <= 0 || y <= 0)
77 return 0;
78
79 destspan = dest->stride;
80 destp = dest->samples + (unsigned int)(destspan * (b.y0 - dest->y) + dest->n * (b.x0 - dest->x));
81
82 do {
83 unsigned char *s = destp;
84 for (x = 0; x < w; x++) {
85 for (i = 0; i < dest->n; i++)
86 *s++ = col[i];
87 }
88 destp += destspan;
89 } while (--y);
90 return 1;
91 }
92
93 //-----------------------------------------------------------------------------
94 // invert a rectangle - also supports non-alpha pixmaps
95 //-----------------------------------------------------------------------------
96 int
97 JM_invert_pixmap_rect(fz_context *ctx, fz_pixmap *dest, fz_irect b)
98 {
99 unsigned char *destp;
100 int x, y, w, i, destspan;
101
102 b = fz_intersect_irect(b, fz_pixmap_bbox(ctx, dest));
103 w = b.x1 - b.x0;
104 y = b.y1 - b.y0;
105 if (w <= 0 || y <= 0)
106 return 0;
107
108 destspan = dest->stride;
109 destp = dest->samples + (unsigned int)(destspan * (b.y0 - dest->y) + dest->n * (b.x0 - dest->x));
110 int n0 = dest->n - dest->alpha;
111 do {
112 unsigned char *s = destp;
113 for (x = 0; x < w; x++) {
114 for (i = 0; i < n0; i++) {
115 *s = 255 - *s;
116 s++;
117 }
118 if (dest->alpha) s++;
119 }
120 destp += destspan;
121 } while (--y);
122 return 1;
123 }
124
125 int
126 JM_is_jbig2_image(fz_context *ctx, pdf_obj *dict)
127 {
128 // fixme: should we remove this function?
129 return 0;
130 /*
131 pdf_obj *filter;
132 int i, n;
133
134 filter = pdf_dict_get(ctx, dict, PDF_NAME(Filter));
135 if (pdf_name_eq(ctx, filter, PDF_NAME(JBIG2Decode)))
136 return 1;
137 n = pdf_array_len(ctx, filter);
138 for (i = 0; i < n; i++)
139 if (pdf_name_eq(ctx, pdf_array_get(ctx, filter, i), PDF_NAME(JBIG2Decode)))
140 return 1;
141 return 0;
142 */
143 }
144
145 //-----------------------------------------------------------------------------
146 // Return basic properties of an image provided as bytes or bytearray
147 // The function creates an fz_image and optionally returns it.
148 //-----------------------------------------------------------------------------
149 PyObject *JM_image_profile(fz_context *ctx, PyObject *imagedata, int keep_image)
150 {
151 if (!EXISTS(imagedata)) {
152 Py_RETURN_NONE; // nothing given
153 }
154 fz_image *image = NULL;
155 fz_buffer *res = NULL;
156 PyObject *result = NULL;
157 unsigned char *c = NULL;
158 Py_ssize_t len = 0;
159 if (PyBytes_Check(imagedata)) {
160 c = PyBytes_AS_STRING(imagedata);
161 len = PyBytes_GET_SIZE(imagedata);
162 } else if (PyByteArray_Check(imagedata)) {
163 c = PyByteArray_AS_STRING(imagedata);
164 len = PyByteArray_GET_SIZE(imagedata);
165 } else {
166 PySys_WriteStderr("bad image data\n");
167 Py_RETURN_NONE;
168 }
169
170 if (len < 8) {
171 PySys_WriteStderr("bad image data\n");
172 Py_RETURN_NONE;
173 }
174 int type = fz_recognize_image_format(ctx, c);
175 if (type == FZ_IMAGE_UNKNOWN) {
176 Py_RETURN_NONE;
177 }
178
179 fz_try(ctx) {
180 if (keep_image) {
181 res = fz_new_buffer_from_copied_data(ctx, c, (size_t) len);
182 } else {
183 res = fz_new_buffer_from_shared_data(ctx, c, (size_t) len);
184 }
185 image = fz_new_image_from_buffer(ctx, res);
186 int xres, yres, orientation;
187 fz_matrix ctm = fz_image_orientation_matrix(ctx, image);
188 fz_image_resolution(image, &xres, &yres);
189 orientation = (int) fz_image_orientation(ctx, image);
190 const char *cs_name = fz_colorspace_name(ctx, image->colorspace);
191 result = PyDict_New();
192 DICT_SETITEM_DROP(result, dictkey_width,
193 Py_BuildValue("i", image->w));
194 DICT_SETITEM_DROP(result, dictkey_height,
195 Py_BuildValue("i", image->h));
196 DICT_SETITEMSTR_DROP(result, "orientation",
197 Py_BuildValue("i", orientation));
198 DICT_SETITEM_DROP(result, dictkey_matrix,
199 JM_py_from_matrix(ctm));
200 DICT_SETITEM_DROP(result, dictkey_xres,
201 Py_BuildValue("i", xres));
202 DICT_SETITEM_DROP(result, dictkey_yres,
203 Py_BuildValue("i", yres));
204 DICT_SETITEM_DROP(result, dictkey_colorspace,
205 Py_BuildValue("i", image->n));
206 DICT_SETITEM_DROP(result, dictkey_bpc,
207 Py_BuildValue("i", image->bpc));
208 DICT_SETITEM_DROP(result, dictkey_ext,
209 Py_BuildValue("s", JM_image_extension(type)));
210 DICT_SETITEM_DROP(result, dictkey_cs_name,
211 Py_BuildValue("s", cs_name));
212
213 if (keep_image) {
214 DICT_SETITEM_DROP(result, dictkey_image,
215 PyLong_FromVoidPtr((void *) fz_keep_image(ctx, image)));
216 }
217 }
218 fz_always(ctx) {
219 if (!keep_image) {
220 fz_drop_image(ctx, image);
221 } else {
222 fz_drop_buffer(ctx, res); // drop the buffer copy
223 }
224 }
225 fz_catch(ctx) {
226 Py_CLEAR(result);
227 fz_rethrow(ctx);
228 }
229 PyErr_Clear();
230 return result;
231 }
232
233 //----------------------------------------------------------------------------
234 // Version of fz_new_pixmap_from_display_list (util.c) to also support
235 // rendering of only the 'clip' part of the displaylist rectangle
236 //----------------------------------------------------------------------------
237 fz_pixmap *
238 JM_pixmap_from_display_list(fz_context *ctx,
239 fz_display_list *list,
240 PyObject *ctm,
241 fz_colorspace *cs,
242 int alpha,
243 PyObject *clip,
244 fz_separations *seps
245 )
246 {
247 fz_rect rect = fz_bound_display_list(ctx, list);
248 fz_matrix matrix = JM_matrix_from_py(ctm);
249 fz_pixmap *pix = NULL;
250 fz_var(pix);
251 fz_device *dev = NULL;
252 fz_var(dev);
253 fz_rect rclip = JM_rect_from_py(clip);
254 rect = fz_intersect_rect(rect, rclip); // no-op if clip is not given
255
256 rect = fz_transform_rect(rect, matrix);
257 fz_irect irect = fz_round_rect(rect);
258
259 pix = fz_new_pixmap_with_bbox(ctx, cs, irect, seps, alpha);
260 if (alpha)
261 fz_clear_pixmap(ctx, pix);
262 else
263 fz_clear_pixmap_with_value(ctx, pix, 0xFF);
264
265 fz_try(ctx) {
266 if (!fz_is_infinite_rect(rclip)) {
267 dev = fz_new_draw_device_with_bbox(ctx, matrix, pix, &irect);
268 fz_run_display_list(ctx, list, dev, fz_identity, rclip, NULL);
269 } else {
270 dev = fz_new_draw_device(ctx, matrix, pix);
271 fz_run_display_list(ctx, list, dev, fz_identity, fz_infinite_rect, NULL);
272 }
273
274 fz_close_device(ctx, dev);
275 }
276 fz_always(ctx) {
277 fz_drop_device(ctx, dev);
278 }
279 fz_catch(ctx) {
280 fz_drop_pixmap(ctx, pix);
281 fz_rethrow(ctx);
282 }
283 return pix;
284 }
285
286 //----------------------------------------------------------------------------
287 // Pixmap creation directly using a short-lived displaylist, so we can support
288 // separations.
289 //----------------------------------------------------------------------------
290 fz_pixmap *
291 JM_pixmap_from_page(fz_context *ctx,
292 fz_document *doc,
293 fz_page *page,
294 PyObject *ctm,
295 fz_colorspace *cs,
296 int alpha,
297 int annots,
298 PyObject *clip
299 )
300 {
301 enum { SPOTS_NONE, SPOTS_OVERPRINT_SIM, SPOTS_FULL };
302 int spots;
303 if (FZ_ENABLE_SPOT_RENDERING)
304 spots = SPOTS_OVERPRINT_SIM;
305 else
306 spots = SPOTS_NONE;
307
308 fz_separations *seps = NULL;
309 fz_pixmap *pix = NULL;
310 fz_colorspace *oi = NULL;
311 fz_var(oi);
312 fz_colorspace *colorspace = cs;
313 fz_rect rect;
314 fz_irect bbox;
315 fz_device *dev = NULL;
316 fz_var(dev);
317 fz_matrix matrix = JM_matrix_from_py(ctm);
318 rect = fz_bound_page(ctx, page);
319 fz_rect rclip = JM_rect_from_py(clip);
320 rect = fz_intersect_rect(rect, rclip); // no-op if clip is not given
321 rect = fz_transform_rect(rect, matrix);
322 bbox = fz_round_rect(rect);
323
324 fz_try(ctx) {
325 // Pixmap of the document's /OutputIntents ("output intents")
326 oi = fz_document_output_intent(ctx, doc);
327 // if present and compatible, use it instead of the parameter
328 if (oi) {
329 if (fz_colorspace_n(ctx, oi) == fz_colorspace_n(ctx, cs)) {
330 colorspace = fz_keep_colorspace(ctx, oi);
331 }
332 }
333
334 // check if spots rendering is available and if so use separations
335 if (spots != SPOTS_NONE) {
336 seps = fz_page_separations(ctx, page);
337 if (seps) {
338 int i, n = fz_count_separations(ctx, seps);
339 if (spots == SPOTS_FULL)
340 for (i = 0; i < n; i++)
341 fz_set_separation_behavior(ctx, seps, i, FZ_SEPARATION_SPOT);
342 else
343 for (i = 0; i < n; i++)
344 fz_set_separation_behavior(ctx, seps, i, FZ_SEPARATION_COMPOSITE);
345 } else if (fz_page_uses_overprint(ctx, page)) {
346 /* This page uses overprint, so we need an empty
347 * sep object to force the overprint simulation on. */
348 seps = fz_new_separations(ctx, 0);
349 } else if (oi && fz_colorspace_n(ctx, oi) != fz_colorspace_n(ctx, colorspace)) {
350 /* We have an output intent, and it's incompatible
351 * with the colorspace our device needs. Force the
352 * overprint simulation on, because this ensures that
353 * we 'simulate' the output intent too. */
354 seps = fz_new_separations(ctx, 0);
355 }
356 }
357
358 pix = fz_new_pixmap_with_bbox(ctx, colorspace, bbox, seps, alpha);
359
360 if (alpha) {
361 fz_clear_pixmap(ctx, pix);
362 } else {
363 fz_clear_pixmap_with_value(ctx, pix, 0xFF);
364 }
365
366 dev = fz_new_draw_device(ctx, matrix, pix);
367 if (annots) {
368 fz_run_page(ctx, page, dev, fz_identity, NULL);
369 } else {
370 fz_run_page_contents(ctx, page, dev, fz_identity, NULL);
371 }
372 fz_close_device(ctx, dev);
373 }
374 fz_always(ctx) {
375 fz_drop_device(ctx, dev);
376 fz_drop_separations(ctx, seps);
377 fz_drop_colorspace(ctx, oi);
378 }
379 fz_catch(ctx) {
380 fz_rethrow(ctx);
381 }
382 return pix;
383 }
384
385 PyObject *JM_color_count(fz_context *ctx, fz_pixmap *pm, PyObject *clip)
386 {
387 PyObject *rc = PyDict_New(), *pixel=NULL, *c=NULL;
388 long cnt=0;
389 fz_irect irect = fz_pixmap_bbox(ctx, pm);
390 irect = fz_intersect_irect(irect, fz_round_rect(JM_rect_from_py(clip)));
391 size_t stride = pm->stride;
392 size_t width = irect.x1 - irect.x0, height = irect.y1 - irect.y0;
393 size_t i, j, n = (size_t) pm->n, substride = width * n;
394 unsigned char *s = pm->samples + stride * (irect.y0 - pm->y) + (irect.x0 - pm->x) * n;
395 unsigned char oldpix[10], newpix[10];
396 memcpy(oldpix, s, n);
397 cnt = 0;
398 fz_try(ctx) {
399 if (fz_is_empty_irect(irect)) goto finished;
400 for (i = 0; i < height; i++) {
401 for (j = 0; j < substride; j += n) {
402 memcpy(newpix, s + j, n);
403 if (memcmp(oldpix, newpix,n) != 0) {
404 pixel = PyBytes_FromStringAndSize(oldpix, n);
405 c = PyDict_GetItem(rc, pixel);
406 if (c) cnt += PyLong_AsLong(c);
407 DICT_SETITEM_DROP(rc, pixel, PyLong_FromLong(cnt));
408 Py_DECREF(pixel);
409 cnt = 1;
410 memcpy(oldpix, newpix, n);
411 } else {
412 cnt += 1;
413 }
414 }
415 s += stride;
416 }
417 pixel = PyBytes_FromStringAndSize(oldpix, n);
418 c = PyDict_GetItem(rc, pixel);
419 if (c) cnt += PyLong_AsLong(c);
420 DICT_SETITEM_DROP(rc, pixel, PyLong_FromLong(cnt));
421 Py_DECREF(pixel);
422 finished:;
423 }
424 fz_catch(ctx) {
425 Py_CLEAR(rc);
426 fz_rethrow(ctx);
427 }
428 PyErr_Clear();
429 return rc;
430 }
431 %}