PyMuPDF 1.24.4 中文文档(三)(1)https://developer.aliyun.com/article/1559529
如何创建部分位图(剪辑)
并非总是需要或想要完整的页面图像。例如,当您在 GUI 中显示图像并希望用页面的放大部分填充相应的窗口时,就是这种情况。
假设你的 GUI 窗口有足够的空间来显示完整的文档页面,但现在你想要用页面的右下角的四分之一来填充这个空间,从而使用四倍更好的分辨率。
为了实现这一点,请定义一个等于您希望显示在 GUI 中的区域的矩形,并将其命名为“剪辑”。在 PyMuPDF 中构建矩形的一种方式是提供两个对角线相对的角,这就是我们在这里做的事情。
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/zoom
和 clip.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 文档
与前几节相反,这一节涉及提取文档中包含的图像,以便它们可以作为一个或多个页面的一部分显示。
如果您想要以文件形式或作为内存区域重新创建原始图像,您基本上有两个选项:
- 将您的文档转换为 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
- 使用
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
,整数)标识。如果您知道这个编号,您有两种方法可以访问图像的数据:
- 创建一个 Pixmap 图像,使用指令 pix = pymupdf.Pixmap(doc, xref)。该方法非常快速(单个数字微秒)。Pixmap 的属性(宽度、高度等)将反映图像的属性。在这种情况下,无法确定嵌入原始图像的图像格式。
- 提取图像,使用 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’图像的编号?”。对此有两个答案:
- “检查页面对象:” 遍历
Page.get_images()
的项。这是一个列表的列表,其项看起来像 [xref, smask, …],包含图像的xref
。然后可以使用其中一个上述方法使用此xref
。对于**有效(未损坏)**文档使用此方法要小心。然而,请注意,同一图像可能被多个页面引用,因此可能需要提供机制以避免多次提取。 - “不需要知道:” 遍历文档的所有 xref 列表,并对每个执行
Document.extract_image()
。如果返回的字典为空,则继续 - 此xref
不是图像。如果 PDF 文件损坏(无法使用的页面),请使用此方法。注意,PDF 文件通常包含具有特殊目的(定义其他图像的透明度)的“伪图像”(“模版掩码”)。您可能希望提供逻辑来排除这些内容的提取。也请查看下一节。
对于这两种提取方法,都有现成的通用脚本可供使用:
extract-from-pages.py 按页提取图像:
和 extract-from-xref.py 通过 xref 表提取图像:
如何处理图像掩码
PDF 中的某些图像伴随着图像掩码。在最简单的形式下,掩码表示作为单独图像存储的 alpha(透明度)字节。为了重建具有掩码的图像的原始图像,必须从其掩码中提取透明度字节来“丰富”它。
在 PyMuPDF 中可以通过以下两种方式之一识别图像是否有掩码:
Document.get_page_images()
的项具有一般格式(xref, smask, ...)
,其中 xref 是图像的xref
,如果 smask 为正,则为掩码的xref
。Document.extract_image()
的(字典)结果具有一个 “smask” 键,如果为正,则包含任何掩码的xref
。
如果 smask == 0,那么通过xref
遇到的图像可以直接处理。
使用 PyMuPDF 恢复原始图像,需执行以下过程:
>>> 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.py 和 extract-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 文件大小大约相同)。
查看这里获取更完整的源代码:它提供了一个目录选择对话框并跳过了不支持的文件和非文件条目。
注意
我们可以使用 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")
这绝对是最快的方法,同时也产生了可能的最小输出文件大小。上述图片在我的机器上需要 20 秒,并生成了一个大小为 510 MB 的 PDF 文件。查看这里获取更完整的源代码:它提供了一个目录选择对话框并跳过了非文件条目。
方法 3:附加文件
实现此任务的第三种方法是通过页面注释附加文件,请查看这里获取完整的源代码。
这与前一个脚本具有类似的性能,并且也产生了类似的文件大小。它将为每个附加文件显示一个‘FileAttachment’图标的 PDF 页面。
注意
嵌入和附加方法都可用于任意文件 - 不仅仅是图像。
注意
我们强烈建议使用出色的包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
备注
- *pymupdf.Pixmap(arg)*的输入参数可以是包含图像的文件或字节/io.BytesIO 对象。
- 而不是一个输出文件,你也可以通过*pix.tobytes(“yyy”)*创建一个字节对象并传递它。
- 当然,输入和输出格式在颜色空间和透明度方面必须兼容。如果需要调整,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"))
PyMuPDF 1.24.4 中文文档(三)(3)https://developer.aliyun.com/article/1559532