首页 > 其他分享 >PyMuPDF-1-24-4-中文文档-三-

PyMuPDF-1-24-4-中文文档-三-

时间:2024-06-17 15:56:26浏览次数:13  
标签:24 pymupdf annot PyMuPDF 文档 图像 PDF page rect

PyMuPDF 1.24.4 中文文档(三)

原文:https://pymupdf.readthedocs.io/en/latest/

图像

原文:pymupdf.readthedocs.io/en/latest/recipes-images.html

如何从文档页面创建图像

这个小脚本将获取一个文档文件名并生成每页的 PNG 文件。

文档可以是任何 支持的类型。

脚本作为一个命令行工具运行,需要将文件名作为参数提供。生成的图像文件(每页一个)存储在脚本所在的目录中:

import sys, pymupdf  # import the bindings
fname = sys.argv[1]  # get filename from command line
doc = pymupdf.open(fname)  # open document
for page in doc:  # iterate through the pages
    pix = page.get_pixmap()  # render page to an image
    pix.save("page-%i.png" % page.number)  # store image as a PNG 

脚本目录现在将包含名为 page-0.pngpage-1.png 等的 PNG 图像文件。图片的尺寸是其页面的宽度和高度取整,例如 A4 纵向页面的尺寸为 595 x 842 像素。它们的 x 和 y 维度分辨率为 96 dpi,并且没有透明度。您可以更改所有这些内容 - 要了解如何执行此操作,请阅读以下各节。

      • 如何增加图像分辨率

文档页面的图像由一个 Pixmap 表示,创建 Pixmap 的最简单方法是通过方法 Page.get_pixmap()

此方法有许多选项可以影响结果。其中最重要的是 Matrix,它允许您缩放、旋转、扭曲或镜像结果。

Page.get_pixmap() 默认将使用 Identity 矩阵,不起任何作用。

在以下示例中,我们对每个维度应用了 2 倍的缩放因子,这将为我们生成一个四倍更好分辨率的图像(并且大约增加了 4 倍的大小):

zoom_x = 2.0  # horizontal zoom
zoom_y = 2.0  # vertical zoom
mat = pymupdf.Matrix(zoom_x, zoom_y)  # zoom factor 2 in each dimension
pix = page.get_pixmap(matrix=mat)  # use 'mat' instead of the identity matrix 

自从版本 1.19.2 开始,有一种更直接的设置分辨率的方法:可以使用参数 "dpi"(每英寸点数)代替 "matrix"。要创建一个 300 dpi 分辨率的页面图像,请指定 pix = page.get_pixmap(dpi=300)。除了简洁的标记法之外,这种方法的额外优势在于 dpi 值保存在图像文件中,而在使用矩阵标记时不会自动发生这种情况。

      • 如何创建部分图像(剪辑)

并不总是需要或希望获取页面的完整图像。例如,在您将图像显示在 GUI 中并希望填充页面的缩放部分时,就是这种情况。

假设您的 GUI 窗口可以显示完整的文档页面,但现在您希望将页面的右下角四分之一填充到这个区域,从而使用四倍更好的分辨率。

要实现这一点,请定义一个与 GUI 中要显示的区域相等的矩形并称其为“clip”。在 PyMuPDF 中构造矩形的一种方法是提供两个对角相对的角,这就是我们在这里做的。

_images/img-clip.jpg

mat = pymupdf.Matrix(2, 2)  # zoom factor 2 in each direction
rect = page.rect  # the page rectangle
mp = (rect.tl + rect.br) / 2  # its middle point, becomes top-left of clip
clip = pymupdf.Rect(mp, rect.br)  # the area we want
pix = page.get_pixmap(matrix=mat, clip=clip) 

在上述过程中,我们通过指定两个对角点构造clip:页面矩形的中点mp和其右下角rect.br

      • 如何将剪辑缩放到 GUI 窗口

请还要阅读前面的部分。这次我们想要计算一个剪辑的缩放因子,使其图像最适合给定的 GUI 窗口。这意味着图像的宽度或高度(或两者)将等于窗口尺寸。对于下面的代码段,您需要提供 GUI 窗口的 WIDTH 和 HEIGHT,以接收页面的剪辑矩形。

# WIDTH: width of the GUI window
# HEIGHT: height of the GUI window
# clip: a subrectangle of the document page
# compare width/height ratios of image and window

if clip.width / clip.height < WIDTH / HEIGHT:
    # clip is narrower: zoom to window HEIGHT
    zoom = HEIGHT / clip.height
else:  # clip is broader: zoom to window WIDTH
    zoom = WIDTH / clip.width
mat = pymupdf.Matrix(zoom, zoom)
pix = page.get_pixmap(matrix=mat, clip=clip) 

对于另一种方式,现在假设你缩放因子,并且需要计算适合的剪辑

在这种情况下,我们有zoom = HEIGHT/clip.height = WIDTH/clip.width,因此我们必须设置clip.height = HEIGHT/zoomclip.width = WIDTH/zoom。选择页面上剪辑的左上点tl来计算正确的像素图:

width = WIDTH / zoom
height = HEIGHT / zoom
clip = pymupdf.Rect(tl, tl.x + width, tl.y + height)
# ensure we still are inside the page
clip &= page.rect
mat = pymupdf.Matrix(zoom, zoom)
pix = pymupdf.Pixmap(matrix=mat, clip=clip) 
      • 如何创建或禁止注释图像

通常,页面的像素图还显示页面的注释。偶尔,这可能不是理想的。

要在渲染页面上禁止注释图像,只需在Page.get_pixmap()中指定annots=False

你也可以单独渲染注释:它们有自己的Annot.get_pixmap()方法。生成的像素图的尺寸与注释矩形相同。

      • 如何提取图像:非 PDF 文档

与前面的部分相比,本节处理从文档中提取包含的图像,以便它们可以作为一个或多个页面的一部分显示。

如果你想要将原始图像重新创建为文件形式或内存区域,基本上有两个选择:

  1. 将文档转换为 PDF,然后使用 PDF-only 提取方法之一。此片段将文档转换为 PDF:

    >>> pdfbytes = doc.convert_to_pdf()  # this a bytes object
    >>> pdf = pymupdf.open("pdf", pdfbytes)  # open it as a PDF document
    >>> # now use 'pdf' like any PDF document 
    
  2. 使用带有“dict”参数的Page.get_text()。这适用于所有文档类型。它将提取页面上显示的所有文本和图像,格式化为 Python 字典。每个图像将出现在一个图像块中,包含元信息和二进制图像数据。有关字典结构的详细信息,请参阅 TextPage。该方法同样适用于 PDF 文件。这将创建一个显示在页面上所有图像的列表:

    >>> d = page.get_text("dict")
    >>> blocks = d["blocks"]  # the list of block dictionaries
    >>> imgblocks = [b for b in blocks if b["type"] == 1]
    >>> pprint(imgblocks[0])
    {'bbox': (100.0, 135.8769989013672, 300.0, 364.1230163574219),
     'bpc': 8,
     'colorspace': 3,
     'ext': 'jpeg',
     'height': 501,
     'image': b'\xff\xd8\xff\xe0\x00\x10JFIF\...',  # CAUTION: LARGE!
     'size': 80518,
     'transform': (200.0, 0.0, -0.0, 228.2460174560547, 100.0, 135.8769989013672),
     'type': 1,
     'width': 439,
     'xres': 96,
     'yres': 96} 
    
      • 如何提取图像:PDF 文档

就像 PDF 中的任何其他“对象”一样,图像由交叉引用号(xref,一个整数)标识。如果你知道这个号码,你有两种方式访问图像的数据:

  1. 使用指令pix = pymupdf.Pixmap(doc, xref)创建 Pixmap 图像。此方法非常快(单个数字微秒)。像素图的属性(宽度、高度等)将反映图像的属性。在这种情况下,无法确定嵌入原始图像的格式。

  2. 提取图像使用img = doc.extract_image(xref)。 这是一个包含二进制图像数据的字典,如img[“image”]所示。还提供了许多元数据,大多数与图像的像素图中的内容相同。主要区别在于字符串img[“ext”],它指定图像格式:除了“png”外,还可能出现像“jpeg”、“bmp”、“tiff”等。如果要存储到磁盘,请使用此字符串作为文件扩展名。与pix = pymupdf.Pixmap(doc, xref);pix.tobytes()语句的组合速度相比,此方法的执行速度应该要快。如果嵌入的图像是 PNG 格式,则Document.extract_image()的速度大致相同(且二进制图像数据相同)。否则,此方法快几千倍,而且图像数据要小得多

问题是:“我怎么知道这些‘xref’图像的编号呢?” 这有两个答案:

  1. “检查页面对象:” 遍历Page.get_images()的项目。它是一个列表的列表,其项目看起来像[xref, smask, …],包含一个图像的 xref。然后可以使用其中一种以上的方法。对于有效(未损坏)的文档,请使用此方法。但请注意,同一图像可能被多个页面引用,因此可能需要提供一个机制以避免多次提取。

  2. “不需要知道:” 遍历文档的所有 xrefs 列表,并对每个执行Document.extract_image()。如果返回的字典为空,则继续 – 此 xref 不是图像。如果 PDF 文件损坏(不可用页面),请使用此方法。请注意,PDF 文件通常包含“伪图像”(“模板掩码”),用于定义其他图像的透明度。您可能希望提供逻辑以排除这些内容的提取。还可以查看下一节。

对于这两种提取方法,都存在可用的通用脚本:

extract-from-pages.py 逐页提取图像:

_images/img-extract-imga.jpg

extract-from-xref.py通过 xref 表提取图像:

_images/img-extract-imgb.jpg

      • 如何处理图像蒙版

PDF 中的一些图像附带图像蒙版。在最简单的形式中,蒙版表示为单独存储的 alpha(透明度)字节图像。为了重建具有蒙版的图像的原始图像,必须使用蒙版中的透明度字节对其进行“丰富”。

在 PyMuPDF 中,判断图像是否具有蒙版可以通过以下两种方式之一进行识别:

  1. Document.get_page_images()的一项具有一般格式(xref, smask, ...), 其中xref是图像的xref,如果smask为正,则是蒙版的xref

  2. Document.extract_image()的结果(字典)具有一个键“smask”,该键还包含任何蒙版的xref(如果为正)。

如果smask == 0,那么通过xref遇到的图像可以按原样处理。

使用 PyMuPDF 恢复原始图像,必须执行以下所示的过程:

_images/img-stencil.jpg

>>> pix1 = pymupdf.Pixmap(doc.extract_image(xref)["image"])    # (1) pixmap of image w/o alpha
>>> mask = pymupdf.Pixmap(doc.extract_image(smask)["image"])   # (2) mask pixmap
>>> pix = pymupdf.Pixmap(pix1, mask)                           # (3) copy of pix1, image mask added 

第一步创建基本图像的位图。第二步对图像蒙版执行相同操作。第三步添加一个 alpha 通道并填充透明度信息。

上面的脚本extract-from-pages.pyextract-from-xref.py也包含此逻辑。

      • 如何制作所有图片(或文件)的一个 PDF

我们在这里展示三个脚本,它们接受一个(图像和其他)文件列表,并将它们全部放入一个 PDF 中。

方法 1: 将图像插入为页面

第一个方法将每个图像转换为具有相同尺寸的 PDF 页面。结果将是一个 PDF,每个图像占一页。它仅适用于支持的图像文件格式:

import os, pymupdf
import PySimpleGUI as psg  # for showing a progress bar
doc = pymupdf.open()  # PDF with the pictures
imgdir = "D:/2012_10_05"  # where the pics are
imglist = os.listdir(imgdir)  # list of them
imgcount = len(imglist)  # pic count

for i, f in enumerate(imglist):
    img = pymupdf.open(os.path.join(imgdir, f))  # open pic as document
    rect = img[0].rect  # pic dimension
    pdfbytes = img.convert_to_pdf()  # make a PDF stream
    img.close()  # no longer needed
    imgPDF = pymupdf.open("pdf", pdfbytes)  # open stream as PDF
    page = doc.new_page(width = rect.width,  # new page with ...
                       height = rect.height)  # pic dimension
    page.show_pdf_page(rect, imgPDF, 0)  # image fills the page
    psg.EasyProgressMeter("Import Images",  # show our progress
        i+1, imgcount)

