Skip to content

Commit 5d54b40

Browse files
committed
Image stamp annotation support
1 parent 421b688 commit 5d54b40

File tree

4 files changed

+64
-12
lines changed

4 files changed

+64
-12
lines changed

docs/document.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ For details on **embedded files** refer to Appendix 3.
9696
:meth:`Document.pdf_catalog` PDF only: :data:`xref` of catalog (root)
9797
:meth:`Document.pdf_trailer` PDF only: trailer source
9898
:meth:`Document.prev_location` return (chapter, pno) of preceding page
99+
:meth:`Document.recolor` PDF only: execute :meth:`Page.recolor` for all pages
99100
:meth:`Document.reload_page` PDF only: provide a new copy of a page
100101
:meth:`Document.resolve_names` PDF only: Convert destination names into a Python dict
101102
:meth:`Document.save` PDF only: save the document
@@ -594,6 +595,16 @@ For details on **embedded files** refer to Appendix 3.
594595

595596
To maintain a consistent API, for document types not supporting a chapter structure (like PDFs), :attr:`Document.chapter_count` is 1, and pages can also be loaded via tuples *(0, pno)*. See this [#f3]_ footnote for comments on performance improvements.
596597

598+
599+
.. method:: recolor(components=1)
600+
601+
PDF only: Change the color component counts for all object types text, image and vector graphics for all pages.
602+
603+
:arg int components: desired color space indicated by the number of color components: 1 = DeviceGRAY, 3 = DeviceRGB, 4 = DeviceCMYK.
604+
605+
The typical use case is 1 (DeviceGRAY) which converts the PDF to grayscale.
606+
607+
597608
.. method:: reload_page(page)
598609

599610
* New in v1.16.10

docs/images/img-imagestamp.png

160 KB
Loading

docs/page.rst

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -536,24 +536,33 @@ In a nutshell, this is what you can do with PyMuPDF:
536536
There is also the `pdf2docx extract tables method`_ which is capable of table extraction if you prefer.
537537

538538

539-
.. method:: add_stamp_annot(rect, stamp=0)
539+
.. method:: add_stamp_annot(rect, stamp=0, *, image=None)
540540

541-
PDF only: Add a "rubber stamp" like annotation to e.g. indicate the document's intended use ("DRAFT", "CONFIDENTIAL", etc.).
541+
PDF only: Add a "rubber stamp"-like annotation to e.g. indicate the document's intended use ("DRAFT", "CONFIDENTIAL", etc.). Instead of text, an image may also be shown.
542542

543543
:arg rect_like rect: rectangle where to place the annotation.
544-
545544
:arg int stamp: id number of the stamp text. For available stamps see :ref:`StampIcons`.
545+
:arg multiple image: if not ``None``, an image specification is assumed and the ``stamp`` parameter will be ignored. Valid argument types are
546+
547+
* a string specifying an image file path,
548+
* a ``bytes``, ``bytearray`` or ``io.BytesIO`` object for an image in memory, and
549+
* a :ref:`Pixmap`.
550+
551+
1. **Text-based stamps**
546552

547-
.. note::
548-
549-
* The stamp's text and its border line will automatically be sized and be put horizontally and vertically centered in the given rectangle. :attr:`Annot.rect` is automatically calculated to fit the given **width** and will usually be smaller than this parameter.
553+
* :attr:`Annot.rect` is automatically calculated as the largest rectangle with an aspect ratio of ``width/height = 3.8`` that fits in the provided ``rect``. Its position is vertically and horizontally centered.
550554
* The font chosen is "Times Bold" and the text will be upper case.
551-
* The appearance can be changed using :meth:`Annot.set_opacity` and by setting the "stroke" color (no "fill" color supported).
552-
* This can be used to create watermark images: on a temporary PDF page create a stamp annotation with a low opacity value, make a pixmap from it with *alpha=True* (and potentially also rotate it), discard the temporary PDF page and use the pixmap with :meth:`insert_image` for your target PDF.
555+
* The appearance can be modified using :meth:`Annot.set_opacity` and by setting the "stroke" color. By PDF specification, stamp annotations have no "fill" color.
556+
557+
.. image:: images/img-stampannot.*
553558

559+
2. **Image-based stamps**
554560

555-
.. image:: images/img-stampannot.*
556-
:scale: 80
561+
* At first, a rectangle is computed like for text stamps: vertically and horizontally centered, aspect ratio ``width/height = 3.8``.
562+
* Into that rectangle, the image will be inserted aligned left and vertically centered. The resulting image boundary box becomes :attr:`Annot.rect`.
563+
* The annotation can be modified via :meth:`Annot.set_opacity`. This is a way to display images without alpha channel with transparency. Setting colors has no effect on image stamps.
564+
565+
.. image:: images/img-imagestamp.*
557566

558567
.. method:: add_widget(widget)
559568

@@ -1929,7 +1938,7 @@ In a nutshell, this is what you can do with PyMuPDF:
19291938

19301939
PDF only: Change the colorspace components of all objects on page.
19311940

1932-
:arg int components: The desired count of color components. Must be one of 1, 3 or 4, which results in color space DeviceGray, DeviceRGB and DeviceCMYK respectively. The method affects text, images and vector graphics. For instance, with the default value 1, a page will be converted to gray-scale.
1941+
:arg int components: The desired count of color components. Must be one of 1, 3 or 4, which results in color spaces DeviceGray, DeviceRGB or DeviceCMYK respectively. The method affects text, images and vector graphics. For instance, with the default value 1, a page will be converted to gray-scale.
19331942

19341943
The changes made are **permanent** and cannot be reverted.
19351944

src/__init__.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5444,6 +5444,19 @@ def resolve_link(self, uri=None, chapters=0):
54445444
pno = mupdf.fz_page_number_from_location(self.this, loc)
54455445
return pno, xp, yp
54465446

5447+
def recolor(self, components=1):
5448+
"""Change the color component count on all pages.
5449+
5450+
Args:
5451+
components: (int) desired color component count, one of 1, 3, 4.
5452+
5453+
Invokes the same-named method for all pages.
5454+
"""
5455+
if not self.is_pdf:
5456+
raise ValueError("is no PDF")
5457+
for i in range(self.page_count):
5458+
self.load_page(i).recolor(components)
5459+
54475460
def resolve_names(self):
54485461
"""Convert the PDF's destination names into a Python dict.
54495462

@@ -8604,11 +8617,30 @@ def add_squiggly_annot(
86048617
q = CheckMarkerArg(quads)
86058618
return self._add_text_marker(q, mupdf.PDF_ANNOT_SQUIGGLY)
86068619

8607-
def add_stamp_annot(self, rect: rect_like, stamp: int =0) -> Annot:
8620+
def add_stamp_annot(self, rect: rect_like, stamp: int =0, *, image=None) -> Annot:
86088621
"""Add a ('rubber') 'Stamp' annotation."""
8622+
if isinstance(image, Pixmap):
8623+
buf = image.tobytes()
8624+
elif isinstance(image, str):
8625+
buf = pathlib.Path(image).read_bytes()
8626+
elif isinstance(image, (bytes, bytearray)):
8627+
buf = image
8628+
elif isinstance(image, io.BytesIO):
8629+
buf = image.getvalue()
8630+
else:
8631+
buf = None
86098632
old_rotation = annot_preprocess(self)
86108633
try:
86118634
annot = self._add_stamp_annot(rect, stamp)
8635+
if buf:
8636+
fzbuff = mupdf.fz_new_buffer_from_copied_data(buf)
8637+
img = mupdf.fz_new_image_from_buffer(fzbuff)
8638+
mupdf.pdf_set_annot_stamp_image(annot, img)
8639+
self.parent.xref_set_key(annot.xref, "Name", "null")
8640+
self.parent.xref_set_key(
8641+
annot.xref, "Contents", "(Image Stamp)")
8642+
buf = None
8643+
fzbuff = None
86128644
finally:
86138645
if old_rotation != 0:
86148646
self.set_rotation(old_rotation)

0 commit comments

Comments
 (0)