From 1eecf9cc3afe12d28738ad539d130416fa846434 Mon Sep 17 00:00:00 2001 From: Julian Smith Date: Tue, 16 Jan 2024 13:13:10 +0000 Subject: [PATCH] Fix for Pixmap.set_pixel(). Also added optimisation in src.extra.i. Addresses #3050. --- src/__init__.py | 23 ++++++++++++++--- src/extra.i | 59 ++++++++++++++++++++++++++++++++++++++++++++ tests/test_pixmap.py | 27 ++++++++++++++++++++ 3 files changed, 105 insertions(+), 4 deletions(-) diff --git a/src/__init__.py b/src/__init__.py index 3ebd7207f..0d1ee6a1d 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -9647,6 +9647,11 @@ def __init__(self, *args): text += f' {type(arg)}: {arg}\n' raise Exception( text) + # 2024-01-16: Experimental support for a memory-view of the underlying + # data. Doesn't seem to make much difference to Pixmap.set_pixel() so + # not currently used. + self._memory_view = None + def __len__(self): return self.size @@ -10117,20 +10122,28 @@ def set_origin(self, x, y): def set_pixel(self, x, y, color): """Set color of pixel (x, y).""" + if g_use_extra: + return extra.set_pixel(self.this.m_internal, x, y, color) pm = self.this if not _INRANGE(x, 0, pm.w() - 1) or not _INRANGE(y, 0, pm.h() - 1): raise ValueError( MSG_PIXEL_OUTSIDE) n = pm.n() - c = list() for j in range(n): i = color[j] if not _INRANGE(i, 0, 255): raise ValueError( MSG_BAD_COLOR_SEQ) - c.append( ord(i)) stride = mupdf.fz_pixmap_stride( pm) i = stride * y + n * x - for j in range(n): - pm.m_internal.samples[i + j] = c[j] + if 0: + # Using a cached self._memory_view doesn't actually make much + # difference to speed. + if not self._memory_view: + self._memory_view = self.samples_mv + for j in range(n): + self._memory_view[i + j] = color[j] + else: + for j in range(n): + pm.fz_samples_set(i + j, color[j]) def set_rect(self, bbox, color): """Set color of all pixels in bbox.""" @@ -10154,6 +10167,8 @@ def shrink(self, factor): JM_Warning("ignoring shrink factor < 1") return mupdf.fz_subsample_pixmap( self.this, factor) + # Pixmap has changed so clear our memory view. + self._memory_view = None @property def size(self): diff --git a/src/extra.i b/src/extra.i index f4690c9b1..aa0f7d268 100644 --- a/src/extra.i +++ b/src/extra.i @@ -3670,6 +3670,7 @@ PyObject* extractBLOCKS(mupdf::FzStextPage& self) mupdf::fz_clear_buffer(res); // set text buffer to empty int line_n = -1; int last_char = 0; + (void) line_n; /* Not actually used, but keeping in the code for now. */ for (fz_stext_line* line = block->u.t.first_line; line; line = line->next) { line_n++; @@ -3954,6 +3955,62 @@ int pixmap_n(mupdf::FzPixmap& pixmap) return mupdf::fz_pixmap_components( pixmap); } +static int +JM_INT_ITEM(PyObject *obj, Py_ssize_t idx, int *result) +{ + PyObject *temp = PySequence_ITEM(obj, idx); + if (!temp) return 1; + if (PyLong_Check(temp)) { + *result = (int) PyLong_AsLong(temp); + Py_DECREF(temp); + } else if (PyFloat_Check(temp)) { + *result = (int) PyFloat_AsDouble(temp); + Py_DECREF(temp); + } else { + Py_DECREF(temp); + return 1; + } + if (PyErr_Occurred()) { + PyErr_Clear(); + return 1; + } + return 0; +} + +PyObject *set_pixel(fz_pixmap* pm, int x, int y, PyObject *color) +{ + fz_context* ctx = mupdf::internal_context_get(); + if (0 + || x < 0 + || x >= pm->w + || y < 0 + || y >= pm->h + ) + { + throw std::range_error( MSG_PIXEL_OUTSIDE); + } + int n = pm->n; + if (!PySequence_Check(color) || PySequence_Size(color) != n) { + throw std::range_error(MSG_BAD_COLOR_SEQ); + } + int i, j; + unsigned char c[5]; + for (j = 0; j < n; j++) { + if (JM_INT_ITEM(color, j, &i) == 1) { + throw std::range_error(MSG_BAD_COLOR_SEQ); + } + if (i < 0 or i >= 256) { + throw std::range_error(MSG_BAD_COLOR_SEQ); + } + c[j] = (unsigned char) i; + } + int stride = fz_pixmap_stride(ctx, pm); + i = stride * y + n * x; + for (j = 0; j < n; j++) { + pm->samples[i + j] = c[j]; + } + Py_RETURN_NONE; +} //------------------------------------------- // make a buffer from an stext_page's text //------------------------------------------- @@ -4409,3 +4466,5 @@ PyObject *pixmap_pixel(fz_pixmap* pm, int x, int y); int pixmap_n(mupdf::FzPixmap& pixmap); PyObject* JM_search_stext_page(fz_stext_page *page, const char *needle); + +PyObject *set_pixel(fz_pixmap* pm, int x, int y, PyObject *color); diff --git a/tests/test_pixmap.py b/tests/test_pixmap.py index ee3a74067..f31e50f47 100644 --- a/tests/test_pixmap.py +++ b/tests/test_pixmap.py @@ -152,3 +152,30 @@ def test_3020(): pm2 = fitz.Pixmap(pm, 20, 30, None) pm3 = fitz.Pixmap(fitz.csGRAY, pm) pm4 = fitz.Pixmap(pm, pm3) + +def test_3050(): + pdf_file = fitz.open(pdf) + for page_no, page in enumerate(pdf_file): + zoom_x = 4.0 + zoom_y = 4.0 + matrix = fitz.Matrix(zoom_x, zoom_y) + pix = page.get_pixmap(matrix=matrix) + digest0 = pix.digest + print(f'{pix.width=} {pix.height=}') + def product(x, y): + for yy in y: + for xx in x: + yield (xx, yy) + n = 0 + # We use a small subset of the image because non-optimised rebase gets + # very slow. + for pos in product(range(100), range(100)): + if sum(pix.pixel(pos[0], pos[1])) >= 600: + n += 1 + pix.set_pixel(pos[0], pos[1], (255, 255, 255)) + digest1 = pix.digest + print(f'{page_no=} {n=} {digest0=} {digest1=}') + digest_expected = b'\xd7x\x94_\x98\xa1<-/\xf3\xf9\x04\xec#\xaa\xee' + pix.save(os.path.abspath(f'{__file__}/../../tests/test_3050_out.png')) + assert digest1 != digest0 + assert digest1 == digest_expected