[Python]遇到黑底PDF怎么办?自动反色输出!

Auto Inverse PDF

介绍

本项目源于某一特殊情景——PDF打印,笔者拿到了一份黑底白字的PDF资料:
资料截图
而使用这个PDF文件直接进行打印将耗费大量油墨,故笔者想要获取一种可以反色打印PDF文件的方式。


经过大量查找之后发现,市面上有不少PDF阅读器具有反色功能,但是经过笔者测试:某些阅读器反色功能仅仅是显示反色,真正打印或者另存为时仍然会保持原色,不可否认的是某些场景下确实需要,但……这并非是笔者所需要的;而某些具有反色打印功能的阅读器要么存在广告,要么需要付费使用(甚至还会有P2P下崽器)。

确定方法

由此,笔者萌发了自制反色输出程序,并大量查找资料。如资料1资料2资料3,大家见仁见智,有使用Python的,有用Photoshop的(毕竟PDF是Adobe的),还有使用一些其他软件配合命令行的,玩法众多。而笔者更偏向于使用Python

  • 轻量化 优于Photoshop
  • 简单化 优于附加下载其他软件
  • 可控性强
    所以笔者首先采用了资料1所使用的方法。

开始工作

根据资料作者所写,配置好环境,直接copy到文件里:

import datetime
import os

from PIL import Image
import PIL.ImageOps  
import glob

import fitz

def pyMuPDF_fitz(pdfPath, imagePath):
    startTime_pdf2img = datetime.datetime.now()  # 开始时间

    print("imagePath=" + imagePath)
    pdfDoc = fitz.open(pdfPath)   # 打开pdf
    for pg in range(pdfDoc.pageCount):
        page = pdfDoc[pg] #取出指定页
        rotate = int(0) # 不进行旋转
        # 每个尺寸的缩放系数为1.3,这将为我们生成分辨率提高2.6的图像。
        # 此处若是不做设置,默认图片大小为:792X612, dpi=96
        zoom_x = 5  
        zoom_y = zoom_x
        mat = fitz.Matrix(zoom_x, zoom_y).preRotate(rotate)
        pix = page.getPixmap(matrix=mat, alpha=False)
        img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
        img = PIL.ImageOps.invert(img)

        if not os.path.exists(imagePath):  # 判断存放图片的文件夹是否存在
            os.makedirs(imagePath)  # 若图片文件夹不存在就创建

        img.save(imagePath + '/' + 'images_%s.png' % pg, "PNG")
        print("<<转换进度>>\t"+str(100*pg/pdfDoc.pageCount)+"%", flush=True)
 
    endTime_pdf2img = datetime.datetime.now()  # 结束时间
    print('pdf转换时间=', (endTime_pdf2img - startTime_pdf2img).seconds)


def pic2pdf(imagePath, newPath):
  doc = fitz.open()
  for img in sorted(glob.glob(imagePath + "/*")): # 读取图片,确保按文件名排序
    print("添加页面"+str(img))
    imgdoc = fitz.open(img)         # 打开图片
    pdfbytes = imgdoc.convertToPDF()    # 使用图片创建单页的 PDF
    imgpdf = fitz.open("pdf", pdfbytes)
    doc.insertPDF(imgpdf)          # 将当前页插入文档
  if os.path.exists(newPath):
    os.remove(newPath)
  doc.save(newPath)          # 保存pdf文件
  doc.close()

if __name__ == "__main__":
    pdfPath = "源文件.pdf"
    imagePath = "中间文件路径"
    newPath = "新文件.pdf"
    pyMuPDF_fitz(pdfPath, imagePath)
    pic2pdf(imagePath,newPath)

由于使用的文件页数超过10页,出现了排序异常的情况:
乱序
根据资料的评论区,热心的大佬为该程序重新编写了排序函数:

def pic2pdf(imagePath, newPath):
  doc = fitz.open()
  imgs = len(glob.glob(imagePath + "/*"))
  for img in range(imgs): # 读取图片,确保按文件名排序
    img = imagePath + '/images_{}.png'.format(img)
    print("添加页面"+str(img))
    imgdoc = fitz.open(img)         # 打开图片
    pdfbytes = imgdoc.convertToPDF()    # 使用图片创建单页的 PDF
    imgpdf = fitz.open("pdf", pdfbytes)
    doc.insertPDF(imgpdf)          # 将当前页插入文档
  if os.path.exists(newPath):
    os.remove(newPath)
  doc.save(newPath)          # 保存pdf文件
  doc.close()

