我在研究 Swift 中的图像压缩和导出时遇到了这个问题,并以此为起点来更好地理解问题并推导出更好的技术。
UIGraphicsBeginImageContext()、UIGraphicsGetImageFromCurrentImageContext()、UIGraphicsEndImageContext() 过程是一种较旧的技术,已被 UIGraphicsImageRenderer 取代,如 iron_john_bonney 和 leo-dabus 使用的那样。他们的示例是作为 UIImage 的扩展编写的,而我选择编写一个独立的函数。方法上所需的差异可以通过比较来识别(查看和靠近 UIGraphicsImageRenderer 调用),并且可以很容易地移植回 UIImage 扩展。
我认为这里使用的压缩算法有改进的潜力,所以我采取了一种方法,首先将图像调整为具有给定的总像素数,然后通过调整 jpeg 压缩来压缩它以达到指定的最终文件大小。指定像素总数的目的是避免陷入图像纵横比问题。虽然我没有做过详尽的调查,但我怀疑将图像缩放到指定的总像素数会使最终的 jpeg 图像文件大小在一个一般范围内,然后可以使用 jpeg 压缩来确保文件大小限制以可接受的图像质量实现,前提是初始像素数不太高。
在使用 UIGraphicsImageRenderer 时,CGRect 在主机 Apple 设备上以 逻辑 像素指定,这与输出 jpeg 中的实际像素不同。查找设备像素比率以了解这一点。为了获得设备像素比,我尝试从环境中提取它,但这些技术导致 Playground 崩溃,因此我使用了一种效率较低的技术。
如果您将此代码粘贴到 Xcode playround 中并将适当的 .jpg 文件放置在 Resources 文件夹中,则输出文件将放置在 Playground 输出文件夹中(使用实时视图中的快速查看来查找此位置)。
import UIKit
func compressUIImage(_ image: UIImage?, numPixels: Int, fileSizeLimitKB: Double, exportImage: Bool) -> Data {
var returnData: Data
if let origWidth = image?.size.width,
let origHeight = image?.size.height {
print("Original image size =", origWidth, "*", origHeight, "pixels")
let imgMult = min(sqrt(CGFloat(numPixels)/(origWidth * origHeight)), 1) // This multiplier scales the image to have the desired number of pixels
print("imageMultiplier =", imgMult)
let cgRect = CGRect(origin: .zero, size: CGSize(width: origWidth * imgMult, height: origHeight * imgMult)) // This is in *logical* pixels
let renderer = UIGraphicsImageRenderer(size: cgRect.size)
let img = renderer.image { ctx in
image?.draw(in: cgRect)
}
// Now get the device pixel ratio if needed...
var img_scale: CGFloat = 1
if exportImage {
img_scale = img.scale
}
print("Image scaling factor =", img_scale)
// ...and use to ensure *output* image has desired number of pixels
let cgRect_scaled = CGRect(origin: .zero, size: CGSize(width: origWidth * imgMult/img_scale, height: origHeight * imgMult/img_scale)) // This is in *logical* pixels
print("New image size (in logical pixels) =", cgRect_scaled.width, "*", cgRect_scaled.height, "pixels") // Due to device pixel ratios, can have fractional pixel dimensions
let renderer_scaled = UIGraphicsImageRenderer(size: cgRect_scaled.size)
let img_scaled = renderer_scaled.image { ctx in
image?.draw(in: cgRect_scaled)
}
var compQual = CGFloat(1.0)
returnData = img_scaled.jpegData(compressionQuality: 1.0)!
var imageSizeKB = Double(returnData.count) / 1000.0
print("compressionQuality =", compQual, "=> imageSizeKB =", imageSizeKB, "KB")
while imageSizeKB > fileSizeLimitKB {
compQual *= 0.9
returnData = img_scaled.jpegData(compressionQuality: compQual)!
imageSizeKB = Double(returnData.count) / 1000.0
print("compressionQuality =", compQual, "=> imageSizeKB =", imageSizeKB, "KB")
}
} else {
returnData = Data()
}
return returnData
}
let image_orig = UIImage(named: "input.jpg")
let image_comp_data = compressUIImage(image_orig, numPixels: Int(4e6), fileSizeLimitKB: 1300, exportImage: true)
func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return paths[0]
}
let filename = getDocumentsDirectory().appendingPathComponent("output.jpg")
try? image_comp_data.write(to: filename)
来源包括Jordan Morgan和Hacking with Swift。