doc.save("all-my-pics.pdf") 

这将生成一个 PDF,大小仅比合并的图片总大小略大。一些性能数据:

上述脚本在我的机器上对 149 张总大小为 514 MB 的图片需要大约 1 分钟时间(生成的 PDF 大小大致相同)。

_images/img-import-progress.jpg

查看这里获取更完整的源代码:它提供了一个目录选择对话框,并跳过不支持的文件和非文件条目。

注意

我们本可以使用Page.insert_image()而不是Page.show_pdf_page()来创建一个外观类似的文件。然而,根据图像类型的不同,可能会存储未压缩的图像。因此,必须使用保存选项deflate = True以获得合理的文件大小,这会大大增加处理大量图像的运行时间。因此,在此不推荐使用这种替代方法。

方法 2:嵌入文件

第二个脚本嵌入任意文件 - 不仅仅是图片。由于技术原因,生成的 PDF 将只有一页(空白页)。要稍后再次访问嵌入的文件,您需要一个能够显示和/或提取嵌入文件的合适 PDF 查看器:

import os, pymupdf
import PySimpleGUI as psg  # for showing progress bar
doc = pymupdf.open()  # PDF with the pictures
imgdir = "D:/2012_10_05"  # where my files are

imglist = os.listdir(imgdir)  # list of pictures
imgcount = len(imglist)  # pic count
imglist.sort()  # nicely sort them

for i, f in enumerate(imglist):
    img = open(os.path.join(imgdir,f), "rb").read()  # make pic stream
    doc.embfile_add(img, f, filename=f,  # and embed it
                        ufilename=f, desc=f)
    psg.EasyProgressMeter("Embedding Files",  # show our progress
        i+1, imgcount)

page = doc.new_page()  # at least 1 page is needed

doc.save("all-my-pics-embedded.pdf") 

_images/img-embed-progress.jpg

这绝对是最快的方法,同时也能生成最小可能的输出文件大小。上述图片在我的设备上花了 20 秒,并生成了一个大小为 510 MB 的 PDF 文件。查看这里获取更完整的源代码:它提供了一个目录选择对话框,并跳过非文件条目。

方法 3:附加文件

第三种完成此任务的方法是通过页面注释附加文件,请参见此处获取完整的源代码。

这与前一个脚本具有类似的性能,并且还会产生类似的文件大小。它将为每个附加文件生成显示“文件附件”图标的 PDF 页面。

_images/img-attach-result.jpg

注意

嵌入附加方法都可以用于任意文件 - 不只是图片。

注意

我们强烈推荐使用令人惊叹的包PySimpleGUI来显示任务可能运行时间较长的进度条。它纯粹使用 Python,使用 Tkinter(无需额外的 GUI 包),并且只需要增加一行代码!

      • 如何创建矢量图像

创建文档页面图像的常规方法是Page.get_pixmap()。像素图表示栅格图像,因此您必须在创建时决定其质量(即分辨率)。以后不能更改。

PyMuPDF 还提供了一种方法来创建页面的矢量图像,以 SVG 格式(可伸缩矢量图形,以 XML 语法定义)。SVG 图像在缩放级别上保持精确(当然,其中嵌入的任何栅格图形元素除外)。

指令 svg = page.get_svg_image(matrix=pymupdf.Identity) 提供了一个 UTF-8 字符串 svg,可以使用扩展名“.svg”存储。

      • 如何转换图像

作为一个特性,PyMuPDF 的图像转换很简单。在许多情况下,可以避免使用其他图形包如 PIL/Pillow。

尽管与 Pillow 的交互几乎是微不足道的。

输入格式 输出格式 描述
BMP . Windows 位图
JPEG JPEG 联合图像专家组
JXR . JPEG 扩展范围
JPX/JP2 . JPEG 2000
GIF . 图形互换格式
TIFF . 标记图像文件格式
PNG PNG 便携网络图形
PNM PNM 便携任意映射
PGM PGM 便携灰度图
PBM PBM 便携位图
PPM PPM 便携像素图
PAM PAM 便携任意映射
. PSD Adobe Photoshop 文档
. PS Adobe Postscript

一般方案仅为以下两行:

pix = pymupdf.Pixmap("input.xxx")  # any supported input format
pix.save("output.yyy")  # any supported output format 

备注

  1. pymupdf.Pixmap(arg)input 参数可以是包含图像的文件或 bytes / io.BytesIO 对象。

  2. 不仅仅可以创建输出 文件,还可以通过 pix.tobytes(“yyy”) 创建一个 bytes 对象并传递它。

  3. 当然,输入和输出格式在颜色空间和透明度方面必须兼容。如果需要调整,Pixmap 类已经内置了电池。

注意

将 JPEG 转换为 Photoshop

pix = pymupdf.Pixmap("myfamily.jpg")
pix.save("myfamily.psd") 

注意

JPEG 转换为 Tkinter 的 PhotoImage。任何 RGB / 无 alpha 图像效果相同。将其转换为一个 便携任意映射 格式(如 PPM、PGM 等)即可,因为所有的 Tkinter 版本都支持它们:

import tkinter as tk
pix = pymupdf.Pixmap("input.jpg")  # or any RGB / no-alpha image
tkimg = tk.PhotoImage(data=pix.tobytes("ppm")) 

注意

带 alpha 通道的 PNG 转换为 Tkinter 的 PhotoImage,这需要在进行 PPM 转换之前 移除 alpha 字节

import tkinter as tk
pix = pymupdf.Pixmap("input.png")  # may have an alpha channel
if pix.alpha:  # we have an alpha channel!
    pix = pymupdf.Pixmap(pix, 0)  # remove it
tkimg = tk.PhotoImage(data=pix.tobytes("ppm")) 
      • 如何使用 Pixmaps: 粘贴图像

这显示了如何仅用于图形、非文档目的的 pixmap。脚本读取一个图像文件并创建一个新图像,该图像由原始图像的 3 * 4 个瓦片组成:

import pymupdf
src = pymupdf.Pixmap("img-7edges.png")      # create pixmap from a picture
col = 3                                  # tiles per row
lin = 4                                  # tiles per column
tar_w = src.width * col                  # width of target
tar_h = src.height * lin                 # height of target

# create target pixmap
tar_pix = pymupdf.Pixmap(src.colorspace, (0, 0, tar_w, tar_h), src.alpha)

# now fill target with the tiles
for i in range(col):
    for j in range(lin):
        src.set_origin(src.width * i, src.height * j)
        tar_pix.copy(src, src.irect) # copy input to new loc

tar_pix.save("tar.png") 

这是输入图片:

_images/img-7edges.png

这是输出:

_images/img-target.png

      • 如何使用 Pixmaps: 制作分形

这是另一个 Pixmap 的示例,创建了 谢尔宾斯基地毯 —— 一种将 康托集 广义化到二维的分形。给定一个正方形地毯,标记其 9 个子方块(3 * 3),并切掉中心的一个。以相同方式处理每个剩余的八个子方块,并继续 ad infinitum。最终结果是一个面积为零且分形维度为 1.8928... 的集合。

此脚本以 PNG 的形式创建其近似图像,通过一像素粒度下降。要增加图像精度,改变 n 的值(精度):

import pymupdf, time
if not list(map(int, pymupdf.VersionBind.split("."))) >= [1, 14, 8]:
    raise SystemExit("need PyMuPDF v1.14.8 for this script")
n = 6                             # depth (precision)
d = 3**n                          # edge length

t0 = time.perf_counter()
ir = (0, 0, d, d)                 # the pixmap rectangle

pm = pymupdf.Pixmap(pymupdf.csRGB, ir, False)
pm.set_rect(pm.irect, (255,255,0)) # fill it with some background color

color = (0, 0, 255)               # color to fill the punch holes

# alternatively, define a 'fill' pixmap for the punch holes
# this could be anything, e.g. some photo image ...
fill = pymupdf.Pixmap(pymupdf.csRGB, ir, False) # same size as 'pm'
fill.set_rect(fill.irect, (0, 255, 255))   # put some color in

def punch(x, y, step):
  """Recursively "punch a hole" in the central square of a pixmap.

 Arguments are top-left coords and the step width.

 Some alternative punching methods are commented out.
 """
    s = step // 3                 # the new step
    # iterate through the 9 sub-squares
    # the central one will be filled with the color
    for i in range(3):
        for j in range(3):
            if i != j or i != 1:  # this is not the central cube
                if s >= 3:        # recursing needed?
                    punch(x+i*s, y+j*s, s)       # recurse
            else:                 # punching alternatives are:
                pm.set_rect((x+s, y+s, x+2*s, y+2*s), color)     # fill with a color
                #pm.copy(fill, (x+s, y+s, x+2*s, y+2*s))  # copy from fill
                #pm.invert_irect((x+s, y+s, x+2*s, y+2*s))       # invert colors

    return

#==============================================================================
# main program
#==============================================================================
# now start punching holes into the pixmap
punch(0, 0, d)
t1 = time.perf_counter()
pm.save("sierpinski-punch.png")
t2 = time.perf_counter()
print ("%g sec to create / fill the pixmap" % round(t1-t0,3))
print ("%g sec to save the image" % round(t2-t1,3)) 

结果应该看起来像这样:

_images/img-sierpinski.png

      • 如何与 NumPy 接口

这显示了如何从 NumPy 数组创建 PNG 文件(比大多数其他方法快几倍):

import numpy as np
import pymupdf
#==============================================================================
# create a fun-colored width * height PNG with pymupdf and numpy
#==============================================================================
height = 150
width  = 100
bild = np.ndarray((height, width, 3), dtype=np.uint8)

for i in range(height):
    for j in range(width):
        # one pixel (some fun coloring)
        bild[i, j] = [(i+j)%256, i%256, j%256]

samples = bytearray(bild.tostring())    # get plain pixel data from numpy array
pix = pymupdf.Pixmap(pymupdf.csRGB, width, height, samples, alpha=False)
pix.save("test.png") 
      • 如何向 PDF 页面添加图像

向 PDF 页面添加图像有两种方法:Page.insert_image()Page.show_pdf_page()。这两种方法有共同之处,但也有不同之处。

标准 Page.insert_image() Page.show_pdf_page()
可显示内容 图像文件,内存中的图像,像素图 PDF 页面
显示分辨率 图像分辨率 矢量化(除了位图页面内容)
旋转 0、90、180 或 270 度 任意角度
裁剪 否(仅完整图像)
保持长宽比 是(默认选项) 是(默认选项)
透明度(水印) 取决于图像 取决于页面
位置 / 放置 缩放以适应目标矩形 缩放以适应目标矩形
性能 自动防止重复; 自动防止重复;
多页图像支持
使用简便性 简单,直观; 简单,直观; 转换为 PDF 后适用于所有文档类型(包括图像!)通过 Document.convert_to_pdf()

Page.insert_image() 的基本代码模式。如果不是重新插入现有图像,则必须给出 文件名 / 流 / 像素图 参数中的一个

page.insert_image(
    rect,                  # where to place the image (rect-like)
    filename=None,         # image in a file
    stream=None,           # image in memory (bytes)
    pixmap=None,           # image from pixmap
    mask=None,             # specify alpha channel separately
    rotate=0,              # rotate (int, multiple of 90)
    xref=0,                # re-use existing image
    oc=0,                  # control visibility via OCG / OCMD
    keep_proportion=True,  # keep aspect ratio
    overlay=True,          # put in foreground
) 

Page.show_pdf_page() 的基本代码模式。源 PDF 和目标 PDF 必须是不同的 Document 对象(但可以从同一文件打开):

page.show_pdf_page(
    rect,                  # where to place the image (rect-like)
    src,                   # source PDF
    pno=0,                 # page number in source PDF
    clip=None,             # only display this area (rect-like)
    rotate=0,              # rotate (float, any value)
    oc=0,                  # control visibility via OCG / OCMD
    keep_proportion=True,  # keep aspect ratio
    overlay=True,          # put in foreground
) 
```  ## 如何使用像素图:检查文本可见性

文本是否实际可见取决于多个因素:

1.  文本没有被其他对象覆盖,但可能与背景色相同,例如白色字体在白色背景上等。

1.  文本可能被图像或矢量图覆盖。检测这一点是一项重要的功能,例如用于揭示不当匿名化的法律文件。

1.  文本是隐藏创建的。这种技术通常由 OCR 工具使用,将识别的文本存储在页面上的不可见层中。

下面展示了如何检测情况 1\. 或者情况 2\. 如果遮盖物是单色的话:

```py
pix = page.get_pixmap(dpi=150)  # make page image with a decent resolution

