Mercurial > hgrepos > Python2 > PyMuPDF
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 %} |