重新运行之后排序正常了。


对了,这里不得不提一下,笔者参考的资料1所使用的模块——PyMuPDF,根据其官方文档由于版本更新问题,修改了几乎全部的内置函数命名方式,由原来的驼峰式、混合式统一为蛇形:

The original naming convention for methods and properties has been “camelCase”. Since its creation around 2013, a tremendous increase of functionality has happened in PyMuPDF – and with it a corresponding increase in classes, methods and properties. In too many cases, this has led to non-intuitive, illogical and ugly names, difficult to memorize or guess.

A few versions ago, I therefore decided to shift gears and switch to a “snake_cased” naming standard. This was a major effort, which needed a step-wise approach. I think am done with it now (version 1.18.14).

这导致笔者在初次使用之时程序直接报错:

Traceback (most recent call last):
  File "e:\Programs\Python\InversePDF\test.py", line 56, in <module>
    pyMuPDF_fitz(pdfPath, imagePath)
  File "e:\Programs\Python\InversePDF\test.py", line 17, in pyMuPDF_fitz
    for pg in range(pdfDoc.pageCount):
AttributeError: 'Document' object has no attribute 'pageCount'

后来回退版本就好了,不过为了适配最新官方手册和新特性,我还是升级了原有的版本。

小问题

其实文章到此也应该结束了,但是笔者在实际使用中还是遇到了一个问题——输出的PDF分辨率不足。


我没有骂人.jpg
于是笔者调整了zoom参数,将输出PDF的分辨率调高,但这时一个重大问题发生了——程序变得异常耗时!

#zoom_x = zoom_y = 5
time=21

#zoom_x = zoom_y = 10
time=54

#zoom_x = zoom_y = 20
time=199

这引发了笔者极大的兴趣,这也是上文提到的,升级PyMuPDF版本,查阅手册,开发!


笔者先是测试了各个步骤的耗时,发现耗时最多的函数分别是

#平均5s
pix = page.getPixmap(matrix=mat, alpha=False)
#平均3s
img.save(imagePath + '/' + 'images_%s.png' % pg, "PNG")

第一个函数无法避免的耗时,因为这个函数是模块内置函数,是将PDF提取成图像的必经之路。


而第二个函数也许还会有一线生机。可以看到这份程序实际的使用逻辑是这样的

读取PDF->

生成图片文件->

反色->

读入图片文件->

合并生成PDF

这个逻辑并无不可,但是过于浪费,中间的生成文件理论上应该不需要,因为具体的图像格式变换逻辑是:pixmap->PIL.Image->png->pixmap


中间步骤png略显多余,所以笔者在第一时间想要去除。


这又涉及到一个问题:


PIL.Image如何转换为pixmap,这一点官方手册并未直接给出,而是给出了numpy.array->pixmap

import numpy as np
import fitz
#==============================================================================
# create a fun-colored width * height PNG with fitz 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 = fitz.Pixmap(fitz.csRGB, width, height, samples, alpha=False)
pix.save("test.png")

所以逻辑就变成了pixmap->PIL.Image->numpy.array->pixmap


而原有的两个自定义函数也被笔者改为了一个自定义函数。


说到这里,应该放上一个速度对比截图的,但是很遗憾,由于笔者开发时不断发现PyMuPDF模块的新特性并应用,导致笔者只有最终版的程序可以跑通。


首先是zoom参数矩阵被替换为dpi参数

#原程序(旧版本1.18.16)
rotate = int(0) # 不进行旋转
zoom_x = 5  
zoom_y = zoom_x
mat = fitz.Matrix(zoom_x, zoom_y).preRotate(rotate)
pix = page.getPixmap(matrix=mat, alpha=False)

#新程序(新版本1.20.2)
pix = page.get_pixmap(dpi=600)

其次是一个重大改进:舍弃掉了PIL库,因为pixmap数据类型自带反色函数invert_irect(),并配合其他相关处理函数,整体操作逻辑变成了:

读取PDF->

生成图片->

反色->

合并生成PDF