# the following matrix transforms page to pixmap coordinates
mat = page.rect.torect(pix.irect)

# search for some string "needle"
rlist = page.search_for("needle")
# check the visibility for each hit rectangle
for rect in rlist:
    if pix.color_topusage(clip=rect * mat)[0] > 0.95:
        print("'needle' is invisible here:", rect) 

方法 Pixmap.color_topusage() 返回一个元组 (ratio, pixel),其中 0 < ratio <= 1,pixel 是颜色的像素值。请注意,我们仅创建一次像素图。如果有多个命中矩形,这可以节省大量处理时间。

上述代码的逻辑是:如果针的矩形是“几乎”单色的(> 95%),则文本不可见。对于可见文本的典型结果返回背景色(通常是白色)和约为 0.7 到 0.8 的比率,例如 (0.685, b'xffxffxff')

你对这个页面有什么反馈吗?


本软件按原样提供,不提供任何明示或暗示的担保。本软件在许可下分发,并且除非在该许可条款明确授权下,否则不得复制、修改或分发。请参考 artifex.com 获取许可信息或联系 Artifex Software Inc.,39 Mesa Street,Suite 108A,San Francisco CA 94129,美国,以获取进一步的信息。

本文档覆盖所有版本,直至 1.24.4。

Discord logo ## 如何从文档页面创建图像

这个小脚本将接受文档文件名,并为每一页生成一个 PNG 文件。

文档可以是任何 支持的类型。

该脚本作为一个命令行工具运行,需要将文件名作为参数提供。生成的图像文件(每页一个)存储在脚本的目录中:

import sys, pymupdf  # import the bindings
fname = sys.argv[1]  # get filename from command line
doc = pymupdf.open(fname)  # open document
for page in doc:  # iterate through the pages
    pix = page.get_pixmap()  # render page to an image
    pix.save("page-%i.png" % page.number)  # store image as a PNG 

现在脚本目录下将包含命名为 page-0.pngpage-1.png 等的 PNG 图像文件。图片的尺寸与其页面的宽度和高度四舍五入为整数,例如 A4 纵向页面的尺寸为 595 x 842 像素。它们在 x 和 y 维度上具有 96 dpi 的分辨率,并且没有透明度。您可以更改所有这些内容-有关如何操作,请阅读下面的部分。


如何增加图像分辨率

文档页面的图像由 Pixmap 表示,创建像素图的最简单方法是通过方法 Page.get_pixmap()

此方法有许多选项可以影响结果。其中最重要的是 Matrix,它允许您缩放、旋转、扭曲或镜像结果。

Page.get_pixmap() 默认将使用 Identity 矩阵,即不进行任何操作。

在以下示例中,我们对每个维度应用了 2 倍的缩放因子,这将为我们生成具有四倍更好分辨率的图像(大约也是四倍的大小):

zoom_x = 2.0  # horizontal zoom
zoom_y = 2.0  # vertical zoom
mat = pymupdf.Matrix(zoom_x, zoom_y)  # zoom factor 2 in each dimension
pix = page.get_pixmap(matrix=mat)  # use 'mat' instead of the identity matrix 

自 1.19.2 版本起,有一种更直接的设置分辨率的方法:参数 "dpi"(每英寸点数)可以用于替换 "matrix"。要创建页面的 300 dpi 图像,请指定 pix = page.get_pixmap(dpi=300)。除了表示简洁外,这种方法的另一个优点是 dpi 值将与图像文件一起保存 —— 使用矩阵表示时不会自动发生这种情况。


如何创建部分位图(剪辑)

并非总是需要或想要完整的页面图像。例如,当您在 GUI 中显示图像并希望用页面的放大部分填充相应的窗口时,就是这种情况。

假设你的 GUI 窗口有足够的空间来显示完整的文档页面,但现在你想要用页面的右下角的四分之一来填充这个空间,从而使用四倍更好的分辨率。

为了实现这一点,请定义一个等于您希望显示在 GUI 中的区域的矩形,并将其命名为“剪辑”。在 PyMuPDF 中构建矩形的一种方式是提供两个对角线相对的角,这就是我们在这里做的事情。

_images/img-clip.jpg

mat = pymupdf.Matrix(2, 2)  # zoom factor 2 in each direction
rect = page.rect  # the page rectangle
mp = (rect.tl + rect.br) / 2  # its middle point, becomes top-left of clip
clip = pymupdf.Rect(mp, rect.br)  # the area we want
pix = page.get_pixmap(matrix=mat, clip=clip) 

在上面的例子中,我们通过指定两个对角线相对的点来构造剪辑:页面矩形的中点mp和它的右下角rect.br


如何将剪辑缩放到 GUI 窗口

请还请阅读前一节。这次我们想要计算剪辑的缩放因子,以便其图像最适合给定的 GUI 窗口。这意味着图像的宽度或高度(或两者)将等于窗口尺寸。对于以下代码片段,您需要提供 GUI 窗口的 WIDTH 和 HEIGHT,该窗口应接收页面的剪辑矩形。

# WIDTH: width of the GUI window
# HEIGHT: height of the GUI window
# clip: a subrectangle of the document page
# compare width/height ratios of image and window

if clip.width / clip.height < WIDTH / HEIGHT:
    # clip is narrower: zoom to window HEIGHT
    zoom = HEIGHT / clip.height
else:  # clip is broader: zoom to window WIDTH
    zoom = WIDTH / clip.width
mat = pymupdf.Matrix(zoom, zoom)
pix = page.get_pixmap(matrix=mat, clip=clip) 

对于另一种情况,现在假设您已有缩放因子并且需要计算适合的剪辑

在这种情况下,我们有 zoom = HEIGHT/clip.height = WIDTH/clip.width,因此我们必须设置 clip.height = HEIGHT/zoomclip.width = WIDTH/zoom。选择页面上剪辑的左上角点tl来计算正确的位图:

width = WIDTH / zoom
height = HEIGHT / zoom
clip = pymupdf.Rect(tl, tl.x + width, tl.y + height)
# ensure we still are inside the page
clip &= page.rect
mat = pymupdf.Matrix(zoom, zoom)
pix = pymupdf.Pixmap(matrix=mat, clip=clip) 

如何创建或抑制注释图片

通常,页面的位图还显示页面的注释。偶尔,这可能是不可取的。

要在渲染页面上抑制注释图片,只需在Page.get_pixmap()中指定annots=False

您还可以单独呈现注释:它们有自己的Annot.get_pixmap()方法。结果位图的尺寸与注释矩形相同。


如何提取图片:非 PDF 文档

与前几节相反,这一节涉及提取文档中包含的图像,以便它们可以作为一个或多个页面的一部分显示。

如果您想要以文件形式或作为内存区域重新创建原始图像,您基本上有两个选项:

  1. 将您的文档转换为 PDF,然后使用其中一个仅适用于 PDF 的提取方法。此代码片段将文档转换为 PDF:

    >>> pdfbytes = doc.convert_to_pdf()  # this a bytes object
    >>> pdf = pymupdf.open("pdf", pdfbytes)  # open it as a PDF document
    >>> # now use 'pdf' like any PDF document 
    
  2. 使用 Page.get_text() 方法和 “dict” 参数。适用于所有文档类型。将提取页面上显示的所有文本和图像,格式化为 Python 字典。每个图像将出现在图像块中,包含元信息和二进制图像数据。有关字典结构的详细信息,请参阅 TextPage。该方法同样适用于 PDF 文件。这将创建页面上显示的所有图像的列表:

    >>> d = page.get_text("dict")
    >>> blocks = d["blocks"]  # the list of block dictionaries
    >>> imgblocks = [b for b in blocks if b["type"] == 1]
    >>> pprint(imgblocks[0])
    {'bbox': (100.0, 135.8769989013672, 300.0, 364.1230163574219),
     'bpc': 8,
     'colorspace': 3,
     'ext': 'jpeg',
     'height': 501,
     'image': b'\xff\xd8\xff\xe0\x00\x10JFIF\...',  # CAUTION: LARGE!
     'size': 80518,
     'transform': (200.0, 0.0, -0.0, 228.2460174560547, 100.0, 135.8769989013672),
     'type': 1,
     'width': 439,
     'xres': 96,
     'yres': 96} 
    

如何提取图像:PDF 文档

就像 PDF 中的任何其他“对象”一样,图像由交叉引用编号(xref,整数)标识。如果您知道这个编号,您有两种方法可以访问图像的数据:

  1. 创建一个 Pixmap 图像,使用指令 pix = pymupdf.Pixmap(doc, xref)。该方法非常快速(单个数字微秒)。Pixmap 的属性(宽度、高度等)将反映图像的属性。在这种情况下,无法确定嵌入原始图像的图像格式。

  2. 提取图像,使用 img = doc.extract_image(xref)。这是一个包含二进制图像数据的字典,如 img[“image”] 所示。还提供了许多元数据 – 大多数与图像的 Pixmap 中相同。主要区别在于字符串 img[“ext”],它指定图像格式:除了“png”外,还可以出现像“jpeg”、“bmp”、“tiff”等的字符串。如果要将其存储到磁盘,请使用此字符串作为文件扩展名。此方法的执行速度应与语句 pix = pymupdf.Pixmap(doc, xref); pix.tobytes() 的组合速度进行比较。如果嵌入的图像是 PNG 格式,则 Document.extract_image() 的速度大约相同(并且二进制图像数据相同)。否则,此方法快几千倍,并且图像数据更小

问题仍然是:“如何知道这些‘xref’图像的编号?”。对此有两个答案:

  1. “检查页面对象:” 遍历 Page.get_images() 的项。这是一个列表的列表,其项看起来像 [xref, smask, …],包含图像的 xref。然后可以使用其中一个上述方法使用此 xref。对于有效(未损坏)文档使用此方法要小心。然而,请注意,同一图像可能被多个页面引用,因此可能需要提供机制以避免多次提取。

  2. “不需要知道:” 遍历文档的所有 xref 列表,并对每个执行 Document.extract_image()。如果返回的字典为空,则继续 - 此xref不是图像。如果 PDF 文件损坏(无法使用的页面),请使用此方法。注意,PDF 文件通常包含具有特殊目的(定义其他图像的透明度)的“伪图像”(“模版掩码”)。您可能希望提供逻辑来排除这些内容的提取。也请查看下一节。

对于这两种提取方法,都有现成的通用脚本可供使用:

extract-from-pages.py 按页提取图像:

_images/img-extract-imga.jpg

extract-from-xref.py 通过 xref 表提取图像:

_images/img-extract-imgb.jpg


如何处理图像掩码

PDF 中的某些图像伴随着图像掩码。在最简单的形式下,掩码表示作为单独图像存储的 alpha(透明度)字节。为了重建具有掩码的图像的原始图像,必须从其掩码中提取透明度字节来“丰富”它。

在 PyMuPDF 中可以通过以下两种方式之一识别图像是否有掩码:

  1. Document.get_page_images() 的项具有一般格式 (xref, smask, ...),其中 xref 是图像的xref,如果 smask 为正,则为掩码的xref

  2. Document.extract_image() 的(字典)结果具有一个 “smask” 键,如果为正,则包含任何掩码的xref

如果 smask == 0,那么通过xref遇到的图像可以直接处理。

使用 PyMuPDF 恢复原始图像,需执行以下过程:

_images/img-stencil.jpg

>>> pix1 = pymupdf.Pixmap(doc.extract_image(xref)["image"])    # (1) pixmap of image w/o alpha
>>> mask = pymupdf.Pixmap(doc.extract_image(smask)["image"])   # (2) mask pixmap
>>> pix = pymupdf.Pixmap(pix1, mask)                           # (3) copy of pix1, image mask added 

步骤(1)创建基本图像的像素图。步骤(2)使用图像掩码完成相同操作。步骤(3)添加 alpha 通道并填充透明信息。

脚本 extract-from-pages.pyextract-from-xref.py 也包含此逻辑。


如何制作所有图片(或文件)的一个 PDF 文件

我们在这里展示了三个脚本,它们接受一个(图像和其他)文件列表,并将它们全部放入一个 PDF 中。

