Skip to content

Commit 916b091

Browse files
JorjMcKiejulian-smith-artifex-com
authored andcommitted
Add new Page utility method
Add `Page.replace_image()`, `Page.delete_image()` to globally replace / delete an image given by its xref.
1 parent 53a723f commit 916b091

File tree

3 files changed

+113
-17
lines changed

3 files changed

+113
-17
lines changed

docs/page.rst

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ In a nutshell, this is what you can do with PyMuPDF:
6262
:meth:`Page.apply_redactions` PDF olny: process the redactions of the page
6363
:meth:`Page.bound` rectangle of the page
6464
:meth:`Page.delete_annot` PDF only: delete an annotation
65+
:meth:`Page.delete_image` PDF only: delete an image
6566
:meth:`Page.delete_link` PDF only: delete a link
6667
:meth:`Page.delete_widget` PDF only: delete a widget / field
6768
:meth:`Page.draw_bezier` PDF only: draw a cubic Bezier curve
@@ -100,6 +101,7 @@ In a nutshell, this is what you can do with PyMuPDF:
100101
:meth:`Page.load_widget` PDF only: load a specific field
101102
:meth:`Page.load_links` return the first link on a page
102103
:meth:`Page.new_shape` PDF only: create a new :ref:`Shape`
104+
:meth:`Page.replace_image` PDF only: replace an image
103105
:meth:`Page.search_for` search for a string
104106
:meth:`Page.set_artbox` PDF only: modify ``/ArtBox``
105107
:meth:`Page.set_bleedbox` PDF only: modify ``/BleedBox``
@@ -949,6 +951,45 @@ In a nutshell, this is what you can do with PyMuPDF:
949951

950952
6. Another efficient way to display the same image on multiple pages is another method: :meth:`show_pdf_page`. Consult :meth:`Document.convert_to_pdf` for how to obtain intermediary PDFs usable for that method. Demo script `fitz-logo.py <https://github.com/pymupdf/PyMuPDF-Utilities/tree/master/demo/fitz-logo.py>`_ implements a fairly complete approach.
951953

954+
955+
.. index::
956+
pair: filename; replace_image
957+
pair: pixmap; replace_image
958+
pair: stream; replace_image
959+
pair: xref; replace_image
960+
961+
.. method:: replace_image(xref, filename=None, pixmap=None, stream=None)
962+
963+
* New in v1.21.0
964+
965+
Replace the image at xref with another one.
966+
967+
:arg int xref: the :data:`xref` of the image.
968+
:arg filename: the filename of the new image.
969+
:arg pixmap: the :ref:`Pixmap` of the new image.
970+
:arg stream: the memory area containing the new image.
971+
972+
Arguments ``filename``, ``pixmap``, ``stream`` have the same meaning as in :meth:`Page.insert_image`, especially exactly one of these must be provided.
973+
974+
This is a **global replacement:** the new image will also be shown wherever the old one has been displayed throughout the file.
975+
976+
This method mainly exists for technical purposes. Typical uses include replacing large images by smaller versions, like a lower resolution, graylevel instead of colored, etc., or changing transparency.
977+
978+
979+
.. index::
980+
pair: xref; delete_image
981+
982+
.. method:: delete_image(xref)
983+
984+
* New in v1.21.0
985+
986+
Delete the image at xref. This is slightly misleading: actually the image is being replaced with a small transparent :ref:`Pixmap` using above :meth:`Page.replace_image`. The visible effect however is equivalent.
987+
988+
:arg int xref: the :data:`xref` of the image.
989+
990+
This is a **global replacement:** the image will disappear wherever the old one has been displayed throughout the file.
991+
992+
952993
.. index::
953994
pair: blocks; Page.get_text
954995
pair: dict; Page.get_text

fitz/__init__.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
# maintained and developed by Artifex Software, Inc. https://artifex.com.
88
# ------------------------------------------------------------------------
99
import sys
10+
1011
from fitz.fitz import *
1112

1213
# define the supported colorspaces for convenience
@@ -31,12 +32,16 @@
3132
# This atexit handler runs, but doesn't cause ~Tools() to be run.
3233
#
3334
import atexit
34-
def cleanup_tools( TOOLS):
35-
#print(f'cleanup_tools: TOOLS={TOOLS} id(TOOLS)={id(TOOLS)}')
36-
#print(f'TOOLS.thisown={TOOLS.thisown}')
35+
36+
37+
def cleanup_tools(TOOLS):
38+
# print(f'cleanup_tools: TOOLS={TOOLS} id(TOOLS)={id(TOOLS)}')
39+
# print(f'TOOLS.thisown={TOOLS.thisown}')
3740
del TOOLS
3841
del fitz.TOOLS
39-
atexit.register( cleanup_tools, TOOLS)
42+
43+
44+
atexit.register(cleanup_tools, TOOLS)
4045

