Swift。如何从 HEIC 图像中提取单个子图像
Swift. How to extract the individual sub images from an HEIC image
我需要能够加载 heic 图像并提取所有子图像并将其输出为 png,类似于预览的方式。例如,如果您在预览中打开动态 heic 壁纸,它会在边栏中显示所有图像及其名称:
你是怎么做到的?我试过像下面这样使用 NSImage 。但这只输出一张图片:
let image = NSImage(byReferencing: url)
image.writePNG(toURL: newUrl)
您需要加载 HEIC 数据,获取其 CGImageSource 及其计数。然后创建一个从 0 到 count-1 的循环,并在相应的索引处获取每个图像。您可以在内存中使用这些 CGImages 创建一个数组或将它们写入磁盘(首选)。请注意,由于 HEIC 文件的大小为 186MB,这将需要一段时间才能执行。提取的每个图像将从 19MB 到 28MB。
func extractHeicImages(from url: URL) throws {
let data = try Data(contentsOf: url)
let location = url.deletingLastPathComponent()
let pathExtension = url.pathExtension
let fileName = url.deletingPathExtension().lastPathComponent
let destinationFolder = location.appendingPathComponent(fileName)
guard pathExtension == "heic", let imageSource = CGImageSourceCreateWithData(data as CFData, nil) else { return }
let count = CGImageSourceGetCount(imageSource)
try FileManager.default.createDirectory(at: destinationFolder, withIntermediateDirectories: false, attributes: nil)
for index in 0..<count {
try autoreleasepool {
if let cgImage = CGImageSourceCreateImageAtIndex(imageSource, index, nil) {
let number = String(format: "#%05d", index)
let destinationURL = destinationFolder
.appendingPathComponent(fileName+number)
.appendingPathExtension(pathExtension)
try NSImage(cgImage: cgImage, size: .init(width: cgImage.width, height: cgImage.height))
.heic?
.write(to: destinationURL)
print("saved image " + number)
}
}
}
}
您还需要这些助手来从图像中提取 cgimate 并从中获取 HEIC 数据表示:
extension NSImage {
var heic: Data? { heic() }
var cgImage: CGImage? {
var rect = NSRect(origin: .zero, size: size)
return cgImage(forProposedRect: &rect, context: .current, hints: nil)
}
func heic(compressionQuality: CGFloat = 1) -> Data? {
guard
let mutableData = CFDataCreateMutable(nil, 0),
let destination = CGImageDestinationCreateWithData(mutableData, "public.heic" as CFString, 1, nil),
let cgImage = cgImage
else { return nil }
CGImageDestinationAddImage(destination, cgImage, [kCGImageDestinationLossyCompressionQuality: compressionQuality] as CFDictionary)
guard CGImageDestinationFinalize(destination) else { return nil }
return mutableData as Data
}
}
游乐场测试。这假设“Catalina.heic”位于您的桌面。
let catalinaURL = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask).first!.appendingPathComponent("Catalina.heic")
do {
try extractHeicImages(from: catalinaURL)
} catch {
print(error)
}
每个子图像由 NSBitmapImageRep
表示。循环图像代表,转换为 png 并保存:
let imageReps = image.representations
for imageIndex in 0..<imageReps.count {
if let imageRep = imageReps[imageIndex] as? NSBitmapImageRep {
if let data = imageRep.representation(using: .png, properties: [:]) {
do {
let url = folderURL.appendingPathComponent("image \(imageIndex).png", isDirectory: false)
try data.write(to: url, options:[])
} catch {
print("Unexpected error: \(error).")
}
}
}
}
转换为 png 需要一些时间。 运行 并行转换更快,但我不确定它是否保存:
DispatchQueue.concurrentPerform(iterations: imageReps.count) { iteration in
if let imageRep = imageReps[iteration] as? NSBitmapImageRep {
if let data = imageRep.representation(using: .png, properties: [:]) {
do {
let url = folderURL.appendingPathComponent("image \(iteration).png", isDirectory: false)
try data.write(to: url, options:[])
} catch {
print("Unexpected error: \(error).")
}
}
}
}
我需要能够加载 heic 图像并提取所有子图像并将其输出为 png,类似于预览的方式。例如,如果您在预览中打开动态 heic 壁纸,它会在边栏中显示所有图像及其名称:
你是怎么做到的?我试过像下面这样使用 NSImage 。但这只输出一张图片:
let image = NSImage(byReferencing: url)
image.writePNG(toURL: newUrl)
您需要加载 HEIC 数据,获取其 CGImageSource 及其计数。然后创建一个从 0 到 count-1 的循环,并在相应的索引处获取每个图像。您可以在内存中使用这些 CGImages 创建一个数组或将它们写入磁盘(首选)。请注意,由于 HEIC 文件的大小为 186MB,这将需要一段时间才能执行。提取的每个图像将从 19MB 到 28MB。
func extractHeicImages(from url: URL) throws {
let data = try Data(contentsOf: url)
let location = url.deletingLastPathComponent()
let pathExtension = url.pathExtension
let fileName = url.deletingPathExtension().lastPathComponent
let destinationFolder = location.appendingPathComponent(fileName)
guard pathExtension == "heic", let imageSource = CGImageSourceCreateWithData(data as CFData, nil) else { return }
let count = CGImageSourceGetCount(imageSource)
try FileManager.default.createDirectory(at: destinationFolder, withIntermediateDirectories: false, attributes: nil)
for index in 0..<count {
try autoreleasepool {
if let cgImage = CGImageSourceCreateImageAtIndex(imageSource, index, nil) {
let number = String(format: "#%05d", index)
let destinationURL = destinationFolder
.appendingPathComponent(fileName+number)
.appendingPathExtension(pathExtension)
try NSImage(cgImage: cgImage, size: .init(width: cgImage.width, height: cgImage.height))
.heic?
.write(to: destinationURL)
print("saved image " + number)
}
}
}
}
您还需要这些助手来从图像中提取 cgimate 并从中获取 HEIC 数据表示:
extension NSImage {
var heic: Data? { heic() }
var cgImage: CGImage? {
var rect = NSRect(origin: .zero, size: size)
return cgImage(forProposedRect: &rect, context: .current, hints: nil)
}
func heic(compressionQuality: CGFloat = 1) -> Data? {
guard
let mutableData = CFDataCreateMutable(nil, 0),
let destination = CGImageDestinationCreateWithData(mutableData, "public.heic" as CFString, 1, nil),
let cgImage = cgImage
else { return nil }
CGImageDestinationAddImage(destination, cgImage, [kCGImageDestinationLossyCompressionQuality: compressionQuality] as CFDictionary)
guard CGImageDestinationFinalize(destination) else { return nil }
return mutableData as Data
}
}
游乐场测试。这假设“Catalina.heic”位于您的桌面。
let catalinaURL = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask).first!.appendingPathComponent("Catalina.heic")
do {
try extractHeicImages(from: catalinaURL)
} catch {
print(error)
}
每个子图像由 NSBitmapImageRep
表示。循环图像代表,转换为 png 并保存:
let imageReps = image.representations
for imageIndex in 0..<imageReps.count {
if let imageRep = imageReps[imageIndex] as? NSBitmapImageRep {
if let data = imageRep.representation(using: .png, properties: [:]) {
do {
let url = folderURL.appendingPathComponent("image \(imageIndex).png", isDirectory: false)
try data.write(to: url, options:[])
} catch {
print("Unexpected error: \(error).")
}
}
}
}
转换为 png 需要一些时间。 运行 并行转换更快,但我不确定它是否保存:
DispatchQueue.concurrentPerform(iterations: imageReps.count) { iteration in
if let imageRep = imageReps[iteration] as? NSBitmapImageRep {
if let data = imageRep.representation(using: .png, properties: [:]) {
do {
let url = folderURL.appendingPathComponent("image \(iteration).png", isDirectory: false)
try data.write(to: url, options:[])
} catch {
print("Unexpected error: \(error).")
}
}
}
}