方法 1:将图像插入为页面

第一个脚本将每个图像转换为具有相同尺寸的 PDF 页面。结果将是一个每个图像一个页面的 PDF。它只适用于支持的图像文件格式:

import os, pymupdf
import PySimpleGUI as psg  # for showing a progress bar
doc = pymupdf.open()  # PDF with the pictures
imgdir = "D:/2012_10_05"  # where the pics are
imglist = os.listdir(imgdir)  # list of them
imgcount = len(imglist)  # pic count

for i, f in enumerate(imglist):
    img = pymupdf.open(os.path.join(imgdir, f))  # open pic as document
    rect = img[0].rect  # pic dimension
    pdfbytes = img.convert_to_pdf()  # make a PDF stream
    img.close()  # no longer needed
    imgPDF = pymupdf.open("pdf", pdfbytes)  # open stream as PDF
    page = doc.new_page(width = rect.width,  # new page with ...
                       height = rect.height)  # pic dimension
    page.show_pdf_page(rect, imgPDF, 0)  # image fills the page
    psg.EasyProgressMeter("Import Images",  # show our progress
        i+1, imgcount)

doc.save("all-my-pics.pdf") 

这将生成一个 PDF 文件,大小仅比合并图片的大小稍大一点。一些关于性能的数字:

上述脚本在我的机器上用于 149 张图片大约需要 1 分钟,总共大小为 514 MB(生成的 PDF 文件大小大约相同)。

_images/img-import-progress.jpg

查看这里获取更完整的源代码:它提供了一个目录选择对话框并跳过了不支持的文件和非文件条目。

注意

我们可以使用 Page.insert_image() 而不是 Page.show_pdf_page(),结果会是一个外观相似的文件。但是,根据图像类型,它可能以未压缩的图像存储。因此,必须使用保存选项 deflate = True 来获得合理的文件大小,这会极大地增加大量图像的运行时间。因此,在这里不能推荐这种替代方案。

方法 2:嵌入文件

第二个脚本嵌入了任意文件 - 不仅仅是图像。由于技术原因,生成的 PDF 将只有一页(空白)。要稍后再次访问嵌入的文件,您需要一个能够显示和/或提取嵌入文件的合适的 PDF 查看器:

import os, pymupdf
import PySimpleGUI as psg  # for showing progress bar
doc = pymupdf.open()  # PDF with the pictures
imgdir = "D:/2012_10_05"  # where my files are

imglist = os.listdir(imgdir)  # list of pictures
imgcount = len(imglist)  # pic count
imglist.sort()  # nicely sort them

for i, f in enumerate(imglist):
    img = open(os.path.join(imgdir,f), "rb").read()  # make pic stream
    doc.embfile_add(img, f, filename=f,  # and embed it
                        ufilename=f, desc=f)
    psg.EasyProgressMeter("Embedding Files",  # show our progress
        i+1, imgcount)

page = doc.new_page()  # at least 1 page is needed

doc.save("all-my-pics-embedded.pdf") 

_images/img-embed-progress.jpg

这绝对是最快的方法,同时也产生了可能的最小输出文件大小。上述图片在我的机器上需要 20 秒,并生成了一个大小为 510 MB 的 PDF 文件。查看这里获取更完整的源代码:它提供了一个目录选择对话框并跳过了非文件条目。

方法 3:附加文件

实现此任务的第三种方法是通过页面注释附加文件,请查看这里获取完整的源代码。

这与前一个脚本具有类似的性能,并且也产生了类似的文件大小。它将为每个附加文件显示一个‘FileAttachment’图标的 PDF 页面。

_images/img-attach-result.jpg

注意

嵌入附加方法都可用于任意文件 - 不仅仅是图像。

注意

我们强烈建议使用出色的包PySimpleGUI来为可能运行较长时间的任务显示进度条。它纯粹使用 Tkinter(无需额外的 GUI 包),只需再加一行代码!


如何创建矢量图像

从文档页面创建图像的常规方法是Page.get_pixmap()。像素图表示一幅光栅图像,因此必须在创建时决定其质量(即分辨率)。后期无法更改。

PyMuPDF 还提供了一种方式来创建 SVG 格式(可缩放矢量图形,以 XML 语法定义)的矢量图像。SVG 图像在各种缩放级别下保持精确(当然除了其中嵌入的任何栅格图形元素)。

指令svg = page.get_svg_image(matrix=pymupdf.Identity)提供了一个 UTF-8 字符串svg,可以用扩展名“.svg”存储。


如何转换图像

就像 PyMuPDF 的一个特性一样,图像转换非常简单。在许多情况下,它可以避免使用其他图形包(如 PIL/Pillow)。

尽管与 Pillow 接口几乎是微不足道的。

输入格式 输出格式 描述
BMP . Windows 位图
JPEG JPEG 联合图像专家组
JXR . JPEG 扩展范围
JPX/JP2 . JPEG 2000
GIF . 图形交换格式
TIFF . 标记图像文件格式
PNG PNG 可移植网络图形
PNM PNM 可移植任意图形
PGM PGM 可移植灰度图
PBM PBM 可移植位图
PPM PPM 可移植像素图
PAM PAM 可移植任意地图
. PSD Adobe Photoshop 文档
. PS Adobe Postscript

基本方案仅包括以下两行:

pix = pymupdf.Pixmap("input.xxx")  # any supported input format
pix.save("output.yyy")  # any supported output format 

备注

  1. pymupdf.Pixmap(arg)输入参数可以是包含图像的文件或字节/io.BytesIO 对象。

  2. 而不是一个输出文件,你也可以通过pix.tobytes(“yyy”)创建一个字节对象并传递它。

  3. 当然,输入和输出格式在颜色空间和透明度方面必须兼容。如果需要调整,Pixmap类已经包括了相关功能。

注意

将 JPEG 转换为 Photoshop

pix = pymupdf.Pixmap("myfamily.jpg")
pix.save("myfamily.psd") 

注意

JPEG 转换为 Tkinter PhotoImage。任何RGB / 无 Alpha图像的工作原理都是相同的。转换为可移植任意图形格式(PPM、PGM 等)是解决问题的方法,因为它们被所有 Tkinter 版本支持:

import tkinter as tk
pix = pymupdf.Pixmap("input.jpg")  # or any RGB / no-alpha image
tkimg = tk.PhotoImage(data=pix.tobytes("ppm")) 

注意

带 Alpha 通道的 PNG 转换为 Tkinter PhotoImage。这需要移除 Alpha 字节,然后进行 PPM 转换:

import tkinter as tk
pix = pymupdf.Pixmap("input.png")  # may have an alpha channel
if pix.alpha:  # we have an alpha channel!
    pix = pymupdf.Pixmap(pix, 0)  # remove it
tkimg = tk.PhotoImage(data=pix.tobytes("ppm")) 

如何使用像素图:粘合图像

这展示了像素图如何用于纯粹的图形非文档目的。脚本读取图像文件并创建一个新图像,由原始图像的 3 * 4 个瓷砖组成:

import pymupdf
src = pymupdf.Pixmap("img-7edges.png")      # create pixmap from a picture
col = 3                                  # tiles per row
lin = 4                                  # tiles per column
tar_w = src.width * col                  # width of target
tar_h = src.height * lin                 # height of target

# create target pixmap
tar_pix = pymupdf.Pixmap(src.colorspace, (0, 0, tar_w, tar_h), src.alpha)

# now fill target with the tiles
for i in range(col):
    for j in range(lin):
        src.set_origin(src.width * i, src.height * j)
        tar_pix.copy(src, src.irect) # copy input to new loc

tar_pix.save("tar.png") 

这是输入图片:

_images/img-7edges.png

这是输出结果:

_images/img-target.png


如何使用 Pixmaps:制作分形

这是另一个 Pixmap 示例,创建了谢尔宾斯基地毯 - 将康托集推广到二维的分形。给定一个正方形地毯,标记其 9 个子正方形(3×3)并切除中心的一个。以相同方式处理剩下的八个子正方形,并无限继续。最终结果是一个面积为零、分形维度为 1.8928… 的集合。

此脚本通过一像素的粒度制作其近似图像,将其制作为 PNG。要增加图像的精度,请更改 n 的值(精度):

import pymupdf, time
if not list(map(int, pymupdf.VersionBind.split("."))) >= [1, 14, 8]:
    raise SystemExit("need PyMuPDF v1.14.8 for this script")
n = 6                             # depth (precision)
d = 3**n                          # edge length

t0 = time.perf_counter()
ir = (0, 0, d, d)                 # the pixmap rectangle

pm = pymupdf.Pixmap(pymupdf.csRGB, ir, False)
pm.set_rect(pm.irect, (255,255,0)) # fill it with some background color

color = (0, 0, 255)               # color to fill the punch holes

# alternatively, define a 'fill' pixmap for the punch holes
# this could be anything, e.g. some photo image ...
fill = pymupdf.Pixmap(pymupdf.csRGB, ir, False) # same size as 'pm'
fill.set_rect(fill.irect, (0, 255, 255))   # put some color in

def punch(x, y, step):
  """Recursively "punch a hole" in the central square of a pixmap.

 Arguments are top-left coords and the step width.

 Some alternative punching methods are commented out.
 """
    s = step // 3                 # the new step
    # iterate through the 9 sub-squares
    # the central one will be filled with the color
    for i in range(3):
        for j in range(3):
            if i != j or i != 1:  # this is not the central cube
                if s >= 3:        # recursing needed?
                    punch(x+i*s, y+j*s, s)       # recurse
            else:                 # punching alternatives are:
                pm.set_rect((x+s, y+s, x+2*s, y+2*s), color)     # fill with a color
                #pm.copy(fill, (x+s, y+s, x+2*s, y+2*s))  # copy from fill
                #pm.invert_irect((x+s, y+s, x+2*s, y+2*s))       # invert colors

    return

#==============================================================================
# main program
#==============================================================================
# now start punching holes into the pixmap
punch(0, 0, d)
t1 = time.perf_counter()
pm.save("sierpinski-punch.png")
t2 = time.perf_counter()
print ("%g sec to create / fill the pixmap" % round(t1-t0,3))
print ("%g sec to save the image" % round(t2-t1,3)) 

结果应该看起来像这样:

_images/img-sierpinski.png


如何与 NumPy 交互

这显示了如何从一个 numpy 数组创建 PNG 文件(比大多数其他方法快几倍):

import numpy as np
import pymupdf
#==============================================================================
# create a fun-colored width * height PNG with pymupdf and numpy
#==============================================================================
height = 150
width  = 100
bild = np.ndarray((height, width, 3), dtype=np.uint8)

for i in range(height):
    for j in range(width):
        # one pixel (some fun coloring)
        bild[i, j] = [(i+j)%256, i%256, j%256]

samples = bytearray(bild.tostring())    # get plain pixel data from numpy array
pix = pymupdf.Pixmap(pymupdf.csRGB, width, height, samples, alpha=False)
pix.save("test.png") 

如何向 PDF 页面添加图像

有两种方法向 PDF 页面添加图像:Page.insert_image()Page.show_pdf_page()。这两种方法有共同之处,但也有区别。

准则 Page.insert_image() Page.show_pdf_page()
可显示内容 图像文件、内存中的图像、Pixmap PDF 页面
显示分辨率 图像分辨率 矢量化(除了光栅页面内容)
旋转 0、90、180 或 270 度 任意角度
裁剪 否(仅完整图像)
保持长宽比 是(默认选项) 是(默认选项)
透明度(水印) 取决于图像 取决于页面
位置 / 放置 缩放以适应目标矩形 缩放以适应目标矩形
性能 自动防止重复; 自动防止重复;
多页面图像支持
使用便捷性 简单、直观; 简单、直观;转换为 PDF 后适用于所有文档类型(包括图像!)通过 Document.convert_to_pdf()

Page.insert_image() 的基本代码模式。如果不重新插入现有图像,则必须恰好给出一个参数文件名 / 流 / Pixmap

page.insert_image(
    rect,                  # where to place the image (rect-like)
    filename=None,         # image in a file
    stream=None,           # image in memory (bytes)
    pixmap=None,           # image from pixmap
    mask=None,             # specify alpha channel separately
    rotate=0,              # rotate (int, multiple of 90)
    xref=0,                # re-use existing image
    oc=0,                  # control visibility via OCG / OCMD
    keep_proportion=True,  # keep aspect ratio
    overlay=True,          # put in foreground
) 