4146
if fitz.VersionFitz != fitz.TOOLS.mupdf_version():
4247
v1 = fitz.VersionFitz.split(".")
@@ -50,7 +55,6 @@ def cleanup_tools( TOOLS):
5055
# copy functions in 'utils' to their respective fitz classes
5156
import fitz.utils
5257

53-
5458
# ------------------------------------------------------------------------------
5559
# General
5660
# ------------------------------------------------------------------------------
@@ -127,6 +131,8 @@ def cleanup_tools( TOOLS):
127131
fitz.Page.get_label = fitz.utils.get_label
128132
fitz.Page.get_image_rects = fitz.utils.get_image_rects
129133
fitz.Page.get_textpage_ocr = fitz.utils.get_textpage_ocr
134+
fitz.Page.delete_image = fitz.utils.delete_image
135+
fitz.Page.replace_image = fitz.utils.replace_image
130136

131137
# ------------------------------------------------------------------------
132138
# Annot

fitz/utils.py

Lines changed: 61 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,12 @@
1212
import os
1313
import random
1414
import string
15+
import tempfile
1516
import typing
1617
import warnings
17-
import tempfile
1818

1919
from fitz import *
2020

21-
2221
TESSDATA_PREFIX = os.environ.get("TESSDATA_PREFIX")
2322
point_like = "point_like"
2423
rect_like = "rect_like"
@@ -236,6 +235,51 @@ def calc_matrix(sr, tr, keep=True, rotate=0):
236235
return xref
237236

238237

238+
def replace_image(page: Page, xref: int, *, filename=None, pixmap=None, stream=None):
239+
"""Replace the image referred to by xref.
240+
241+
Replace the image by changing the object definition stored under xref. This
242+
will leave the pages appearance instructions intact, so the new image is
243+
being displayed with the same bbox, rotation etc.
244+
By providing a small fully transparent image, an effect as if the image had
245+
been deleted can be achieved.
246+
A typical use may include replacing large images by a smaller version,
247+
e.g. with a lower resolution or graylevel instead of colored.
248+
249+
Args:
250+
xref: the xref of the image to replace.
251+
filename, pixmap, stream: exactly one of these must be provided. The
252+
meaning being the same as in Page.insert_image.
253+
"""
254+
doc = page.parent # the owning document
255+
if not doc.is_image(xref):
256+
raise ValueError("xref not an image") # insert new image anywhere in page
257+
if bool(filename) + bool(stream) + bool(pixmap) != 1:
258+
raise ValueError("Exactly one of filename/stream/pixmap must be given")
259+
new_xref = page.insert_image(
260+
page.rect, filename=filename, stream=stream, pixmap=pixmap
261+
)
262+
doc.xref_copy(new_xref, xref) # copy over new to old
263+
last_contents_xref = page.get_contents()[-1]
264+
# new image insertion has created a new /Contents source,
265+
# which we will set to spaces now
266+
doc.update_stream(last_contents_xref, b" ")
267+
268+
269+
def delete_image(page: Page, xref: int):
270+
"""Delete the image referred to by xef.
271+
272+
Actually replaces by a small transparent Pixmap using method Page.replace_image.
273+
274+
Args:
275+
xref: xref of the image to delete.
276+
"""
277+
# make a small 100% transparent pixmap (of just any dimension)
278+
pix = fitz.Pixmap(fitz.csGRAY, (0, 0, 1, 1), 1)
279+
pix.clear_with() # clear all samples bytes to 0x00
280+
page.replace_image(xref, pixmap=pix)
281+
282+
239283
def insert_image(page, rect, **kwargs):
240284
"""Insert an image for display in a rectangle.
241285
@@ -810,15 +854,15 @@ def get_page_text(
810854

811855

812856
def get_pixmap(
813-
page: Page,
814-
*,
815-
matrix: matrix_like=Identity,
816-
dpi=None,
817-
colorspace: Colorspace=csRGB,
818-
clip: rect_like=None,
819-
alpha: bool=False,
820-
annots: bool=True,
821-
) -> Pixmap:
857+
page: Page,
858+
*,
859+
matrix: matrix_like = Identity,
860+
dpi=None,
861+
colorspace: Colorspace = csRGB,
862+
clip: rect_like = None,
863+
alpha: bool = False,
864+
annots: bool = True,
865+
) -> Pixmap:
822866
"""Create pixmap of page.
823867
824868
Keyword args:
@@ -876,7 +920,12 @@ def get_page_pixmap(
876920
annots: (bool) also render annotations
877921
"""
878922
return doc[pno].get_pixmap(
879-
matrix=matrix, dpi=dpi, colorspace=colorspace, clip=clip, alpha=alpha, annots=annots
923+
matrix=matrix,
924+
dpi=dpi,
925+
colorspace=colorspace,
926+
clip=clip,
927+
alpha=alpha,
928+
annots=annots,
880929
)
881930

882931

0 commit comments

Comments
 (0)