当前位置: 首页 > 面试题库 >

如何有效地将PDF转换为PNG?

贾实
2023-03-14
问题内容

我具有以下功能,可以将PDF转换为一系列图像(每页一个图像):

import Quartz

func convertPDF(at sourceURL: URL, to destinationURL: URL, fileType: NSBitmapImageFileType, dpi: CGFloat = 200) throws -> [URL] {
    let fileExtension: String
    switch fileType {
    case .BMP:              fileExtension = "bmp"
    case .GIF:              fileExtension = "gif"
    case .JPEG, .JPEG2000:  fileExtension = "jpeg"
    case .PNG:              fileExtension = "png"
    case .TIFF:             fileExtension = "tiff"
    }

    let data = try Data(contentsOf: sourceURL)
    let pdfImageRep = NSPDFImageRep(data: data)!
    var imageURLs = [URL]()

    for i in 0..<pdfImageRep.pageCount {
        pdfImageRep.currentPage = i

        let width = pdfImageRep.size.width / 72 * dpi
        let height = pdfImageRep.size.height / 72 * dpi
        let image = NSImage(size: CGSize(width: width, height: height), flipped: false) { dstRect in
            pdfImageRep.draw(in: dstRect)
        }

        let bitmapImageRep = NSBitmapImageRep(data: image.tiffRepresentation!)!
        let bitmapData = bitmapImageRep.representation(using: fileType, properties: [:])!

        let imageURL = destinationURL.appendingPathComponent("\(sourceURL.deletingPathExtension().lastPathComponent)-Page\(i+1).\(fileExtension)")
        try bitmapData.write(to: imageURL, options: [.atomic])
        imageURLs.append(imageURL)
    }

    return imageURLs
}

这可以很好地工作,性能并没有那么快,但这并不重要。我的问题与内存消耗有关。假设我要转换一个较长的PDF(Apple的10-Q,长达51页):

let sourceURL = URL(string: "http://files.shareholder.com/downloads/AAPL/4907179320x0x952191/4B5199AE-34E7-47D7-8502-CF30488B3B05/10-Q_Q3_2017_As-Filed_.pdf")!
let destinationURL = URL(fileURLWithPath: "/Users/mike/PDF")
let _ = try convertPDF(at: sourceURL, to: destinationURL, fileType: .PNG, dpi: 200)

到最后一页的末尾,内存使用量一直增加到〜11GB!

我还注意到一些注意事项:

  • 当我通过Instruments运行此程序时,它出乎意料地显示没有泄漏。两个大记忆猪是bitmapImageRepbitmapData。它们似乎没有在两次迭代之间发布。
  • 即使与Debug构建相比,对其进行性能分析也会降低性能。
  • 减少DPI显然可以减少内存占用,但是行为保持不变。内存随着页数线性增加。
  • 转换单个51页PDF或51个单页PDF都是一样的。

那么如何减少html" target="_blank">内存占用呢?有没有更好的方法将PDF转换为图像?


问题答案:

经过一整天的努力,我最终回答了自己的问题。

解决方案是将其放到Core Graphics和Image I /
O框架中,以将每个PDF页面渲染到位图上下文中。由于每个页面都可以在其自己的线程上转换为位图,因此该问题非常适合并行化。

struct ImageFileType {
    var uti: CFString
    var fileExtention: String

    // This list can include anything returned by CGImageDestinationCopyTypeIdentifiers()
    // I'm including only the popular formats here
    static let bmp = ImageFileType(uti: kUTTypeBMP, fileExtention: "bmp")
    static let gif = ImageFileType(uti: kUTTypeGIF, fileExtention: "gif")
    static let jpg = ImageFileType(uti: kUTTypeJPEG, fileExtention: "jpg")
    static let png = ImageFileType(uti: kUTTypePNG, fileExtention: "png")
    static let tiff = ImageFileType(uti: kUTTypeTIFF, fileExtention: "tiff")
}

