使用 SwiftUI 对图像进行下采样

Downsampling Images with SwiftUI

我在我的应用程序中显示从网络下载的图像,但我想对它们进行缩减采样,以免它们占用数 MB 的内存。我以前可以用 UIKit 很容易地做到这一点:

func resizedImage(image: UIImage, for size: CGSize) -> UIImage? {
    let renderer = UIGraphicsImageRenderer(size: size)
    return renderer.image { (context) in
        image.draw(in: CGRect(origin: .zero, size: size))
    }
}

还有其他方法,但它们都依赖于了解图像视图的所需大小,这在 SwiftUI 中并不简单。

是否有专门用于下采样 SwiftUI 图像的好工具API/method?

此方法使用CIFilter缩小UIImage

https://developer.apple.com/library/archive/documentation/GraphicsImaging/Reference/CoreImageFilterReference/index.html#//apple_ref/doc/filter/ci/CILanczosScaleTransform

public extension UIImage {
    func downsampled(by reductionAmount: Float) -> UIImage? {

        let image = UIKit.CIImage(image: self)
        guard let lanczosFilter = CIFilter(name: "CILanczosScaleTransform") else { return nil }
        lanczosFilter.setValue(image, forKey: kCIInputImageKey)
        lanczosFilter.setValue(NSNumber.init(value: reductionAmount), forKey: kCIInputScaleKey)

        guard let outputImage = lanczosFilter.outputImage else { return nil }
        let context = CIContext(options: [CIContextOption.useSoftwareRenderer: false])
        guard let cgImage = context.createCGImage(outputImage, from: outputImage.extent) else { return nil}
        let scaledImage = UIImage(cgImage: cgImage)

        return scaledImage
    }
}

然后就可以在SwiftUI View中使用了

struct ContentView: View {
    var body: some View {
        if let uiImage = UIImage(named: "sample")?.downsampled(by: 0.3) {
            Image(uiImage: uiImage)
        }
    }
}

我最终用几何 reader 解决了它,这并不理想,因为它有点弄乱了布局。

@State var image: UIImage
var body: some View {
   GeometryReader { geo in
       Image(uiImage: self.image)
           .resizable()
           .aspectRatio(contentMode: .fit)
           .onAppear {
               let imageFrame = CGRect(x: 0, y: 0, width: geo.size.width, height: geo.size.height)
               self.downsize(frame: imageFrame) // call whatever downsizing function you want
           }
      }
}

使用几何代理确定图像的帧,然后下采样到该帧。我希望 SwiftUI 为此拥有自己的 API。

要调整大小,请使用此功能。它在列表或 LazyVStack 中也能快速运行并减少图像的内存消耗。

public var body: some View {
    GeometryReader { proxy in
        let image = UIImage(named: imageName)?
            .resize(height: proxy.size.height)
        Image(uiImage: image ?? UIImage())
            .resizable()
            .scaledToFill()
    }
}
public extension UIImage {
    
    /// Resizes the image by keeping the aspect ratio
    func resize(height: CGFloat) -> UIImage {
        let scale = height / self.size.height
        let width = self.size.width * scale
        let newSize = CGSize(width: width, height: height)
        let renderer = UIGraphicsImageRenderer(size: newSize)
        
        return renderer.image { _ in
            self.draw(in: CGRect(origin: .zero, size: newSize))
        }
    }
}