diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src_classic/helper-pixmap.i	Mon Sep 15 11:37:51 2025 +0200
@@ -0,0 +1,431 @@
+%{
+/*
+# ------------------------------------------------------------------------
+# Copyright 2020-2022, Harald Lieder, mailto:harald.lieder@outlook.com
+# License: GNU AFFERO GPL 3.0, https://www.gnu.org/licenses/agpl-3.0.html
+#
+# Part of "PyMuPDF", a Python binding for "MuPDF" (http://mupdf.com), a
+# lightweight PDF, XPS, and E-book viewer, renderer and toolkit which is
+# maintained and developed by Artifex Software, Inc. https://artifex.com.
+# ------------------------------------------------------------------------
+*/
+//-----------------------------------------------------------------------------
+// pixmap helper functions
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+// Clear a pixmap rectangle - my version also supports non-alpha pixmaps
+//-----------------------------------------------------------------------------
+int
+JM_clear_pixmap_rect_with_value(fz_context *ctx, fz_pixmap *dest, int value, fz_irect b)
+{
+    unsigned char *destp;
+    int x, y, w, k, destspan;
+
+    b = fz_intersect_irect(b, fz_pixmap_bbox(ctx, dest));
+    w = b.x1 - b.x0;
+    y = b.y1 - b.y0;
+    if (w <= 0 || y <= 0)
+        return 0;
+
+    destspan = dest->stride;
+    destp = dest->samples + (unsigned int)(destspan * (b.y0 - dest->y) + dest->n * (b.x0 - dest->x));
+
+    /* CMYK needs special handling (and potentially any other subtractive colorspaces) */
+    if (fz_colorspace_n(ctx, dest->colorspace) == 4) {
+        value = 255 - value;
+        do {
+            unsigned char *s = destp;
+            for (x = 0; x < w; x++) {
+                *s++ = 0;
+                *s++ = 0;
+                *s++ = 0;
+                *s++ = value;
+                if (dest->alpha) *s++ = 255;
+            }
+            destp += destspan;
+        } while (--y);
+        return 1;
+    }
+
+    do {
+        unsigned char *s = destp;
+        for (x = 0; x < w; x++) {
+            for (k = 0; k < dest->n - 1; k++)
+                *s++ = value;
+            if (dest->alpha) *s++ = 255;
+            else *s++ = value;
+        }
+        destp += destspan;
+    } while (--y);
+    return 1;
+}
+
+//-----------------------------------------------------------------------------
+// fill a rect with a color tuple
+//-----------------------------------------------------------------------------
+int
+JM_fill_pixmap_rect_with_color(fz_context *ctx, fz_pixmap *dest, unsigned char col[5], fz_irect b)
+{
+    unsigned char *destp;
+    int x, y, w, i, destspan;
+
+    b = fz_intersect_irect(b, fz_pixmap_bbox(ctx, dest));
+    w = b.x1 - b.x0;
+    y = b.y1 - b.y0;
+    if (w <= 0 || y <= 0)
+        return 0;
+
+    destspan = dest->stride;
+    destp = dest->samples + (unsigned int)(destspan * (b.y0 - dest->y) + dest->n * (b.x0 - dest->x));
+
+    do {
+        unsigned char *s = destp;
+        for (x = 0; x < w; x++) {
+            for (i = 0; i < dest->n; i++)
+                *s++ = col[i];
+        }
+        destp += destspan;
+    } while (--y);
+    return 1;
+}
+
+//-----------------------------------------------------------------------------
+// invert a rectangle - also supports non-alpha pixmaps
+//-----------------------------------------------------------------------------
+int
+JM_invert_pixmap_rect(fz_context *ctx, fz_pixmap *dest, fz_irect b)
+{
+    unsigned char *destp;
+    int x, y, w, i, destspan;
+
+    b = fz_intersect_irect(b, fz_pixmap_bbox(ctx, dest));
+    w = b.x1 - b.x0;
+    y = b.y1 - b.y0;
+    if (w <= 0 || y <= 0)
+        return 0;
+
+    destspan = dest->stride;
+    destp = dest->samples + (unsigned int)(destspan * (b.y0 - dest->y) + dest->n * (b.x0 - dest->x));
+    int n0 = dest->n - dest->alpha;
+    do {
+        unsigned char *s = destp;
+        for (x = 0; x < w; x++) {
+            for (i = 0; i < n0; i++) {
+                *s = 255 - *s;
+                s++;
+            }
+            if (dest->alpha) s++;
+        }
+        destp += destspan;
+    } while (--y);
+    return 1;
+}
+
+int
+JM_is_jbig2_image(fz_context *ctx, pdf_obj *dict)
+{
+    // fixme: should we remove this function?
+	return 0;
+    /*
+    pdf_obj *filter;
+	int i, n;
+
+	filter = pdf_dict_get(ctx, dict, PDF_NAME(Filter));
+	if (pdf_name_eq(ctx, filter, PDF_NAME(JBIG2Decode)))
+		return 1;
+	n = pdf_array_len(ctx, filter);
+	for (i = 0; i < n; i++)
+		if (pdf_name_eq(ctx, pdf_array_get(ctx, filter, i), PDF_NAME(JBIG2Decode)))
+			return 1;
+	return 0;
+    */
+}
+
+//-----------------------------------------------------------------------------
+// Return basic properties of an image provided as bytes or bytearray
+// The function creates an fz_image and optionally returns it.
+//-----------------------------------------------------------------------------
+PyObject *JM_image_profile(fz_context *ctx, PyObject *imagedata, int keep_image)
+{
+    if (!EXISTS(imagedata)) {
+        Py_RETURN_NONE;  // nothing given
+    }
+    fz_image *image = NULL;
+    fz_buffer *res = NULL;
+    PyObject *result = NULL;
+    unsigned char *c = NULL;
+    Py_ssize_t len = 0;
+    if (PyBytes_Check(imagedata)) {
+        c = PyBytes_AS_STRING(imagedata);
+        len = PyBytes_GET_SIZE(imagedata);
+    } else if (PyByteArray_Check(imagedata)) {
+        c = PyByteArray_AS_STRING(imagedata);
+        len = PyByteArray_GET_SIZE(imagedata);
+    } else {
+        PySys_WriteStderr("bad image data\n");
+        Py_RETURN_NONE;
+    }
+
+    if (len < 8) {
+        PySys_WriteStderr("bad image data\n");
+        Py_RETURN_NONE;
+    }
+    int type = fz_recognize_image_format(ctx, c);
+    if (type == FZ_IMAGE_UNKNOWN) {
+        Py_RETURN_NONE;
+    }
+
+    fz_try(ctx) {
+        if (keep_image) {
+            res = fz_new_buffer_from_copied_data(ctx, c, (size_t) len);
+        } else {
+            res = fz_new_buffer_from_shared_data(ctx, c, (size_t) len);
+        }
+        image = fz_new_image_from_buffer(ctx, res);
+        int xres, yres, orientation;
+        fz_matrix ctm = fz_image_orientation_matrix(ctx, image);
+        fz_image_resolution(image, &xres, &yres);
+        orientation = (int) fz_image_orientation(ctx, image);
+        const char *cs_name = fz_colorspace_name(ctx, image->colorspace);
+        result = PyDict_New();
+        DICT_SETITEM_DROP(result, dictkey_width,
+                Py_BuildValue("i", image->w));
+        DICT_SETITEM_DROP(result, dictkey_height,
+                Py_BuildValue("i", image->h));
+        DICT_SETITEMSTR_DROP(result, "orientation",
+                Py_BuildValue("i", orientation));
+        DICT_SETITEM_DROP(result, dictkey_matrix,
+                JM_py_from_matrix(ctm));
+        DICT_SETITEM_DROP(result, dictkey_xres,
+                Py_BuildValue("i", xres));
+        DICT_SETITEM_DROP(result, dictkey_yres,
+                Py_BuildValue("i", yres));
+        DICT_SETITEM_DROP(result, dictkey_colorspace,
+                Py_BuildValue("i", image->n));
+        DICT_SETITEM_DROP(result, dictkey_bpc,
+                Py_BuildValue("i", image->bpc));
+        DICT_SETITEM_DROP(result, dictkey_ext,
+                Py_BuildValue("s", JM_image_extension(type)));
+        DICT_SETITEM_DROP(result, dictkey_cs_name,
+                Py_BuildValue("s", cs_name));
+
+        if (keep_image) {
+            DICT_SETITEM_DROP(result, dictkey_image,
+                    PyLong_FromVoidPtr((void *) fz_keep_image(ctx, image)));
+        }
+    }
+    fz_always(ctx) {
+        if (!keep_image) {
+            fz_drop_image(ctx, image);
+        } else {
+            fz_drop_buffer(ctx, res);  // drop the buffer copy
+        }
+    }
+    fz_catch(ctx) {
+        Py_CLEAR(result);
+        fz_rethrow(ctx);
+    }
+    PyErr_Clear();
+    return result;
+}
+
+//----------------------------------------------------------------------------
+// Version of fz_new_pixmap_from_display_list (util.c) to also support
+// rendering of only the 'clip' part of the displaylist rectangle
+//----------------------------------------------------------------------------
+fz_pixmap *
+JM_pixmap_from_display_list(fz_context *ctx,
+                            fz_display_list *list,
+                            PyObject *ctm,
+                            fz_colorspace *cs,
+                            int alpha,
+                            PyObject *clip,
+                            fz_separations *seps
+                           )
+{
+    fz_rect rect = fz_bound_display_list(ctx, list);
+    fz_matrix matrix = JM_matrix_from_py(ctm);
+    fz_pixmap *pix = NULL;
+    fz_var(pix);
+    fz_device *dev = NULL;
+    fz_var(dev);
+    fz_rect rclip = JM_rect_from_py(clip);
+    rect = fz_intersect_rect(rect, rclip);  // no-op if clip is not given
+
+    rect = fz_transform_rect(rect, matrix);
+    fz_irect irect = fz_round_rect(rect);
+
+    pix = fz_new_pixmap_with_bbox(ctx, cs, irect, seps, alpha);
+    if (alpha)
+        fz_clear_pixmap(ctx, pix);
+    else
+        fz_clear_pixmap_with_value(ctx, pix, 0xFF);
+
+    fz_try(ctx) {
+        if (!fz_is_infinite_rect(rclip)) {
+            dev = fz_new_draw_device_with_bbox(ctx, matrix, pix, &irect);
+            fz_run_display_list(ctx, list, dev, fz_identity, rclip, NULL);
+        } else {
+            dev = fz_new_draw_device(ctx, matrix, pix);
+            fz_run_display_list(ctx, list, dev, fz_identity, fz_infinite_rect, NULL);
+        }
+
+        fz_close_device(ctx, dev);
+    }
+    fz_always(ctx) {
+        fz_drop_device(ctx, dev);
+    }
+    fz_catch(ctx) {
+        fz_drop_pixmap(ctx, pix);
+        fz_rethrow(ctx);
+    }
+    return pix;
+}
+
+//----------------------------------------------------------------------------
+// Pixmap creation directly using a short-lived displaylist, so we can support
+// separations.
+//----------------------------------------------------------------------------
+fz_pixmap *
+JM_pixmap_from_page(fz_context *ctx,
+                    fz_document *doc,
+                    fz_page *page,
+                    PyObject *ctm,
+                    fz_colorspace *cs,
+                    int alpha,
+                    int annots,
+                    PyObject *clip
+                   )
+{
+    enum { SPOTS_NONE, SPOTS_OVERPRINT_SIM, SPOTS_FULL };
+    int spots;
+    if (FZ_ENABLE_SPOT_RENDERING)
+        spots = SPOTS_OVERPRINT_SIM;
+    else
+        spots = SPOTS_NONE;
+
+    fz_separations *seps = NULL;
+    fz_pixmap *pix = NULL;
+    fz_colorspace *oi = NULL;
+    fz_var(oi);
+    fz_colorspace *colorspace = cs;
+    fz_rect rect;
+    fz_irect bbox;
+    fz_device *dev = NULL;
+    fz_var(dev);
+    fz_matrix matrix = JM_matrix_from_py(ctm);
+    rect = fz_bound_page(ctx, page);
+    fz_rect rclip = JM_rect_from_py(clip);
+    rect = fz_intersect_rect(rect, rclip);  // no-op if clip is not given
+    rect = fz_transform_rect(rect, matrix);
+    bbox = fz_round_rect(rect);
+
+    fz_try(ctx) {
+        // Pixmap of the document's /OutputIntents ("output intents")
+        oi = fz_document_output_intent(ctx, doc);
+        // if present and compatible, use it instead of the parameter
+        if (oi) {
+            if (fz_colorspace_n(ctx, oi) == fz_colorspace_n(ctx, cs)) {
+                colorspace = fz_keep_colorspace(ctx, oi);
+            }
+        }
+
+        // check if spots rendering is available and if so use separations
+        if (spots != SPOTS_NONE) {
+            seps = fz_page_separations(ctx, page);
+            if (seps) {
+                int i, n = fz_count_separations(ctx, seps);
+                if (spots == SPOTS_FULL)
+                    for (i = 0; i < n; i++)
+                        fz_set_separation_behavior(ctx, seps, i, FZ_SEPARATION_SPOT);
+                else
+                    for (i = 0; i < n; i++)
+                        fz_set_separation_behavior(ctx, seps, i, FZ_SEPARATION_COMPOSITE);
+            } else if (fz_page_uses_overprint(ctx, page)) {
+                /* This page uses overprint, so we need an empty
+                 * sep object to force the overprint simulation on. */
+                seps = fz_new_separations(ctx, 0);
+            } else if (oi && fz_colorspace_n(ctx, oi) != fz_colorspace_n(ctx, colorspace)) {
+                /* We have an output intent, and it's incompatible
+                 * with the colorspace our device needs. Force the
+                 * overprint simulation on, because this ensures that
+                 * we 'simulate' the output intent too. */
+                seps = fz_new_separations(ctx, 0);
+            }
+        }
+
+        pix = fz_new_pixmap_with_bbox(ctx, colorspace, bbox, seps, alpha);
+
+        if (alpha) {
+            fz_clear_pixmap(ctx, pix);
+        } else {
+            fz_clear_pixmap_with_value(ctx, pix, 0xFF);
+        }
+
+        dev = fz_new_draw_device(ctx, matrix, pix);
+        if (annots) {
+            fz_run_page(ctx, page, dev, fz_identity, NULL);
+        } else {
+            fz_run_page_contents(ctx, page, dev, fz_identity, NULL);
+        }
+        fz_close_device(ctx, dev);
+    }
+    fz_always(ctx) {
+        fz_drop_device(ctx, dev);
+        fz_drop_separations(ctx, seps);
+        fz_drop_colorspace(ctx, oi);
+    }
+    fz_catch(ctx) {
+        fz_rethrow(ctx);
+    }
+    return pix;
+}
+
+PyObject *JM_color_count(fz_context *ctx, fz_pixmap *pm, PyObject *clip)
+{
+    PyObject *rc = PyDict_New(), *pixel=NULL, *c=NULL;
+    long cnt=0;
+    fz_irect irect = fz_pixmap_bbox(ctx, pm);
+    irect = fz_intersect_irect(irect, fz_round_rect(JM_rect_from_py(clip)));
+    size_t stride = pm->stride;
+    size_t width = irect.x1 - irect.x0, height = irect.y1 - irect.y0;
+    size_t i, j, n = (size_t) pm->n, substride = width * n;
+    unsigned char *s = pm->samples + stride * (irect.y0 - pm->y) + (irect.x0 - pm->x) * n;
+    unsigned char oldpix[10], newpix[10];
+    memcpy(oldpix, s, n);
+    cnt = 0;
+    fz_try(ctx) {
+        if (fz_is_empty_irect(irect)) goto finished;
+        for (i = 0; i < height; i++) {
+            for (j = 0; j < substride; j += n) {
+                memcpy(newpix, s + j, n);
+                if (memcmp(oldpix, newpix,n) != 0) {
+                    pixel = PyBytes_FromStringAndSize(oldpix, n);
+                    c = PyDict_GetItem(rc, pixel);
+                    if (c) cnt += PyLong_AsLong(c);
+                    DICT_SETITEM_DROP(rc, pixel, PyLong_FromLong(cnt));
+                    Py_DECREF(pixel);
+                    cnt = 1;
+                    memcpy(oldpix, newpix, n);
+                } else {
+                    cnt += 1;
+                }
+            }
+            s += stride;
+        }
+        pixel = PyBytes_FromStringAndSize(oldpix, n);
+        c = PyDict_GetItem(rc, pixel);
+        if (c) cnt += PyLong_AsLong(c);
+        DICT_SETITEM_DROP(rc, pixel, PyLong_FromLong(cnt));
+        Py_DECREF(pixel);
+        finished:;
+    }
+    fz_catch(ctx) {
+        Py_CLEAR(rc);
+        fz_rethrow(ctx);
+    }
+    PyErr_Clear();
+    return rc;
+}
+%}