Skip to content

Commit 1eecf9c

Browse files
Fix for Pixmap.set_pixel().
Also added optimisation in src.extra.i. Addresses #3050.
1 parent 274e351 commit 1eecf9c

File tree

3 files changed

+105
-4
lines changed

3 files changed

+105
-4
lines changed

src/__init__.py

+19-4
Original file line numberDiff line numberDiff line change
@@ -9647,6 +9647,11 @@ def __init__(self, *args):
96479647
text += f' {type(arg)}: {arg}\n'
96489648
raise Exception( text)
96499649

9650+
# 2024-01-16: Experimental support for a memory-view of the underlying
9651+
# data. Doesn't seem to make much difference to Pixmap.set_pixel() so
9652+
# not currently used.
9653+
self._memory_view = None
9654+
96509655
def __len__(self):
96519656
return self.size
96529657

@@ -10117,20 +10122,28 @@ def set_origin(self, x, y):
1011710122

1011810123
def set_pixel(self, x, y, color):
1011910124
"""Set color of pixel (x, y)."""
10125+
if g_use_extra:
10126+
return extra.set_pixel(self.this.m_internal, x, y, color)
1012010127
pm = self.this
1012110128
if not _INRANGE(x, 0, pm.w() - 1) or not _INRANGE(y, 0, pm.h() - 1):
1012210129
raise ValueError( MSG_PIXEL_OUTSIDE)
1012310130
n = pm.n()
10124-
c = list()
1012510131
for j in range(n):
1012610132
i = color[j]
1012710133
if not _INRANGE(i, 0, 255):
1012810134
raise ValueError( MSG_BAD_COLOR_SEQ)
10129-
c.append( ord(i))
1013010135
stride = mupdf.fz_pixmap_stride( pm)
1013110136
i = stride * y + n * x
10132-
for j in range(n):
10133-
pm.m_internal.samples[i + j] = c[j]
10137+
if 0:
10138+
# Using a cached self._memory_view doesn't actually make much
10139+
# difference to speed.
10140+
if not self._memory_view:
10141+
self._memory_view = self.samples_mv
10142+
for j in range(n):
10143+
self._memory_view[i + j] = color[j]
10144+
else:
10145+
for j in range(n):
10146+
pm.fz_samples_set(i + j, color[j])
1013410147

1013510148
def set_rect(self, bbox, color):
1013610149
"""Set color of all pixels in bbox."""
@@ -10154,6 +10167,8 @@ def shrink(self, factor):
1015410167
JM_Warning("ignoring shrink factor < 1")
1015510168
return
1015610169
mupdf.fz_subsample_pixmap( self.this, factor)
10170+
# Pixmap has changed so clear our memory view.
10171+
self._memory_view = None
1015710172

1015810173
@property
1015910174
def size(self):

src/extra.i

+59
Original file line numberDiff line numberDiff line change
@@ -3670,6 +3670,7 @@ PyObject* extractBLOCKS(mupdf::FzStextPage& self)
36703670
mupdf::fz_clear_buffer(res); // set text buffer to empty
36713671
int line_n = -1;
36723672
int last_char = 0;
3673+
(void) line_n; /* Not actually used, but keeping in the code for now. */
36733674
for (fz_stext_line* line = block->u.t.first_line; line; line = line->next)
36743675
{
36753676
line_n++;
@@ -3954,6 +3955,62 @@ int pixmap_n(mupdf::FzPixmap& pixmap)
39543955
return mupdf::fz_pixmap_components( pixmap);
39553956
}
39563957