Page.show_pdf_page() 的基本代码模式。源 PDF 和目标 PDF 必须是不同的 Document 对象(但可以从同一文件打开):

page.show_pdf_page(
    rect,                  # where to place the image (rect-like)
    src,                   # source PDF
    pno=0,                 # page number in source PDF
    clip=None,             # only display this area (rect-like)
    rotate=0,              # rotate (float, any value)
    oc=0,                  # control visibility via OCG / OCMD
    keep_proportion=True,  # keep aspect ratio
    overlay=True,          # put in foreground
) 

如何使用 Pixmaps:检查文本可见性

给定文本是否实际上在页面上可见取决于多个因素:

  1. 文本未被其他对象覆盖,但可能与背景颜色相同,例如白色背景等。

  2. 文本可能被图像或矢量图形覆盖。检测这一点是一个重要的能力,例如揭示糟糕匿名化的法律文件。

  3. 文本被创建为隐藏状态。这种技术通常被 OCR 工具使用,将识别的文本存储在页面的一个不可见层中。

以下显示如何检测上述情况 1,或者如果遮盖对象是单色的,则检测情况 2。

pix = page.get_pixmap(dpi=150)  # make page image with a decent resolution

# the following matrix transforms page to pixmap coordinates
mat = page.rect.torect(pix.irect)

# search for some string "needle"
rlist = page.search_for("needle")
# check the visibility for each hit rectangle
for rect in rlist:
    if pix.color_topusage(clip=rect * mat)[0] > 0.95:
        print("'needle' is invisible here:", rect) 

方法 Pixmap.color_topusage() 返回一个元组 (ratio, pixel),其中 0 < ratio <= 1,pixel 是颜色的像素值。请注意,我们只创建一次pixmap。如果有多个命中矩形,这可以节省大量处理时间。

上述代码的逻辑是:如果针的矩形是(“几乎”: > 95%)单色的,则文本不可见。对于可见文本的典型结果返回背景的颜色(主要是白色)和约 0.7 到 0.8 的比率,例如 (0.685, b'xffxffxff')

对本页面有任何反馈吗?


本软件按原样提供,不附带任何明示或暗示的保证。本软件在许可下分发,除非在该许可的条款下明确授权,否则不得复制、修改或分发。请参阅 artifex.com 的许可信息或联系位于美国旧金山 CA 94129 Mesa Street, Suite 108A 的 Artifex Software Inc. 了解更多信息。

本文档涵盖所有版本直至 1.24.4。

Discord logo

注释

原文:pymupdf.readthedocs.io/en/latest/recipes-annotations.html

如何添加和修改注释

在 PyMuPDF 中,可以通过 Page 方法添加新注释。一旦注释存在,就可以使用 Annot 类的方法在很大程度上进行修改。

注释只能插入到 PDF 页面中 - 其他文档类型不支持插入注释。

与许多其他工具不同,初始插入注释时使用了最少数量的属性。我们留给程序员设置诸如作者、创建日期或主题等属性。

作为这些功能的概述,请看下面填充 PDF 页面的脚本,其中包含大部分可用注释。在后续部分中查看更多特殊情况:

# -*- coding: utf-8 -*-
"""
-------------------------------------------------------------------------------
Demo script showing how annotations can be added to a PDF using PyMuPDF.

It contains the following annotation types:
Caret, Text, FreeText, text markers (underline, strike-out, highlight,
squiggle), Circle, Square, Line, PolyLine, Polygon, FileAttachment, Stamp
and Redaction.
There is some effort to vary appearances by adding colors, line ends,
opacity, rotation, dashed lines, etc.

Dependencies
------------
PyMuPDF v1.17.0
-------------------------------------------------------------------------------
"""
from __future__ import print_function

import gc
import sys

import pymupdf

print(pymupdf.__doc__)
if pymupdf.VersionBind.split(".") < ["1", "17", "0"]:
    sys.exit("PyMuPDF v1.17.0+ is needed.")

gc.set_debug(gc.DEBUG_UNCOLLECTABLE)

highlight = "this text is highlighted"
underline = "this text is underlined"
strikeout = "this text is striked out"
squiggled = "this text is zigzag-underlined"
red = (1, 0, 0)
blue = (0, 0, 1)
gold = (1, 1, 0)
green = (0, 1, 0)

displ = pymupdf.Rect(0, 50, 0, 50)
r = pymupdf.Rect(72, 72, 220, 100)
t1 = u"têxt üsès Lätiñ charß,\nEUR: €, mu: µ, super scripts: ²³!"

def print_descr(annot):
  """Print a short description to the right of each annot rect."""
    annot.parent.insert_text(
        annot.rect.br + (10, -5), "%s annotation" % annot.type[1], color=red
    )

doc = pymupdf.open()
page = doc.new_page()

page.set_rotation(0)

annot = page.add_caret_annot(r.tl)
print_descr(annot)

r = r + displ
annot = page.add_freetext_annot(
    r,
    t1,
    fontsize=10,
    rotate=90,
    text_color=blue,
    fill_color=gold,
    align=pymupdf.TEXT_ALIGN_CENTER,
)
annot.set_border(width=0.3, dashes=[2])
annot.update(text_color=blue, fill_color=gold)
print_descr(annot)

r = annot.rect + displ
annot = page.add_text_annot(r.tl, t1)
print_descr(annot)

# Adding text marker annotations:
# first insert a unique text, then search for it, then mark it
pos = annot.rect.tl + displ.tl
page.insert_text(
    pos,  # insertion point
    highlight,  # inserted text
    morph=(pos, pymupdf.Matrix(-5)),  # rotate around insertion point
)
rl = page.search_for(highlight, quads=True)  # need a quad b/o tilted text
annot = page.add_highlight_annot(rl[0])
print_descr(annot)

pos = annot.rect.bl  # next insertion point
page.insert_text(pos, underline, morph=(pos, pymupdf.Matrix(-10)))
rl = page.search_for(underline, quads=True)
annot = page.add_underline_annot(rl[0])
print_descr(annot)

pos = annot.rect.bl
page.insert_text(pos, strikeout, morph=(pos, pymupdf.Matrix(-15)))
rl = page.search_for(strikeout, quads=True)
annot = page.add_strikeout_annot(rl[0])
print_descr(annot)

pos = annot.rect.bl
page.insert_text(pos, squiggled, morph=(pos, pymupdf.Matrix(-20)))
rl = page.search_for(squiggled, quads=True)
annot = page.add_squiggly_annot(rl[0])
print_descr(annot)

pos = annot.rect.bl
r = pymupdf.Rect(pos, pos.x + 75, pos.y + 35) + (0, 20, 0, 20)
annot = page.add_polyline_annot([r.bl, r.tr, r.br, r.tl])  # 'Polyline'
annot.set_border(width=0.3, dashes=[2])
annot.set_colors(stroke=blue, fill=green)
annot.set_line_ends(pymupdf.PDF_ANNOT_LE_CLOSED_ARROW, pymupdf.PDF_ANNOT_LE_R_CLOSED_ARROW)
annot.update(fill_color=(1, 1, 0))
print_descr(annot)

r += displ
annot = page.add_polygon_annot([r.bl, r.tr, r.br, r.tl])  # 'Polygon'
annot.set_border(width=0.3, dashes=[2])
annot.set_colors(stroke=blue, fill=gold)
annot.set_line_ends(pymupdf.PDF_ANNOT_LE_DIAMOND, pymupdf.PDF_ANNOT_LE_CIRCLE)
annot.update()
print_descr(annot)

r += displ
annot = page.add_line_annot(r.tr, r.bl)  # 'Line'
annot.set_border(width=0.3, dashes=[2])
annot.set_colors(stroke=blue, fill=gold)
annot.set_line_ends(pymupdf.PDF_ANNOT_LE_DIAMOND, pymupdf.PDF_ANNOT_LE_CIRCLE)
annot.update()
print_descr(annot)

r += displ
annot = page.add_rect_annot(r)  # 'Square'
annot.set_border(width=1, dashes=[1, 2])
annot.set_colors(stroke=blue, fill=gold)
annot.update(opacity=0.5)
print_descr(annot)

r += displ
annot = page.add_circle_annot(r)  # 'Circle'
annot.set_border(width=0.3, dashes=[2])
annot.set_colors(stroke=blue, fill=gold)
annot.update()
print_descr(annot)

r += displ
annot = page.add_file_annot(
    r.tl, b"just anything for testing", "testdata.txt"  # 'FileAttachment'
)
print_descr(annot)  # annot.rect

r += displ
annot = page.add_stamp_annot(r, stamp=10)  # 'Stamp'
annot.set_colors(stroke=green)
annot.update()
print_descr(annot)

r += displ + (0, 0, 50, 10)
rc = page.insert_textbox(
    r,
    "This content will be removed upon applying the redaction.",
    color=blue,
    align=pymupdf.TEXT_ALIGN_CENTER,
)
annot = page.add_redact_annot(r)
print_descr(annot)

doc.save(__file__.replace(".py", "-%i.pdf" % page.rotation), deflate=True) 

此脚本应导致以下输出:

_images/img-annots.jpg

      • 如何使用自由文本

此脚本展示了处理 ‘FreeText’ 注释的几种方式:

# -*- coding: utf-8 -*-
import pymupdf

# some colors
blue  = (0,0,1)
green = (0,1,0)
red   = (1,0,0)
gold  = (1,1,0)

# a new PDF with 1 page
doc = pymupdf.open()
page = doc.new_page()

# 3 rectangles, same size, above each other
r1 = pymupdf.Rect(100,100,200,150)
r2 = r1 + (0,75,0,75)
r3 = r2 + (0,75,0,75)

# the text, Latin alphabet
t = "¡Un pequeño texto para practicar!"

# add 3 annots, modify the last one somewhat
a1 = page.add_freetext_annot(r1, t, color=red)
a2 = page.add_freetext_annot(r2, t, fontname="Ti", color=blue)
a3 = page.add_freetext_annot(r3, t, fontname="Co", color=blue, rotate=90)
a3.set_border(width=0)
a3.update(fontsize=8, fill_color=gold)

# save the PDF
doc.save("a-freetext.pdf") 

结果如下:

_images/img-freetext.jpg


使用按钮和 JavaScript

自 MuPDF v1.16 起,‘FreeText’ 注释不再支持 Times-Roman、Helvetica 或 Courier 字体的粗体或斜体版本。

衷心感谢我们的用户 @kurokawaikki,他为绕过此限制贡献了以下脚本。

"""
Problem: Since MuPDF v1.16 a 'Freetext' annotation font is restricted to the
"normal" versions (no bold, no italics) of Times-Roman, Helvetica, Courier.
It is impossible to use PyMuPDF to modify this.

Solution: Using Adobe's JavaScript API, it is possible to manipulate properties
of Freetext annotations. Check out these references:
https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/js_api_reference.pdf,
or https://www.adobe.com/devnet/acrobat/documentation.html.

Function 'this.getAnnots()'  will return all annotations  as an array. We loop
over this array to set the properties of the text through the 'richContents'
attribute.
There is no explicit property to set text to bold, but it is possible to set
fontWeight=800 (400 is the normal size) of richContents.
Other attributes, like color, italics, etc. can also be set via richContents.

If we have 'FreeText' annotations created with PyMuPDF, we can make use of this
JavaScript feature to modify the font - thus circumventing the above restriction.

Use PyMuPDF v1.16.12 to create a push button that executes a Javascript
containing the desired code. This is what this program does.
Then open the resulting file with Adobe reader (!).
After clicking on the button, all Freetext annotations will be bold, and the
file can be saved.
If desired, the button can be removed again, using free tools like PyMuPDF or
PDF XChange editor.

Note / Caution:
---------------
The JavaScript will **only** work if the file is opened with Adobe Acrobat reader!
When using other PDF viewers, the reaction is unforeseeable.
"""
import sys

import pymupdf

# this JavaScript will execute when the button is clicked:
jscript = """
var annt = this.getAnnots();
annt.forEach(function (item, index) {
 try {
 var span = item.richContents;
 span.forEach(function (it, dx) {
 it.fontWeight = 800;
 })
 item.richContents = span;
 } catch (err) {}
});
app.alert('Done');
"""
i_fn = sys.argv[1]  # input file name
o_fn = "bold-" + i_fn  # output filename
doc = pymupdf.open(i_fn)  # open input
page = doc[0]  # get desired page

