view src_classic/helper-pixmap.i @ 46:7ee69f120f19 default tip

>>>>> tag v1.26.5+1 for changeset b74429b0f5c4
author Franz Glasner <fzglas.hg@dom66.de>
date Sat, 11 Oct 2025 17:17:30 +0200
parents 1d09e1dec1d9
children
line wrap: on
line source

%{
/*
# ------------------------------------------------------------------------
# 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;
}
%}