3958+
static int
3959+
JM_INT_ITEM(PyObject *obj, Py_ssize_t idx, int *result)
3960+
{
3961+
PyObject *temp = PySequence_ITEM(obj, idx);
3962+
if (!temp) return 1;
3963+
if (PyLong_Check(temp)) {
3964+
*result = (int) PyLong_AsLong(temp);
3965+
Py_DECREF(temp);
3966+
} else if (PyFloat_Check(temp)) {
3967+
*result = (int) PyFloat_AsDouble(temp);
3968+
Py_DECREF(temp);
3969+
} else {
3970+
Py_DECREF(temp);
3971+
return 1;
3972+
}
3973+
if (PyErr_Occurred()) {
3974+
PyErr_Clear();
3975+
return 1;
3976+
}
3977+
return 0;
3978+
}
3979+
3980+
PyObject *set_pixel(fz_pixmap* pm, int x, int y, PyObject *color)
3981+
{
3982+
fz_context* ctx = mupdf::internal_context_get();
3983+
if (0
3984+
|| x < 0
3985+
|| x >= pm->w
3986+
|| y < 0
3987+
|| y >= pm->h
3988+
)
3989+
{
3990+
throw std::range_error( MSG_PIXEL_OUTSIDE);
3991+
}
3992+
int n = pm->n;
3993+
if (!PySequence_Check(color) || PySequence_Size(color) != n) {
3994+
throw std::range_error(MSG_BAD_COLOR_SEQ);
3995+
}
3996+
int i, j;
3997+
unsigned char c[5];
3998+
for (j = 0; j < n; j++) {
3999+
if (JM_INT_ITEM(color, j, &i) == 1) {
4000+
throw std::range_error(MSG_BAD_COLOR_SEQ);
4001+
}
4002+
if (i < 0 or i >= 256) {
4003+
throw std::range_error(MSG_BAD_COLOR_SEQ);
4004+
}
4005+
c[j] = (unsigned char) i;
4006+
}
4007+
int stride = fz_pixmap_stride(ctx, pm);
4008+
i = stride * y + n * x;
4009+
for (j = 0; j < n; j++) {
4010+
pm->samples[i + j] = c[j];
4011+
}
4012+
Py_RETURN_NONE;
4013+
}
39574014
//-------------------------------------------
39584015
// make a buffer from an stext_page's text
39594016
//-------------------------------------------
@@ -4409,3 +4466,5 @@ PyObject *pixmap_pixel(fz_pixmap* pm, int x, int y);
44094466
int pixmap_n(mupdf::FzPixmap& pixmap);
44104467

44114468
PyObject* JM_search_stext_page(fz_stext_page *page, const char *needle);
4469+
4470+
PyObject *set_pixel(fz_pixmap* pm, int x, int y, PyObject *color);

tests/test_pixmap.py

+27
Original file line numberDiff line numberDiff line change
@@ -152,3 +152,30 @@ def test_3020():
152152
pm2 = fitz.Pixmap(pm, 20, 30, None)
153153
pm3 = fitz.Pixmap(fitz.csGRAY, pm)
154154
pm4 = fitz.Pixmap(pm, pm3)
155+
156+
def test_3050():
157+
pdf_file = fitz.open(pdf)
158+
for page_no, page in enumerate(pdf_file):
159+
zoom_x = 4.0
160+
zoom_y = 4.0
161+
matrix = fitz.Matrix(zoom_x, zoom_y)
162+
pix = page.get_pixmap(matrix=matrix)
163+
digest0 = pix.digest
164+
print(f'{pix.width=} {pix.height=}')
165+
def product(x, y):
166+
for yy in y:
167+
for xx in x:
168+
yield (xx, yy)
169+
n = 0
170+
# We use a small subset of the image because non-optimised rebase gets
171+
# very slow.
172+
for pos in product(range(100), range(100)):
173+
if sum(pix.pixel(pos[0], pos[1])) >= 600:
174+
n += 1
175+
pix.set_pixel(pos[0], pos[1], (255, 255, 255))
176+
digest1 = pix.digest
177+
print(f'{page_no=} {n=} {digest0=} {digest1=}')
178+
digest_expected = b'\xd7x\x94_\x98\xa1<-/\xf3\xf9\x04\xec#\xaa\xee'
179+
pix.save(os.path.abspath(f'{__file__}/../../tests/test_3050_out.png'))
180+
assert digest1 != digest0
181+
assert digest1 == digest_expected

0 commit comments

Comments
 (0)