# ------------------------------------------------
# make a push button for invoking the JavaScript
# ------------------------------------------------

widget = pymupdf.Widget()  # create widget

# make it a 'PushButton'
widget.field_type = pymupdf.PDF_WIDGET_TYPE_BUTTON
widget.field_flags = pymupdf.PDF_BTN_FIELD_IS_PUSHBUTTON

widget.rect = pymupdf.Rect(5, 5, 20, 20)  # button position

widget.script = jscript  # fill in JavaScript source text
widget.field_name = "Make bold"  # arbitrary name
widget.field_value = "Off"  # arbitrary value
widget.fill_color = (0, 0, 1)  # make button visible

annot = page.add_widget(widget)  # add the widget to the page
doc.save(o_fn)  # output the file 

如何使用墨迹注释

墨迹注释用于包含手写涂鸦。一个典型的例子可能是包含名字和姓氏的签名图像。从技术上讲,墨迹注释实现为点列表的列表。每个点列表被视为连接点的连续线。不同的点列表表示注释的独立线段。

以下脚本创建了一个墨迹注释,其中包含两个数学曲线(正弦和余弦函数图形)作为线段:

import math
import pymupdf

#------------------------------------------------------------------------------
# preliminary stuff: create function value lists for sine and cosine
#------------------------------------------------------------------------------
w360 = math.pi * 2  # go through full circle
deg = w360 / 360  # 1 degree as radians
rect = pymupdf.Rect(100,200, 300, 300)  # use this rectangle
first_x = rect.x0  # x starts from left
first_y = rect.y0 + rect.height / 2.  # rect middle means y = 0
x_step = rect.width / 360  # rect width means 360 degrees
y_scale = rect.height / 2.  # rect height means 2
sin_points = []  # sine values go here
cos_points = []  # cosine values go here
for x in range(362):  # now fill in the values
    x_coord = x * x_step + first_x  # current x coordinate
    y = -math.sin(x * deg)  # sine
    p = (x_coord, y * y_scale + first_y)  # corresponding point
    sin_points.append(p)  # append
    y = -math.cos(x * deg)  # cosine
    p = (x_coord, y * y_scale + first_y)  # corresponding point
    cos_points.append(p)  # append

#------------------------------------------------------------------------------
# create the document with one page
#------------------------------------------------------------------------------
doc = pymupdf.open()  # make new PDF
page = doc.new_page()  # give it a page

#------------------------------------------------------------------------------
# add the Ink annotation, consisting of 2 curve segments
#------------------------------------------------------------------------------
annot = page.addInkAnnot((sin_points, cos_points))
# let it look a little nicer
annot.set_border(width=0.3, dashes=[1,])  # line thickness, some dashing
annot.set_colors(stroke=(0,0,1))  # make the lines blue
annot.update()  # update the appearance

page.draw_rect(rect, width=0.3)  # only to demonstrate we did OK

doc.save("a-inktest.pdf") 

这是结果:

_images/img-inkannot.jpg对此页面有任何反馈吗?


此软件按原样提供,没有明示或默示的任何保证。此软件根据许可分发,并且未经明确授权不得复制、修改或分发。请参阅artifex.com上的许可信息或联系美国加利福尼亚州旧金山 Mesa 街 39 号 108A 室的 Artifex Software Inc. 了解更多信息。

此文档涵盖了所有版本直到 1.24.4。

Discord logo ## 如何添加和修改注释

在 PyMuPDF 中,可以通过 Page 方法添加新注释。一旦存在注释,可以使用 Annot 类的方法在很大程度上进行修改。

注释能插入 PDF 页面 - 其他文档类型不支持注释插入。

与许多其他工具相比,注释的初始插入具有最少的属性。我们将设置作者、创建日期或主题等属性留给程序员。

作为这些能力的概述,请查看以下脚本,它使用大多数可用的注释填充 PDF 页面。在下一节中查看更多特殊情况:

# -*- coding: utf-8 -*-
"""
-------------------------------------------------------------------------------
Demo script showing how annotations can be added to a PDF using PyMuPDF.

It contains the following annotation types:
Caret, Text, FreeText, text markers (underline, strike-out, highlight,
squiggle), Circle, Square, Line, PolyLine, Polygon, FileAttachment, Stamp
and Redaction.
There is some effort to vary appearances by adding colors, line ends,
opacity, rotation, dashed lines, etc.

Dependencies
------------
PyMuPDF v1.17.0
-------------------------------------------------------------------------------
"""
from __future__ import print_function

import gc
import sys

import pymupdf

print(pymupdf.__doc__)
if pymupdf.VersionBind.split(".") < ["1", "17", "0"]:
    sys.exit("PyMuPDF v1.17.0+ is needed.")

gc.set_debug(gc.DEBUG_UNCOLLECTABLE)

highlight = "this text is highlighted"
underline = "this text is underlined"
strikeout = "this text is striked out"
squiggled = "this text is zigzag-underlined"
red = (1, 0, 0)
blue = (0, 0, 1)
gold = (1, 1, 0)
green = (0, 1, 0)

displ = pymupdf.Rect(0, 50, 0, 50)
r = pymupdf.Rect(72, 72, 220, 100)
t1 = u"têxt üsès Lätiñ charß,\nEUR: €, mu: µ, super scripts: ²³!"

def print_descr(annot):
  """Print a short description to the right of each annot rect."""
    annot.parent.insert_text(
        annot.rect.br + (10, -5), "%s annotation" % annot.type[1], color=red
    )

doc = pymupdf.open()
page = doc.new_page()

page.set_rotation(0)

annot = page.add_caret_annot(r.tl)
print_descr(annot)

r = r + displ
annot = page.add_freetext_annot(
    r,
    t1,
    fontsize=10,
    rotate=90,
    text_color=blue,
    fill_color=gold,
    align=pymupdf.TEXT_ALIGN_CENTER,
)
annot.set_border(width=0.3, dashes=[2])
annot.update(text_color=blue, fill_color=gold)
print_descr(annot)

r = annot.rect + displ
annot = page.add_text_annot(r.tl, t1)
print_descr(annot)

# Adding text marker annotations:
# first insert a unique text, then search for it, then mark it
pos = annot.rect.tl + displ.tl
page.insert_text(
    pos,  # insertion point
    highlight,  # inserted text
    morph=(pos, pymupdf.Matrix(-5)),  # rotate around insertion point
)
rl = page.search_for(highlight, quads=True)  # need a quad b/o tilted text
annot = page.add_highlight_annot(rl[0])
print_descr(annot)

pos = annot.rect.bl  # next insertion point
page.insert_text(pos, underline, morph=(pos, pymupdf.Matrix(-10)))
rl = page.search_for(underline, quads=True)
annot = page.add_underline_annot(rl[0])
print_descr(annot)

pos = annot.rect.bl
page.insert_text(pos, strikeout, morph=(pos, pymupdf.Matrix(-15)))
rl = page.search_for(strikeout, quads=True)
annot = page.add_strikeout_annot(rl[0])
print_descr(annot)

pos = annot.rect.bl
page.insert_text(pos, squiggled, morph=(pos, pymupdf.Matrix(-20)))
rl = page.search_for(squiggled, quads=True)
annot = page.add_squiggly_annot(rl[0])
print_descr(annot)

pos = annot.rect.bl
r = pymupdf.Rect(pos, pos.x + 75, pos.y + 35) + (0, 20, 0, 20)
annot = page.add_polyline_annot([r.bl, r.tr, r.br, r.tl])  # 'Polyline'
annot.set_border(width=0.3, dashes=[2])
annot.set_colors(stroke=blue, fill=green)
annot.set_line_ends(pymupdf.PDF_ANNOT_LE_CLOSED_ARROW, pymupdf.PDF_ANNOT_LE_R_CLOSED_ARROW)
annot.update(fill_color=(1, 1, 0))
print_descr(annot)

r += displ
annot = page.add_polygon_annot([r.bl, r.tr, r.br, r.tl])  # 'Polygon'
annot.set_border(width=0.3, dashes=[2])
annot.set_colors(stroke=blue, fill=gold)
annot.set_line_ends(pymupdf.PDF_ANNOT_LE_DIAMOND, pymupdf.PDF_ANNOT_LE_CIRCLE)
annot.update()
print_descr(annot)

r += displ
annot = page.add_line_annot(r.tr, r.bl)  # 'Line'
annot.set_border(width=0.3, dashes=[2])
annot.set_colors(stroke=blue, fill=gold)
annot.set_line_ends(pymupdf.PDF_ANNOT_LE_DIAMOND, pymupdf.PDF_ANNOT_LE_CIRCLE)
annot.update()
print_descr(annot)

r += displ
annot = page.add_rect_annot(r)  # 'Square'
annot.set_border(width=1, dashes=[1, 2])
annot.set_colors(stroke=blue, fill=gold)
annot.update(opacity=0.5)
print_descr(annot)

r += displ
annot = page.add_circle_annot(r)  # 'Circle'
annot.set_border(width=0.3, dashes=[2])
annot.set_colors(stroke=blue, fill=gold)
annot.update()
print_descr(annot)

r += displ
annot = page.add_file_annot(
    r.tl, b"just anything for testing", "testdata.txt"  # 'FileAttachment'
)
print_descr(annot)  # annot.rect

r += displ
annot = page.add_stamp_annot(r, stamp=10)  # 'Stamp'
annot.set_colors(stroke=green)
annot.update()
print_descr(annot)

r += displ + (0, 0, 50, 10)
rc = page.insert_textbox(
    r,
    "This content will be removed upon applying the redaction.",
    color=blue,
    align=pymupdf.TEXT_ALIGN_CENTER,
)
annot = page.add_redact_annot(r)
print_descr(annot)

doc.save(__file__.replace(".py", "-%i.pdf" % page.rotation), deflate=True) 

本脚本应导致以下输出:

_images/img-annots.jpg


如何使用自由文本

本脚本展示了处理“FreeText”注释的几种方法:

# -*- coding: utf-8 -*-
import pymupdf

# some colors
blue  = (0,0,1)
green = (0,1,0)
red   = (1,0,0)
gold  = (1,1,0)

# a new PDF with 1 page
doc = pymupdf.open()
page = doc.new_page()

# 3 rectangles, same size, above each other
r1 = pymupdf.Rect(100,100,200,150)
r2 = r1 + (0,75,0,75)
r3 = r2 + (0,75,0,75)

# the text, Latin alphabet
t = "¡Un pequeño texto para practicar!"

# add 3 annots, modify the last one somewhat
a1 = page.add_freetext_annot(r1, t, color=red)
a2 = page.add_freetext_annot(r2, t, fontname="Ti", color=blue)
a3 = page.add_freetext_annot(r3, t, fontname="Co", color=blue, rotate=90)
a3.set_border(width=0)
a3.update(fontsize=8, fill_color=gold)

# save the PDF
doc.save("a-freetext.pdf") 

结果如下所示:

_images/img-freetext.jpg

使用按钮和 JavaScript

自 MuPDF v1.16 版本起,“FreeText”注释不再支持 Times-Roman、Helvetica 或 Courier 字体的粗体或斜体版本。

特别感谢我们的用户@kurokawaikki,他贡献了以下脚本来绕过此限制

"""
Problem: Since MuPDF v1.16 a 'Freetext' annotation font is restricted to the
"normal" versions (no bold, no italics) of Times-Roman, Helvetica, Courier.
It is impossible to use PyMuPDF to modify this.

Solution: Using Adobe's JavaScript API, it is possible to manipulate properties
of Freetext annotations. Check out these references:
https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/js_api_reference.pdf,
or https://www.adobe.com/devnet/acrobat/documentation.html.

Function 'this.getAnnots()'  will return all annotations  as an array. We loop
over this array to set the properties of the text through the 'richContents'
attribute.
There is no explicit property to set text to bold, but it is possible to set
fontWeight=800 (400 is the normal size) of richContents.
Other attributes, like color, italics, etc. can also be set via richContents.

If we have 'FreeText' annotations created with PyMuPDF, we can make use of this
JavaScript feature to modify the font - thus circumventing the above restriction.

Use PyMuPDF v1.16.12 to create a push button that executes a Javascript
containing the desired code. This is what this program does.
Then open the resulting file with Adobe reader (!).
After clicking on the button, all Freetext annotations will be bold, and the
file can be saved.
If desired, the button can be removed again, using free tools like PyMuPDF or
PDF XChange editor.

Note / Caution:
---------------
The JavaScript will **only** work if the file is opened with Adobe Acrobat reader!
When using other PDF viewers, the reaction is unforeseeable.
"""
import sys

