Skip to content

Commit

Permalink
Fix for Pixmap.set_pixel().
Browse files Browse the repository at this point in the history
Also added optimisation in src.extra.i.

Addresses #3050.
  • Loading branch information
julian-smith-artifex-com committed Jan 16, 2024
1 parent 274e351 commit 1eecf9c
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 4 deletions.
23 changes: 19 additions & 4 deletions src/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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."""
Expand All @@ -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):
Expand Down
59 changes: 59 additions & 0 deletions src/extra.i
Original file line number Diff line number Diff line change
Expand Up @@ -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++;
Expand Down Expand Up @@ -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
//-------------------------------------------
Expand Down Expand Up @@ -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);
27 changes: 27 additions & 0 deletions tests/test_pixmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 1eecf9c

Please sign in to comment.