使用Swift如何从PDF中提取图像?

6
我了解到,PDFKit可以将文本和格式作为NSAttributedString提取出来,但是我找不到任何关于使用Swift从任何PDF文档中提取每个单独图像的信息。
如有帮助,将不胜感激,谢谢!
编辑:https://dev59.com/gFwY5IYBdhLWcg3weXgb#40788449 解释了如何将整个页面转换为图像,但我需要解析已经包含在一系列PDF文档中的所有图像,而不知道它们位于何处,因此该解决方案不适用于我的问题。

感谢@Damon的编辑建议!你可以看出我在这里是新手 :) - Debee
所以你需要编辑你的问题,展示你尝试过什么,以及你所面临的问题,否则我无法重新打开你的问题。 - Leo Dabus
这个问题应该再次关闭。 - Leo Dabus
感谢@LeoDabus - https://dev59.com/I0zSa4cB1Zd3GeqPoarx#2492305 提供的解决方案是Obj-C且该帖子已关闭 - 我本来想在那里添加评论以请求Swift 4版本的解决方案,但我不能(需要至少10个声望)。 - Debee
1
真棒,非常感谢 @LeoDabus 的帮助,以及对像我这样的新手额外的耐心! - Debee
显示剩余5条评论
1个回答

5
这里是一个 Swift 函数,用于从 PDF 页面中提取图像,更具体地说,是提取所有带有子类型“Image”的对象:
import PDFKit

func extractImages(from pdf: PDFDocument, extractor: @escaping (ImageInfo)->Void) throws {
    for pageNumber in 0..<pdf.pageCount {
        guard let page = pdf.page(at: pageNumber) else {
            throw PDFReadError.couldNotOpenPageNumber(pageNumber)
        }
        try extractImages(from: page, extractor: extractor)
    }
}

func extractImages(from page: PDFPage, extractor: @escaping (ImageInfo)->Void) throws {
    let pageNumber = page.label ?? "unknown page"
    guard let page = page.pageRef else {
        throw PDFReadError.couldNotOpenPage(pageNumber)
    }

    guard let dictionary = page.dictionary else {
        throw PDFReadError.couldNotOpenDictionaryOfPage(pageNumber)
    }

    guard let resources = dictionary[CGPDFDictionaryGetDictionary, "Resources"] else {
        throw PDFReadError.couldNotReadResources(pageNumber)
    }

    if let xObject = resources[CGPDFDictionaryGetDictionary, "XObject"] {
        print("reading resources of page", pageNumber)

        func extractImage(key: UnsafePointer<Int8>, object: CGPDFObjectRef, info: UnsafeMutableRawPointer?) -> Bool {
            guard let stream: CGPDFStreamRef = object[CGPDFObjectGetValue, .stream] else { return true }
            guard let dictionary = CGPDFStreamGetDictionary(stream) else {return true}

            guard dictionary.getName("Subtype", CGPDFDictionaryGetName) == "Image" else {return true}

            let colorSpaces = dictionary.getNameArray(for: "ColorSpace") ?? []
            let filter = dictionary.getNameArray(for: "Filter") ?? []

            var format = CGPDFDataFormat.raw
            guard let data = CGPDFStreamCopyData(stream, &format) as Data? else { return false }

            extractor(
              ImageInfo(
                name: String(cString: key),
                colorSpaces: colorSpaces,
                filter: filter,
                format: format,
                data: data
              )
            )

            return true
        }

        CGPDFDictionaryApplyBlock(xObject, extractImage, nil)
    }
}

struct ImageInfo: CustomDebugStringConvertible {
    let name: String
    let colorSpaces: [String]
    let filter: [String]
    let format: CGPDFDataFormat
    let data: Data

    var debugDescription: String {
        """
          Image "\(name)"
           - color spaces: \(colorSpaces)
           - format: \(format == .JPEG2000 ? "JPEG2000" : format == .jpegEncoded ? "jpeg" : "raw")
           - filters: \(filter)
           - size: \(ByteCountFormatter.string(fromByteCount: Int64(data.count), countStyle: .binary))
        """
    }
}

extension CGPDFObjectRef {
    func getName<K>(_ key: K, _ getter: (OpaquePointer, K, UnsafeMutablePointer<UnsafePointer<Int8>?>)->Bool) -> String? {
        guard let pointer = self[getter, key] else { return nil }
        return String(cString: pointer)
    }

    func getName<K>(_ key: K, _ getter: (OpaquePointer, K, UnsafeMutableRawPointer?)->Bool) -> String? {
        guard let pointer: UnsafePointer<UInt8> = self[getter, key] else { return nil }
        return String(cString: pointer)
    }

    subscript<R, K>(_ getter: (OpaquePointer, K, UnsafeMutablePointer<R?>)->Bool, _ key: K) -> R? {
        var result: R!
        guard getter(self, key, &result) else { return nil }
        return result
    }

    subscript<R, K>(_ getter: (OpaquePointer, K, UnsafeMutableRawPointer?)->Bool, _ key: K) -> R? {
        var result: R!
        guard getter(self, key, &result) else { return nil }
        return result
    }

    func getNameArray(for key: String) -> [String]? {
        var object: CGPDFObjectRef!
        guard CGPDFDictionaryGetObject(self, key, &object) else { return nil }

        if let name = object.getName(.name, CGPDFObjectGetValue) {
            return [name]
        } else {
            guard let array: CGPDFArrayRef = object[CGPDFObjectGetValue, .array] else {return nil}
            var names = [String]()
            for index in 0..<CGPDFArrayGetCount(array) {
                guard let name = array.getName(index, CGPDFArrayGetName) else { continue }
                names.append(name)
            }
            return names
        }
    }
}

enum PDFReadError: Error {
    case couldNotOpenPageNumber(Int)
    case couldNotOpenPage(String)
    case couldNotOpenDictionaryOfPage(String)
    case couldNotReadResources(String)
    case cannotReadXObjectStream(xObject: String, page: String)
}

你需要知道 PDF 中的图像可以以不同的方式表示。它们可以作为自包含的 JPG 嵌入,也可以作为原始像素数据(有损压缩或无损压缩)嵌入,同时还提供了有关压缩、颜色空间、宽度、高度等元信息。

因此,如果您想要导出嵌入的 JPG,则这段代码非常有效。但是,如果您还想可视化原始图像,则需要更多的解析代码。您可以查看 PDF 2.0 规范(或较旧的免费规范版本),以及这个代码片段,该代码片段可以解释任何颜色配置文件中的 JPG 和以下任何颜色配置文件中的原始图像:

  • DeviceGray
  • DeviceRGB
  • DeviceCMYK
  • Indexed
  • ICCBased

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接