import pymupdf

# this JavaScript will execute when the button is clicked:
jscript = """
var annt = this.getAnnots();
annt.forEach(function (item, index) {
 try {
 var span = item.richContents;
 span.forEach(function (it, dx) {
 it.fontWeight = 800;
 })
 item.richContents = span;
 } catch (err) {}
});
app.alert('Done');
"""
i_fn = sys.argv[1]  # input file name
o_fn = "bold-" + i_fn  # output filename
doc = pymupdf.open(i_fn)  # open input
page = doc[0]  # get desired page

# ------------------------------------------------
# make a push button for invoking the JavaScript
# ------------------------------------------------

widget = pymupdf.Widget()  # create widget

# make it a 'PushButton'
widget.field_type = pymupdf.PDF_WIDGET_TYPE_BUTTON
widget.field_flags = pymupdf.PDF_BTN_FIELD_IS_PUSHBUTTON

widget.rect = pymupdf.Rect(5, 5, 20, 20)  # button position

widget.script = jscript  # fill in JavaScript source text
widget.field_name = "Make bold"  # arbitrary name
widget.field_value = "Off"  # arbitrary value
widget.fill_color = (0, 0, 1)  # make button visible

annot = page.add_widget(widget)  # add the widget to the page
doc.save(o_fn)  # output the file 

如何使用墨迹注释

墨迹注释用于包含自由手绘涂鸦。典型示例可能是您签名的图像,由名字和姓氏组成。技术上,墨迹注释实现为点列表的列表。每个点列表被视为连接点的连续线。不同的点列表表示注释的独立线段。

下面的脚本创建了一个带有两个数学曲线(正弦和余弦函数图形)作为线段的墨迹注释:

import math
import pymupdf

#------------------------------------------------------------------------------
# preliminary stuff: create function value lists for sine and cosine
#------------------------------------------------------------------------------
w360 = math.pi * 2  # go through full circle
deg = w360 / 360  # 1 degree as radians
rect = pymupdf.Rect(100,200, 300, 300)  # use this rectangle
first_x = rect.x0  # x starts from left
first_y = rect.y0 + rect.height / 2.  # rect middle means y = 0
x_step = rect.width / 360  # rect width means 360 degrees
y_scale = rect.height / 2.  # rect height means 2
sin_points = []  # sine values go here
cos_points = []  # cosine values go here
for x in range(362):  # now fill in the values
    x_coord = x * x_step + first_x  # current x coordinate
    y = -math.sin(x * deg)  # sine
    p = (x_coord, y * y_scale + first_y)  # corresponding point
    sin_points.append(p)  # append
    y = -math.cos(x * deg)  # cosine
    p = (x_coord, y * y_scale + first_y)  # corresponding point
    cos_points.append(p)  # append

#------------------------------------------------------------------------------
# create the document with one page
#------------------------------------------------------------------------------
doc = pymupdf.open()  # make new PDF
page = doc.new_page()  # give it a page

#------------------------------------------------------------------------------
# add the Ink annotation, consisting of 2 curve segments
#------------------------------------------------------------------------------
annot = page.addInkAnnot((sin_points, cos_points))
# let it look a little nicer
annot.set_border(width=0.3, dashes=[1,])  # line thickness, some dashing
annot.set_colors(stroke=(0,0,1))  # make the lines blue
annot.update()  # update the appearance

page.draw_rect(rect, width=0.3)  # only to demonstrate we did OK

doc.save("a-inktest.pdf") 

这就是结果:

_images/img-inkannot.jpg您对本页面有任何反馈意见吗?


本软件按原样提供,不提供任何明示或暗示的保证。本软件根据许可证分发,未经许可不得复制、修改或分发。请参阅artifex.com上的许可信息,或联系美国加利福尼亚州旧金山 Mesa 街 39 号 108A 套房的 Artifex Software Inc.获取更多信息。

本文档覆盖了截至 1.24.4 版本的所有版本。

Discord 标志

绘图和图形

原文:pymupdf.readthedocs.io/en/latest/recipes-drawing-and-graphics.html

注意

当提到“绘图”或“图形”时,我们指的是“矢量图形”或“线条艺术”。

因此,请将这些术语视为同义词!

PDF 文件支持作为其语法一部分的基本绘图操作。这些是矢量图形,包括诸如线条、曲线、圆、矩形以及指定颜色的基本几何对象。

此类操作的语法在 Adobe PDF References 的第 643 页中的“A Operator Summary”中定义。为 PDF 页面指定这些运算符发生在其 contents 对象中。

PyMuPDF 通过其 Shape 类实现了大部分可用功能,这类似于其他软件包中的“画布”概念(例如 reportlab)。

一个形状始终是页面的子级,通常使用类似 shape = page.new_shape() 的指令创建。该类定义了许多在页面区域上执行绘图操作的方法。例如,last_point = shape.draw_rect(rect) 可以沿着适当定义的 rect = pymupdf.Rect(...) 边界绘制一个矩形。

返回的 last_point 总是 绘制操作结束的 Point(“最后一个点”)。每个这样的基本绘图都需要接下来的 Shape.finish() 来“关闭”它,但可能有多个绘图共享一个 finish() 方法。

实际上,Shape.finish() 定义 了一组前面的绘图操作,形成一个可能相当复杂的图形对象。PyMuPDF 在 shapes_and_symbols.py 中提供了几个预定义的图形,展示了这是如何工作的。

如果导入此脚本,则还可以像下面的示例一样直接使用其图形:

# -*- coding: utf-8 -*-
"""
Created on Sun Dec  9 08:34:06 2018

@author: Jorj
@license: GNU AFFERO GPL V3

Create a list of available symbols defined in shapes_and_symbols.py

This also demonstrates an example usage: how these symbols could be used
as bullet-point symbols in some text.

"""

import pymupdf
import shapes_and_symbols as sas

# list of available symbol functions and their descriptions
tlist = [
         (sas.arrow, "arrow (easy)"),
         (sas.caro, "caro (easy)"),
         (sas.clover, "clover (easy)"),
         (sas.diamond, "diamond (easy)"),
         (sas.dontenter, "do not enter (medium)"),
         (sas.frowney, "frowney (medium)"),
         (sas.hand, "hand (complex)"),
         (sas.heart, "heart (easy)"),
         (sas.pencil, "pencil (very complex)"),
         (sas.smiley, "smiley (easy)"),
         ]

r = pymupdf.Rect(50, 50, 100, 100)  # first rect to contain a symbol
d = pymupdf.Rect(0, r.height + 10, 0, r.height + 10)  # displacement to next rect
p = (15, -r.height * 0.2)  # starting point of explanation text
rlist = [r]  # rectangle list

for i in range(1, len(tlist)):  # fill in all the rectangles
    rlist.append(rlist[i-1] + d)

doc = pymupdf.open()  # create empty PDF
page = doc.new_page()  # create an empty page
shape = page.new_shape()  # start a Shape (canvas)

for i, r in enumerate(rlist):
    tlist[i]0  # execute symbol creation
    shape.insert_text(rlist[i].br + p,  # insert description text
                   tlist[i][1], fontsize=r.height/1.2)

# store everything to the page's /Contents object
shape.commit()

import os
scriptdir = os.path.dirname(__file__)
doc.save(os.path.join(scriptdir, "symbol-list.pdf"))  # save the PDF 

这是脚本的结果:

_images/img-symbols.jpg


如何提取绘图

  • v1.18.0 中的新功能

页面发出的绘图命令(矢量图形)可以提取为一个字典列表。有趣的是,这对于所有支持的文档类型都适用,不仅仅是 PDF:因此您也可以用于 XPS、EPUB 等。

页面方法 Page.get_drawings() 访问绘图命令并将其转换为 Python 字典列表。每个称为“路径”的字典代表一个单独的绘图,它可能简单如一条线,或是前一节中形状的复杂组合之一。

路径字典被设计为可以轻松由形状类及其方法使用。这里有一个页面的示例,其中绘制了一个红边黄色圆在矩形Rect(100, 100, 200, 200)内:

>>> pprint(page.get_drawings())
[{'closePath': True,
'color': [1.0, 0.0, 0.0],
'dashes': '[] 0',
'even_odd': False,
'fill': [1.0, 1.0, 0.0],
'items': [('c',
 Point(100.0, 150.0),
 Point(100.0, 177.614013671875),
 Point(122.38600158691406, 200.0),
 Point(150.0, 200.0)),
 ('c',
 Point(150.0, 200.0),
 Point(177.61399841308594, 200.0),
 Point(200.0, 177.614013671875),
 Point(200.0, 150.0)),
 ('c',
 Point(200.0, 150.0),
 Point(200.0, 122.385986328125),
 Point(177.61399841308594, 100.0),
 Point(150.0, 100.0)),
 ('c',
 Point(150.0, 100.0),
 Point(122.38600158691406, 100.0),
 Point(100.0, 122.385986328125),
 Point(100.0, 150.0))],
'lineCap': (0, 0, 0),
'lineJoin': 0,
'opacity': 1.0,
'rect': Rect(100.0, 100.0, 200.0, 200.0),
'width': 1.0}]
>>> 

注意

要以令人满意的精度绘制圆,您至少需要 4 个 3 阶贝塞尔曲线。请参阅这篇Wikipedia 文章了解一些背景知识。

以下是一个代码片段,它提取页面的图形并将它们重新绘制在新页面上:

import pymupdf
doc = pymupdf.open("some.file")
page = doc[0]
paths = page.get_drawings()  # extract existing drawings
# this is a list of "paths", which can directly be drawn again using Shape
# -------------------------------------------------------------------------
#
# define some output page with the same dimensions
outpdf = pymupdf.open()
outpage = outpdf.new_page(width=page.rect.width, height=page.rect.height)
shape = outpage.new_shape()  # make a drawing canvas for the output page
# --------------------------------------
# loop through the paths and draw them
# --------------------------------------
for path in paths:
    # ------------------------------------
    # draw each entry of the 'items' list
    # ------------------------------------
    for item in path["items"]:  # these are the draw commands
        if item[0] == "l":  # line
            shape.draw_line(item[1], item[2])
        elif item[0] == "re":  # rectangle
            shape.draw_rect(item[1])
        elif item[0] == "qu":  # quad
            shape.draw_quad(item[1])
        elif item[0] == "c":  # curve
            shape.draw_bezier(item[1], item[2], item[3], item[4])
        else:
            raise ValueError("unhandled drawing", item)
    # ------------------------------------------------------
    # all items are drawn, now apply the common properties
    # to finish the path
    # ------------------------------------------------------
    shape.finish(
        fill=path["fill"],  # fill color
        color=path["color"],  # line color
        dashes=path["dashes"],  # line dashing
        even_odd=path.get("even_odd", True),  # control color of overlaps
        closePath=path["closePath"],  # whether to connect last and first point
        lineJoin=path["lineJoin"],  # how line joins should look like
        lineCap=max(path["lineCap"]),  # how line ends should look like
        width=path["width"],  # line width
        stroke_opacity=path.get("stroke_opacity", 1),  # same value for both
        fill_opacity=path.get("fill_opacity", 1),  # opacity parameters
        )
# all paths processed - commit the shape to its page
shape.commit()
outpdf.save("drawings-page-0.pdf") 

如所示,与形状类有很高的一致性水平。只有一个例外:出于技术原因,这里的lineCap是一个包含 3 个数字的元组,而在形状(以及 PDF)中是一个整数。因此,我们简单地取该元组的最大值。

这是一个示例页面的输入和输出比较,由上一个脚本创建:

_images/img-getdrawings.png

注意

像这里显示的图形重建并不完美。截至本版本,以下方面将不会重现:

  • 页面定义可以很复杂,并包括不显示/隐藏某些区域的指令,以保持它们的隐形。这类东西被Page.get_drawings()忽略——它将始终返回所有路径。

