我具有以下功能,可以将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!
我还注意到一些注意事项:
bitmapImageRep
bitmapData
那么如何减少内存占用呢?有没有更好的方法将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加速。