func convertPDF(at sourceURL: URL, to destinationURL: URL, fileType: ImageFileType, dpi: CGFloat = 200) throws -> [URL] {
    let pdfDocument = CGPDFDocument(sourceURL as CFURL)!
    let colorSpace = CGColorSpaceCreateDeviceRGB()
    let bitmapInfo = CGImageAlphaInfo.noneSkipLast.rawValue

    var urls = [URL](repeating: URL(fileURLWithPath : "/"), count: pdfDocument.numberOfPages)
    DispatchQueue.concurrentPerform(iterations: pdfDocument.numberOfPages) { i in
        // Page number starts at 1, not 0
        let pdfPage = pdfDocument.page(at: i + 1)!

        let mediaBoxRect = pdfPage.getBoxRect(.mediaBox)
        let scale = dpi / 72.0
        let width = Int(mediaBoxRect.width * scale)
        let height = Int(mediaBoxRect.height * scale)

        let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: 8, bytesPerRow: 0, space: colorSpace, bitmapInfo: bitmapInfo)!
        context.interpolationQuality = .high
        context.setFillColor(.white)
        context.fill(CGRect(x: 0, y: 0, width: width, height: height))
        context.scaleBy(x: scale, y: scale)
        context.drawPDFPage(pdfPage)

        let image = context.makeImage()!
        let imageName = sourceURL.deletingPathExtension().lastPathComponent
        let imageURL = destinationURL.appendingPathComponent("\(imageName)-Page\(i+1).\(fileType.fileExtention)")

        let imageDestination = CGImageDestinationCreateWithURL(imageURL as CFURL, fileType.uti, 1, nil)!
        CGImageDestinationAddImage(imageDestination, image, nil)
        CGImageDestinationFinalize(imageDestination)

        urls[i] = imageURL
    }
    return urls
}

用法:

let sourceURL = URL(string: "http://files.shareholder.com/downloads/AAPL/4907179320x0x952191/4B5199AE-34E7-47D7-8502-CF30488B3B05/10-Q_Q3_2017_As-Filed_.pdf")!
let destinationURL = URL(fileURLWithPath: "/Users/mike/PDF")
let urls = try convertPDF(at: sourceURL, to: destinationURL, fileType: .png, dpi: 200)

现在,转换速度非常快。内存使用率要低得多。显然,DPI越高,所需的CPU和内存就越多。由于我只有一个弱的Intel集成GPU,因此不确定GPU加速。



 类似资料:
  • 所以我试图生成一个数组,其中填充了唯一的随机整数,我发现用arraylist来实现这一点是最有效的方法。 现在我试着使用但我不太确定括号里应该放什么,也不确定这是否真的能起作用。有没有其他转换方法,因为我不能简单地通过。

  • 问题内容: 我想将mongo-go-driver中的 bson转换为json。 我应该小心处理,因为如果数据中存在则失败。 例如,我想将以下bson数据转换为json。 以下失败。 问题答案: 如果您知道BSON的结构,则可以创建一个实现和接口的自定义类型,并根据需要处理NaN。例: 如果您的BSON具有任意结构,则唯一的选择是使用反射遍历该结构,并将所有出现的NaN转换为类型(可能是如上所述的自

  • 问题内容: 我正在寻找一种有效的方法,如何轻松地将a转换为二进制字符串。我们说它的通常长度将是数千位。 例如,让我们有这个: 这是预期的结果: 我总体上有一些想法(信息流等),但是可能我缺少一些明显的标准方法。 问题答案: 因此,这是到目前为止我尝试过的最有效的方法: 或使用其他流的其他方式:

  • 问题内容: 我目前在一个项目中,需要执行一些步骤,以处理旧版Matlab代码(使用Matlab引擎),其余部分则使用Python(numpy)进行处理。 我注意到,将结果从Matlab转换为numpy的速度似乎非常慢。 这是一些示例代码,用于从另一个ndarray,列表和mlarray创建具有1000个元素的ndarray: 这需要以下时间: 转换所需时间约为列表转换的100倍。 有什么办法可以加

  • 问题内容: 如何将Word文档转换为PDF,其中文档包含各种内容,例如表格。尝试使用iText时,原始文档看起来与转换后的PDF不同。有没有我可以使用的开源API /库,而不是调用可执行文件? 问题答案: 这是一项艰巨的任务,如果您想要完美的结果(如果不使用Word则不可能),则难度就更大了,因为仅使用纯Java即可为您完成所有操作且都是开源的API数量为零,我相信( 更新:我错了,请参见下文 )

  • 问题内容: 我已经使用htmldoc一段时间了,但是遇到了一些相当严重的限制。我需要最终解决方案才能在Linux机器上工作。我将从Perl应用程序调用此库/实用程序/应用程序,因此任何Perl接口都将是一个奖励。 问题答案: 注意:此答案来自2008年,现在可能不正确;请检查其他答案 PrinceXML是我见过的最好的XML(它解析常规HTML以及XML / XHTML)。最好吗?好吧,它通过了a