2017-08-19 8 views
1

私は一連の画像(1ページに1つの画像)にPDFを変換するには、次の機能を持っている:PDFをPNGに効率的に変換するには?

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) 

メモリ使用量は、最後のページの最後で〜11ギガバイトに増加し続けます!

私も気づかいくつかのこと:

  • 私は楽器を介してこれを実行したときには、驚くべきことに、何の漏出を示しませんでした。 2つの大きなメモリの豚はbitmapImageRepbitmapDataです。それらは反復の間に解放されたようには見えない。
  • これをプロファイリングすると、Debugビルドと比較してもパフォーマンスが低下します。
  • DPIを小さくするとメモリの占有面積は明らかに減少しますが、動作は変わりません。メモリはページ数に比例して増加します。
  • 51ページのPDFを1ページに変換するのか、1ページの51ページのPDFを変換するのかは同じです。

どのようにしてメモリフットプリントを減らすことができますか? PDFを画像に変換するより良い方法はありますか?

答えて

3

これで一日中苦労した後、私は自分自身の質問に答えることになります。

解決策は、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とメモリが増えます。私は弱いインテルの統合GPUしか持っていないので、GPUアクセラレーションについてはわかりません。

+0

質問があります - 私のapiレスポンスは "image"という文字列を返します。タイプは "png"または "pdf"をキャプチャする別のプロパティタイプです。文字列base64Encodedを使用して、どのようにpngにこのデコードされたデータを変換するのですか?任意のポインタ? – user2525211

関連する問題