注意

您可以使用路径列表制作您自己的列表,例如页面上的所有线条或所有矩形,并根据标准(如颜色或页面上的位置等)进行子选择。## 如何删除图形

要删除绘图/矢量图形,我们必须使用一个修订注释,其边界框是图形的边界框,然后添加并应用一个修订来删除它。

以下代码显示了删除页面上找到的第一个图形的示例:

paths = page.get_drawings()
rect = paths[0]["rect"]  # rectangle of the 1st drawing
page.add_redact_annot(rect)
page.apply_redactions(0,2,1)  # potentially set options for any of images, drawings, text 

注意

参见Page.apply_redactions()以获取可以发送的参数选项——您可以将删除选项应用于受注释区域约束的图像、绘图和文本对象。

如何绘制图形

绘制图形就像调用你可能想要的绘制方法类型一样简单。你可以直接在页面上或形状对象内绘制图形。

例如,要绘制一个圆:

# Draw a circle on the page using the Page method
page.draw_circle((center_x, center_y), radius, color=(1, 0, 0), width=2)

# Draw a circle on the page using a Shape object
shape = page.new_shape()
shape.draw_circle((center_x, center_y), radius)
shape.finish(color=(1, 0, 0), width=2)
shape.commit(overlay=True) 

形状对象可用于组合多个应接收公共属性的图形,如Shape.finish()所指定。

您对此页面有任何反馈吗?


此软件按原样提供,不提供任何明示或暗示的担保。此软件按许可分发,未经许可明确授权的情况下,不得复制、修改或分发此软件。请参阅artifex.com获取许可信息或联系 Artifex Software Inc.,39 Mesa Street,Suite 108A,San Francisco CA 94129,美国,以获取进一步信息。

此文档涵盖所有版本,直至 1.24.4。

Discord logo ## 如何提取绘图

  • v1.18.0 中的新功能

页面发出的绘图命令(矢量图形)可以提取为字典列表。有趣的是,这对于所有支持的文档类型都适用 - 不仅限于 PDF:因此,您可以在 XPS、EPUB 和其他格式中使用它。

页面方法,Page.get_drawings()访问绘图命令并将其转换为 Python 字典列表。每个字典 - 称为“路径” - 表示单独的绘图,它可能简单如一条直线,或复杂如前一节形状之一的线条和曲线组合。

路径字典已经设计得可以轻松地被 Shape 类及其方法使用。这里是一个带有一个路径的页面的示例,它在Rect(100, 100, 200, 200)矩形内绘制了一个带红边的黄色圆:

>>> pprint(page.get_drawings())
[{'closePath': True,
'color': [1.0, 0.0, 0.0],
'dashes': '[] 0',
'even_odd': False,
'fill': [1.0, 1.0, 0.0],
'items': [('c',
 Point(100.0, 150.0),
 Point(100.0, 177.614013671875),
 Point(122.38600158691406, 200.0),
 Point(150.0, 200.0)),
 ('c',
 Point(150.0, 200.0),
 Point(177.61399841308594, 200.0),
 Point(200.0, 177.614013671875),
 Point(200.0, 150.0)),
 ('c',
 Point(200.0, 150.0),
 Point(200.0, 122.385986328125),
 Point(177.61399841308594, 100.0),
 Point(150.0, 100.0)),
 ('c',
 Point(150.0, 100.0),
 Point(122.38600158691406, 100.0),
 Point(100.0, 122.385986328125),
 Point(100.0, 150.0))],
'lineCap': (0, 0, 0),
'lineJoin': 0,
'opacity': 1.0,
'rect': Rect(100.0, 100.0, 200.0, 200.0),
'width': 1.0}]
>>> 

注意

您至少需要 4 个三阶贝塞尔曲线才能以可接受的精度绘制一个圆。请参阅这篇Wikipedia 文章了解背景信息。

下面是一个代码片段,用于提取页面的绘图并将其重新绘制到新页面上:

import pymupdf
doc = pymupdf.open("some.file")
page = doc[0]
paths = page.get_drawings()  # extract existing drawings
# this is a list of "paths", which can directly be drawn again using Shape
# -------------------------------------------------------------------------
#
# define some output page with the same dimensions
outpdf = pymupdf.open()
outpage = outpdf.new_page(width=page.rect.width, height=page.rect.height)
shape = outpage.new_shape()  # make a drawing canvas for the output page
# --------------------------------------
# loop through the paths and draw them
# --------------------------------------
for path in paths:
    # ------------------------------------
    # draw each entry of the 'items' list
    # ------------------------------------
    for item in path["items"]:  # these are the draw commands
        if item[0] == "l":  # line
            shape.draw_line(item[1], item[2])
        elif item[0] == "re":  # rectangle
            shape.draw_rect(item[1])
        elif item[0] == "qu":  # quad
            shape.draw_quad(item[1])
        elif item[0] == "c":  # curve
            shape.draw_bezier(item[1], item[2], item[3], item[4])
        else:
            raise ValueError("unhandled drawing", item)
    # ------------------------------------------------------
    # all items are drawn, now apply the common properties
    # to finish the path
    # ------------------------------------------------------
    shape.finish(
        fill=path["fill"],  # fill color
        color=path["color"],  # line color
        dashes=path["dashes"],  # line dashing
        even_odd=path.get("even_odd", True),  # control color of overlaps
        closePath=path["closePath"],  # whether to connect last and first point
        lineJoin=path["lineJoin"],  # how line joins should look like
        lineCap=max(path["lineCap"]),  # how line ends should look like
        width=path["width"],  # line width
        stroke_opacity=path.get("stroke_opacity", 1),  # same value for both
        fill_opacity=path.get("fill_opacity", 1),  # opacity parameters
        )
# all paths processed - commit the shape to its page
shape.commit()
outpdf.save("drawings-page-0.pdf") 

如您所见,与 Shape 类存在高度一致性。唯一的例外是:由于技术原因,这里的lineCap是一个包含 3 个数字的元组,而在 Shape(以及 PDF 中)是一个整数。因此我们简单地取该元组的最大值。

这里是通过前面的脚本创建的示例页面的输入和输出比较:

_images/img-getdrawings.png

注意

如此所示的图形重建并非完美。截至本版本,以下方面将不会重现:

  • 页面定义可能很复杂,并包括指示不显示/隐藏某些区域以使它们保持不可见的说明。类似的东西在Page.get_drawings()中被忽略 - 它总是返回所有路径。

注意

您可以使用路径列表制作自己的列表,例如页面上的所有直线或所有矩形,并按照颜色或页面位置等标准进行子选择。

如何删除绘图

要删除绘图/矢量图形,我们必须使用删除注释,并使用绘图的边界框添加和应用删除操作。

以下代码显示了如何删除页面上找到的第一个绘图的示例:

paths = page.get_drawings()
rect = paths[0]["rect"]  # rectangle of the 1st drawing
page.add_redact_annot(rect)
page.apply_redactions(0,2,1)  # potentially set options for any of images, drawings, text 

注意

参见Page.apply_redactions(),了解可发送的参数选项,可以对绑定在注释区域内的图像、绘图和文本对象应用删除选项。

如何绘制图形

绘制图形就像调用所需的Drawing Method类型一样简单。您可以直接在页面上或形状对象内绘制图形。

例如,要绘制一个圆:

# Draw a circle on the page using the Page method
page.draw_circle((center_x, center_y), radius, color=(1, 0, 0), width=2)

# Draw a circle on the page using a Shape object
shape = page.new_shape()
shape.draw_circle((center_x, center_y), radius)
shape.finish(color=(1, 0, 0), width=2)
shape.commit(overlay=True) 

对象形状可以用于组合多个绘图,这些绘图应根据Shape.finish()指定的公共属性进行设置。

您对此页面有任何反馈吗?


本软件按原样提供,不附带任何明示或暗示的担保。本软件在许可下分发,并且未经授权明确许可的情况下不得复制、修改或分发。有关更多信息,请参阅artifex.com,或联系位于美国加利福尼亚州旧金山 94129 Mesa Street 39 号 108A 套房的 Artifex Software Inc.。

此文档涵盖所有版本,直至 1.24.4。

Discord logo

标签:24,pymupdf,annot,PyMuPDF,文档,图像,PDF,page,rect
From: https://www.cnblogs.com/apachecn/p/18252558

相关文章

  • PyMuPDF-1-24-4-中文文档-七-
    PyMuPDF1.24.4中文文档(七)原文:https://pymupdf.readthedocs.io/en/latest/存档原文:pymupdf.readthedocs.io/en/latest/archive-class.htmlv1.21.0版新增内容此类表示文件夹和容器文件(如ZIP和TAR存档)的泛化。存档允许像它们都是一个文件夹层次结构树的一部分一样......
  • PyMuPDF-1-24-4-中文文档-二-
    PyMuPDF1.24.4中文文档(二)原文:https://pymupdf.readthedocs.io/en/latest/教程原文:pymupdf.readthedocs.io/en/latest/tutorial.html本教程将逐步展示您如何在Python中使用PyMuPDF和MuPDF。因为MuPDF不仅支持PDF,还支持XPS、OpenXPS、CBZ、CBR、FB2和EPUB格......
  • git学习笔记——202406171525
    想将本地仓库代码提交到远程仓库,应注意:如果在新建远程仓库时里面还新建了文件,在本地提交代码时会显示两个分支是冲突的,git认为是两个不相关的仓库代码,会拒绝上传。解决方法是gitpullremotemaster拉取远程代码到本地,然后再gitpushremote-umaster相关链接:https://www.cn......
  • 自动驾驶、AI、高端医疗……芯驿电子携 FPGA 创新成果亮相 2024 上海国际嵌入式展
      6月12日至14日,2024上海国际嵌入式展(embeddedworldChina)在上海世博展览馆正式举行。本届展会以“智慧赋能,科技全球”为主题,旨在打造嵌入式系统全产业链交流与合作平台。 作为中国嵌入式技术领域领先的FPGA方案商,芯驿电子科技(上海)有限公司携多款FPGA新品和行业方案......
  • 生成式 AI 服务应用之Langchain 和 DashScope Reranker 彻底改变您的文档检索过程(教程
    介绍在当今信息泛滥的时代,找到所需的确切文档似乎是一件不可能完成的事情。传统搜索引擎经常让您在无关内容中苦苦挣扎,浪费宝贵的时间。但不要担心,因为有一对强大的组合正在等待彻底改变您的搜索体验:Langchain和DashScopeReranker。推荐文章《如何使用CodeLlama......
  • Java速成笔记 2024.6.17版
    变量:可以变化的容器不同变量可以存储不同类型的值变量声明方法:变量类型变量名=初始值;E.G.inta=1;变量类型:整型:intlong浮点数:floatdouble布尔:boolean字符串:String字符:char变量命名注意事项:不能重名不能以数字开头常量:关键字:final语法:finalfl......
  • 国产最好用的EasyRecovery数据恢复软件2024中文电脑版下载
    EasyRecovery数据恢复软件,简直就是数据丢失者的救星!......
  • 软件工程项目开发文档资料(规格说明书、详细设计、测试计划、验收报告)
      前言:在软件开发过程中,文档资料是非常关键的一部分,它们帮助团队成员理解项目需求、设计、实施、测试、验收等各个环节,确保项目的顺利进行。以下是各个阶段的文档资料概述:软件项目管理部分文档清单: 工作安排任务书,可行性分析报告,立项申请审批表,产品需求规格说明书,需求调......
  • 山东大学2023-2024深度学习期末回忆及参考答案
    文章目录名词解释(3*8=24分)分布式表示超参数共现矩阵截断BPTTattention机制梯度确认疑惑度还有一个忘了简答(6题)1、说出训练数据测试数据验证数据的作用,为什么要分训练数据和测试数据?训练数据、测试数据、验证数据的作用为什么要分训练数据和测试数据2、为什么激活函数要......
  • 2024 第六届机器人与计算机视觉国际会议(ICRCV 2024)即将召开!
    2024第六届机器人与计算机视觉国际会议(ICRCV2024)将于2024年9月20日-22日在中国·无锡召开,由IEEE,IEEERAS,南京理工大学联合主办。会议旨在为行业内专家和学者分享技术进步和业务经验,聚焦机器人与计算机视觉的前沿研究,提供一个合作交流的平台。会议官网:ICRCV2024|......