格式变换逻辑变成了:
pdf->pixmap->pdf(上面所讨论的变换逻辑统统失效)

最终,程序从原来复杂的 50+ 行,变成了现在的 20+ 行:

import datetime, os, fitz

def Inverse(pdfPath, newPath):
    startTime_pdf2img = datetime.datetime.now()  # 开始时间
    pdfDoc = fitz.open(pdfPath)   # 打开源PDF
    outdoc = fitz.open()    # 建立输出PDF
    for page in pdfDoc:
        pix = page.get_pixmap(dpi=600)
        pix.invert_irect()
        outdoc.new_page(pno=-1, width= page.rect.x1, height= page.rect.y1)          # 输出文件新建页
        outpage = outdoc[-1]
        outpage.insert_image(outpage.rect,pixmap=pix)
        
    endTime_pdf2img = datetime.datetime.now()  # 结束时间
    print('转换时间=', (endTime_pdf2img - startTime_pdf2img).seconds,'s')
    if os.path.exists(newPath):
        os.remove(newPath)
    pdfDoc.close()
    outdoc.save(newPath)          # 保存pdf文件
    outdoc.close()

if __name__ == "__main__":
    pdfPath = "./PDF/陶瓷.pdf"
    newPath = "./out/反色-陶瓷.pdf"
    Inverse(pdfPath, newPath)

运行时间大幅缩短

dpi=600

time=24s

可根据需求修改dpi参数以提高分辨率(当然了pixmap带来的分辨率下滑是无法改变的,毕竟很多的PDF文件中的内容是矢量图形,笔者也在考虑是否应当将其改为svg

开源仓库:GithubGitee

这里是ZnYC!非常感谢看到最后,我们会在这里不定期更新一些奇奇怪怪的东西,欢迎关注!

您的赞赏是我们前进的最大动力!!

↘点↓个↓赞↓吧↙!!

  • 7
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
### 回答1: 我可以为你提供这段代码:from PIL import Imageimg = Image.open("图像的路径")inverted_image = ImageOps.invert(img)inverted_image.save("反色图像的路径") ### 回答2: 要使用Python来实现图像反色,可以使用PIL(Python Imaging Library)库来处理图像。首先,需要安装PIL库。 代码示例: ``` from PIL import Image # 打开图像文件 image = Image.open('image.jpg') # 获取图像的宽度和高度 width, height = image.size # 遍历每个像素点并计算反色 for x in range(width): for y in range(height): # 获取像素点的RGB值 r, g, b = image.getpixel((x, y)) # 计算反色RGB值 r = 255 - r g = 255 - g b = 255 - b # 设置像素点的RGB值 image.putpixel((x, y), (r, g, b)) #保存反色后的图像 image.save('inverted_image.jpg') ``` 上述代码首先打开了一张图像,然后获取了图像的宽度和高度。接下来,通过遍历每个像素点并计算反色的方式来实现图像反色。 最后,将反色后的图像保存到磁盘上。 使用这段代码,你可以将任何图像进行反色处理,生成一个反色版本的图像。将代码保存为.py文件并执行即可得到反色后的图像。需要注意的是,需事先安装PIL库才能运行该代码。 ### 回答3: 您可以使用PIL库来实现图像反色。以下是一段使用Python编写的代码示例: ```python from PIL import Image # 读取图像 image = Image.open("image.jpg") # 获取图片的宽度和高度 width, height = image.size # 遍历每个像素点 for x in range(width): for y in range(height): # 获取像素点的RGB值 r, g, b = image.getpixel((x, y)) # 计算反色值 r = 255 - r g = 255 - g b = 255 - b # 设置像素点的RGB值 image.putpixel((x, y), (r, g, b)) # 保存反色后的图像 image.save("inverted_image.jpg") ``` 在代码中,首先使用`Image.open()`方法打开图像文件,然后使用`image.size`获取图像的宽度和高度。接下来,使用嵌套的循环遍历图像中的每个像素点。使用`image.getpixel()`方法获取每个像素点的RGB值,并计算其反色值。最后,使用`image.putpixel()`方法设置像素点的反色RGB值。最后,使用`image.save()`方法保存反色后的图像。 注意,您需要将代码中的`"image.jpg"`替换为您自己的图像文件路径。生成的反色图像将保存为`"inverted_image.jpg"`。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值