【发布时间】:2018-08-24 19:40:06
【问题描述】:
值得澄清的是,对于编程语言 Swift 及其所有组件,我已经了解了三天! 三天我尝试解决这个问题,我没有找到一个单一的解决方案,我翻遍了很多网站!我需要确保上传到服务器时的图像很有趣,不超过 150 kb,但同时要保证质量好。 在搜索过程中,我发现了一种算法,在网站和 GitHub 上,他们写道,它是 WhatsApp messenger 图像压缩的副本,但在 iphone 8 上它不能很好地压缩图像。举个例子,iphone 8上拍照的图片在传输到Messenger WhatsApp时压缩到100kb,而图片质量非常好,长宽都超过1000px。如果你使用这个算法,那么在长度为:800px、宽度为:600px、压缩比为 0.01 的情况下,图像的重量超过 200 kb。而且质量很糟糕。 这是算法:
extension UIImage {
func compressImage() -> UIImage? {
// Reducing file size to a 10th
var actualHeight: CGFloat = self.size.height
var actualWidth: CGFloat = self.size.width
let maxHeight: CGFloat = 800.0
let maxWidth: CGFloat = 600.0
var imgRatio: CGFloat = actualWidth/actualHeight
let maxRatio: CGFloat = maxWidth/maxHeight
var compressionQuality: CGFloat = 0.01
if actualHeight > maxHeight || actualWidth > maxWidth {
if imgRatio < maxRatio {
//adjust width according to maxHeight
imgRatio = maxHeight / actualHeight
actualWidth = imgRatio * actualWidth
actualHeight = maxHeight
} else if imgRatio > maxRatio {
//adjust height according to maxWidth
imgRatio = maxWidth / actualWidth
actualHeight = imgRatio * actualHeight
actualWidth = maxWidth
} else {
actualHeight = maxHeight
actualWidth = maxWidth
//compressionQuality = 1
}
}
let rect = CGRect(x: 0.0, y: 0.0, width: actualWidth, height: actualHeight)
UIGraphicsBeginImageContext(rect.size)
self.draw(in: rect)
guard let img = UIGraphicsGetImageFromCurrentImageContext() else {
return nil
}
UIGraphicsEndImageContext()
guard let imageData = UIImageJPEGRepresentation(img, compressionQuality) else {
return nil
}
return UIImage(data: imageData)
}
}
由于它不适用于这个算法,我开始寻找 Swift 的图像压缩库,但不幸的是我没有找到任何东西!然后我尝试使用LZFSE压缩算法,但据我了解,在另一台设备上没有解压缩器(假设使用Android OS),图像将不会显示,此外,他在其中一行代码中抱怨nil-data . 代码如下:
public enum CompressionAlgorithm {
case lz4 // speed is critical
case lz4a // space is critical
case zlib // reasonable speed and space
case lzfse // better speed and space
}
private enum CompressionOperation {
case compression, decompression
}
private func perform(_ operation: CompressionOperation,
on input: Data,
using algorithm: CompressionAlgorithm,
workingBufferSize: Int = 2000) -> Data? {
var output = Data()
// set the algorithm
let streamAlgorithm: compression_algorithm
switch algorithm {
case .lz4: streamAlgorithm = COMPRESSION_LZ4
case .lz4a: streamAlgorithm = COMPRESSION_LZMA
case .zlib: streamAlgorithm = COMPRESSION_ZLIB
case .lzfse: streamAlgorithm = COMPRESSION_LZFSE
}
// set the stream operation, and flags
let streamOperation: compression_stream_operation
let flags: Int32
switch operation {
case .compression:
streamOperation = COMPRESSION_STREAM_ENCODE
flags = Int32(COMPRESSION_STREAM_FINALIZE.rawValue)
case .decompression:
streamOperation = COMPRESSION_STREAM_DECODE
flags = 0
}
// create a stream
var streamPointer = UnsafeMutablePointer<compression_stream>
.allocate(capacity: 1)
defer {
streamPointer.deallocate(capacity: 1)
}
// initialize the stream
var stream = streamPointer.pointee
var status = compression_stream_init(&stream, streamOperation, streamAlgorithm)
guard status != COMPRESSION_STATUS_ERROR else {
return nil
}
defer {
compression_stream_destroy(&stream)
}
// set up a destination buffer
let dstSize = workingBufferSize
let dstPointer = UnsafeMutablePointer<UInt8>.allocate(capacity: dstSize)
defer {
dstPointer.deallocate(capacity: dstSize)
}
// process the input
return input.withUnsafeBytes { (srcPointer: UnsafePointer<UInt8>) in
stream.src_ptr = srcPointer
stream.src_size = input.count
stream.dst_ptr = dstPointer
stream.dst_size = dstSize
while status == COMPRESSION_STATUS_OK {
// process the stream
status = compression_stream_process(&stream, flags)
// collect bytes from the stream and reset
switch status {
case COMPRESSION_STATUS_OK:
output.append(dstPointer, count: dstSize)
stream.dst_ptr = dstPointer
stream.dst_size = dstSize
case COMPRESSION_STATUS_ERROR:
return nil
case COMPRESSION_STATUS_END:
output.append(dstPointer, count: stream.dst_ptr - dstPointer)
default:
fatalError()
}
}
return output
}
}
// Compressed keeps the compressed data and the algorithm
// together as one unit, so you never forget how the data was
// compressed.
public struct Compressed {
public let data: Data
public let algorithm: CompressionAlgorithm
public init(data: Data, algorithm: CompressionAlgorithm) {
self.data = data
self.algorithm = algorithm
}
// Compress the input with the specified algorithm. Returns nil if it fails.
public static func compress(input: Data,
with algorithm: CompressionAlgorithm) -> Compressed? {
guard let data = perform(.compression, on: input, using: algorithm) else {
return nil
}
return Compressed(data: data, algorithm: algorithm)
}
// Factory method to return uncompressed data. Returns nil if it cannot be decompressed.
public func makeDecompressed() -> Data? {
return perform(.decompression, on: data, using: algorithm)
}
}
// For discoverability, add a compressed method to Data
extension Data {
// Factory method to make compressed data or nil if it fails.
public func makeCompressed(with algorithm: CompressionAlgorithm) -> Compressed? {
return Compressed.compress(input: self, with: algorithm)
}
}
在此行中,应用此算法后:
UIImage(data: imageCompressData)//к переменной imageCompressData был применен код сверху!
接下来,我无意中发现了这段代码:
func resizeImageUsingVImage(image:UIImage, size:CGSize) -> UIImage? {
let cgImage = image.cgImage!
var format = vImage_CGImageFormat(bitsPerComponent: 8, bitsPerPixel: 32, colorSpace: nil, bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.first.rawValue), version: 0, decode: nil, renderingIntent: CGColorRenderingIntent.defaultIntent)
var sourceBuffer = vImage_Buffer()
defer {
free(sourceBuffer.data)
}
var error = vImageBuffer_InitWithCGImage(&sourceBuffer, &format, nil, cgImage, numericCast(kvImageNoFlags))
guard error == kvImageNoError else { return nil }
// create a destination buffer
let scale = image.scale
let destWidth = Int(size.width)
let destHeight = Int(size.height)
let bytesPerPixel = image.cgImage!.bitsPerPixel/8
let destBytesPerRow = destWidth * bytesPerPixel
let destData = UnsafeMutablePointer<UInt8>.allocate(capacity: destHeight * destBytesPerRow)
defer {
destData.deallocate(capacity: destHeight * destBytesPerRow)
}
var destBuffer = vImage_Buffer(data: destData, height: vImagePixelCount(destHeight), width: vImagePixelCount(destWidth), rowBytes: destBytesPerRow)
// scale the image
error = vImageScale_ARGB8888(&sourceBuffer, &destBuffer, nil, numericCast(kvImageHighQualityResampling))
guard error == kvImageNoError else { return nil }
// create a CGImage from vImage_Buffer
var destCGImage = vImageCreateCGImageFromBuffer(&destBuffer, &format, nil, nil, numericCast(kvImageNoFlags), &error)?.takeRetainedValue()
guard error == kvImageNoError else { return nil }
// create a UIImage
let resizedImage = destCGImage.flatMap { UIImage(cgImage: $0, scale: 0.0, orientation: image.imageOrientation) }
destCGImage = nil
return resizedImage
}
更详细地查看代码并研究了 vImage 和 Accelerate 库的文档后,我意识到这个非常强大的图像处理库,如果我有足够的知识,也许我可以创建一个好的图像压缩器,但是我的知识是不够的,所以所有的希望都寄托在你身上。
【问题讨论】:
-
压缩质量 0.01 的质量低得离谱。你不会得到可用的结果。我通常使用大约 0.6 或更好的质量。尝试将质量设置提高到更合理的值,然后查看您获得的文件大小。
标签: swift xcode image compression