3
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Core GraphicsでPDFから画像をぶっこ抜き

Last updated at Posted at 2019-01-19

アプリ上でPDFから画像をぶっこ抜きたい(ネットランナー感)ことってありますよね!?

...ありますよね!?

それ、Core Graphicsを使えばできます。

Core Graphicsについて

Core Graphicsは、UIImageなどの画像を使った処理ではなく、一からオリジナルの画像を描画するために使用されるフレームワークです。
そんなCore Graphicsさんですが、CGPDFDocumentというAPIがあり、PDF内の要素を取得することができます。

実装

なかなかに大変です。
PDFのコンテンツデータにはオペレータと呼ばれる、ストリームに含まれるデータに対する処理方法を示す記号が含まれています。
コンテンツデータを逐次読んでいき、読み込みたい種類のデータを示すオペレータが見つかったら、コールバックを行うAPIがCore Graphicsに用意されています。

class PDFExtractor{
    private var returnImageData: Array<CFData> = Array<CFData>()

    public func extractImage(filePath: CFURL) -> Array<CFData>?{
        //PDFファイルを読み込む
        guard let pdfDocument: CGPDFDocument = CGPDFDocument(filePath) else { return nil }
        
        //コールバックを登録するために、PDFOperatorTableを生成
        let table: CGPDFScannerRef = CGPDFOperatorTableCreate()!
        
        //コールバックを登録する。"Do"オペレータをスキャンしたらコールバックする
        //https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/PDF32000_2008.pdf
        CGPDFOperatorTableSetCallback(table, "Do", {(scanner: CGPDFScannerRef, info: UnsafeMutableRawPointer?) -> Void in
            let stream: CGPDFContentStreamRef = CGPDFScannerGetContentStream(scanner)
            //画像のファイル名を取得する
            var name: UnsafePointer<Int8>?
            if !CGPDFScannerPopName(scanner, &name) { return }
            
            //ラスタ画像のカテゴリは"XObjext"となる。取得したファイル名を指定する
            guard let object: CGPDFObjectRef = CGPDFContentStreamGetResource(stream, "XObject", name!) else { return }
            
            //画像のstreamを取得する
            var objectStream: CGPDFObjectRef?
            if(!CGPDFObjectGetValue(object, CGPDFObjectType.stream, &objectStream)){ return }
            
            //画像のstreamの種類を判別する
            guard let objectDictionary = CGPDFStreamGetDictionary(objectStream!) else{ return }
            var subType: UnsafePointer<Int8>?
            if(!CGPDFDictionaryGetName(objectDictionary, "Subtype", &subType)) { return }
            if(String(cString: subType!) != "Image") { return }
            
            //streamをCFDataとしてコピー
            var format: CGPDFDataFormat = CGPDFDataFormat.raw
            guard let data: CFData = CGPDFStreamCopyData(objectStream!, &format) else { return }
            
            //クラスのポインタを使って取得した画像を配列に入れる
            let instance = Unmanaged<PDFExtractor>.fromOpaque(info!).takeUnretainedValue()
            instance.returnImageData.append(data)
        })
        
        //ページ毎に画像を取得する
        for i in 1 ... pdfDocument.numberOfPages{
            let page = pdfDocument.page(at: i)
            let stream : CGPDFContentStreamRef = CGPDFContentStreamCreateWithPage(page!)
            
            //取り出した複数の画像を返すために、クラスのポインタを渡す
            let pointer :  UnsafeMutableRawPointer = UnsafeMutableRawPointer(mutating: bridge(obj: self))
            let scanner : CGPDFScannerRef = CGPDFScannerCreate(stream, table, pointer)
            
            //ContentStreamをスキャンする。
            //CGPDFOperatorTableに登録されたオペレータをスキャンすると、コールバックされる
            CGPDFScannerScan(scanner)
            
            //ちゃんと解放しましょう
            CGPDFScannerRelease(scanner)
            CGPDFContentStreamRelease(stream)
        }
        
        CGPDFOperatorTableRelease(table)
        
        return self.returnImageData
    }

    private func bridge<T: AnyObject>(obj: T) -> UnsafeRawPointer{
        return UnsafeRawPointer(Unmanaged.passUnretained(obj).toOpaque())
    }
}

終わりに

PDFのファイル構造について全くの無知だったのですが、構造自体はシンプルなことがわかりました。
、、が仕様がかなり膨大です。
ちなみにPDFの仕様はhttps://www.adobe.com/devnet/pdf/pdf_reference.html で公開されています。

あ、悪用厳禁でお願いします(ネットランナー感)。

3
7
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?