代码之家  ›  专栏  ›  技术社区  ›  Tigran Iskandaryan

苹果的照片应用程序中的过滤器uiscrollview/uicollectionview是如何实现的?它的打开速度如此之快?

  •  2
  • Tigran Iskandaryan  · 技术社区  · 6 年前

    我不是在问确切的代码,而是整个想法。

    我的问题是:我正在尝试创建类似于在照片应用程序中筛选选择用户界面的东西。我尝试过多种方法,它们都有缺点。

    1)我尝试使用带有集合视图的 operation operationQueue which prefetching is enabled.这将快速加载ViewController,但在滚动时会丢弃帧。

    2)现在我使用的是滚动视图和 gcd,但它加载viewcontroller的时间太长(因为它一次将所有过滤器应用于其中的所有按钮),但随后它会平滑滚动。

    注: 要回答问题,不需要阅读下面的部分(我相信),但是如果您对我如何尝试实现功能感兴趣,欢迎您阅读。

    对于所有过滤器的实现,我使用一个名为 filters的结构,它负责启动每个过滤器并将其附加到数组中。

    结构过滤器{ var图像:uiimage var allfilters:[cifilter]=[] init(图像:uiimage){ self.image=图像 guard let sepia=sepia(image:image)else返回 allfilters.append(内容如下:[深褐色、深褐色、深褐色、深褐色、深褐色、深褐色、深褐色、深褐色、深褐色、深褐色]) } } < /代码>

    现在我只使用一个过滤器。 sepia cifilter的一个子类。我已经创建了它作为一个子类,因为将来我将从中创建一个自定义的子类。这是它的实现:

    class sepia:cifilter{
    
    var输入图像:ciimage?
    var输入强度:nsnumber?
    
    @objc override var filtername:字符串?{
    返回nslocalizedstring(“sepia”,comment:“筛选器名称”)。
    }
    
    方便初始化?(图像:ui图像,输入强度:nsnumber?= nIL){
    In()
    
    guard let cgimage=image.cgimage其他{
    返回零
    }
    
    如果输入强度!= nIL{
    self.inputinentity=输入强度
    }否则{
    self.setdefaults()。
    }
    
    让inputimage=ciimage(cgimage:cgimage)
    self.inputimage=输入图像
    }
    
    覆盖func setdefaults()。{
    输入强度=1.0
    }
    
    覆盖var输出图像:ciimage?{
    guard let inputimage=inputimage,let inputitensity=inputitensity else{
    返回零
    }
    
    设filter=cifilter(名称:“cispiatone”,带inputparameters:[kciinputimagekey:inputimage,kciinputitensitykey:inputitensity])
    
    返回过滤器?超标
    
    }
    }
    < /代码> 
    
    

    在viewController的viewdidLoadi initiatefiltersstruct:。

    self.filters=filters(image:image)
    < /代码> 
    
    

    然后,我调用一个方法,该方法根据filters.allfilters数组中的筛选器数配置一些视图(filterview),并对它们进行迭代,并调用一个方法,该方法获取缩略图uiimage并对其应用筛选器,然后在完成处理程序中返回它(我使用dispatchgroupin因为调试原因)。下面是将筛选器应用于缩略图的方法:

    func imagewithfilter(filter:cifilter,completion:@escaping(uiimage?)。-&空格){
    
    让group=DispatchGroup()。
    组()
    DispatchQueue.Global().Async{
    guard let outputimage=filter.value(forkey:kcioutputimagekey)为?ciimage,让cgimageresult=self.context.createcgimage(outputimage,from:outputimage.extent)else{
    DispatchQueue.main.asyncqueue(调度队列.main.asyncqueue){
    完成(零)
    }
    Leave[()
    返回
    }
    
    让filteredimage=uiimage(cgimage:cgimageresult)
    DispatchQueue.main.asyncqueue(调度队列.main.asyncqueue){
    打印(筛选图像)
    完成(筛选图像)
    
    }
    
    Leave[()
    }
    
    group.notify(队列:.main){
    打印(“过滤器已设置”)
    }
    }
    < /代码> 
    
    

    上面的打印语句和过滤后的图像地址很快就会打印出来,但是图像不会出现在视图中。

    我试过使用time profiler但是它给了我一些奇怪的结果。例如,它显示了在backtrace根目录中执行相当长的时间:

    当我试图在Xcode中看到代码时,我得到了以下信息,这没有多大帮助:

    所以,这就是问题所在。如果您对如何在照片应用程序中实现它有任何想法,认为它是如此快速和响应迅速,或者如果您对我的实现有任何建议,我将非常感谢您的帮助。

    1)我试过使用OperationOperationQueue使用集合视图,启用预取。这将快速加载视图控制器,但在滚动时会丢弃帧。

    2)现在我正在使用滚动视图和GCD但是它加载viewcontroller的时间太长(因为它一次将所有过滤器应用于其中的所有按钮),但随后它会平滑滚动。

    注:要回答这个问题,不需要阅读下面的部分(我相信),但是如果您对我如何尝试实现功能感兴趣,欢迎阅读。

    对于所有过滤器的实现,我使用一个名为Filters它负责启动每个过滤器并将其附加到一个数组中。

    struct Filters {
    var image: UIImage
    var allFilters: [CIFilter] = []
    
    init(image: UIImage) {
    
        self.image = image
    
        guard  let sepia = Sepia(image: image) else {return}
    
        allFilters.append(contentsOf: [sepia, sepia, sepia, sepia, sepia, sepia, sepia, sepia, sepia, sepia, sepia, sepia, sepia])
    
           }
      }
    

    现在我只使用一个过滤器。Sepia是的子类CIFilter. 我已经创建了它作为一个子类,因为将来我将从中创建一个自定义的子类。这是它的实现:

    class Sepia: CIFilter {
    
    var inputImage: CIImage?
    var inputIntensity: NSNumber?
    
    @objc override var filterName: String? {
        return NSLocalizedString("Sepia", comment: "Name of a Filter")
    }
    
    convenience init?(image: UIImage, inputIntensity: NSNumber? = nil) {
        self.init()
    
        guard let cgImage = image.cgImage else {
            return nil
        }
    
        if inputIntensity != nil {
            self.inputIntensity = inputIntensity
        } else {
            self.setDefaults()
        }
    
        let inputImage = CIImage(cgImage: cgImage)
        self.inputImage = inputImage
    }
    
    override func setDefaults() {
        inputIntensity = 1.0
    }
    
    override var outputImage: CIImage? {
        guard let inputImage = inputImage, let inputIntensity = inputIntensity else {
            return nil
        }
    
        let filter = CIFilter(name: "CISepiaTone", withInputParameters: [kCIInputImageKey: inputImage, kCIInputIntensityKey: inputIntensity])
    
       return filter?.outputImage
    
      }
    }
    

    在视图控制器中viewDidLoad我发起过滤器结构:

    self.filters = Filters(image: image)
    

    然后我调用一个方法来配置一些视图(filterViews)基于filters.allFilters数组并对其进行迭代,并调用一个获取缩略图的方法UIImage并对其应用筛选器,然后在完成处理程序中返回它(我使用DispatchGroup出于调试原因)。下面是将筛选器应用于缩略图的方法:

    func imageWithFilter(filter: CIFilter, completion: @escaping(UIImage?)->Void) {
    
        let group = DispatchGroup()
        group.enter()
        DispatchQueue.global().async {
            guard let outputImage = filter.value(forKey: kCIOutputImageKey) as? CIImage, let cgImageResult = self.context.createCGImage(outputImage, from: outputImage.extent) else  {
                DispatchQueue.main.async {
                     completion(nil)
                }
                group.leave()
                return
            }
    
            let filteredImage = UIImage(cgImage: cgImageResult)
            DispatchQueue.main.async {
                print (filteredImage)
                completion(filteredImage)
    
            }
    
           group.leave()
        }
    
        group.notify(queue: .main) {
            print ("Filteres are set")
        }
    }
    

    上面的打印语句和过滤后的图像地址很快就会打印出来,但是图像不会出现在视图中。

    我试过用Time Profiler但它给了我一些奇怪的结果。例如,它显示了在backtrace根目录中执行相当长的时间: enter image description here

    当我试图在Xcode中看到代码时,我得到了以下信息,这没有多大帮助: enter image description here

    所以,这就是问题所在。如果你对如何在照片应用程序中实现它有任何想法,它是如此快速和响应,或者如果你对我的实现有任何建议,我将非常感谢你的帮助。

    1 回复  |  直到 6 年前
        1
  •  4
  •   matt    6 年前

    enter image description here

    class EditingViewController: UIViewController, MTKViewDelegate {
        @IBOutlet weak var slider: UISlider!
        @IBOutlet weak var mtkview: MTKView!
    
        var context : CIContext!
        let displayImage : CIImage! // must be set before viewDidLoad
        let vig = VignetteFilter()
        var queue: MTLCommandQueue!
    
        // slider value changed
        @IBAction func doSlider(_ sender: Any?) {
            self.mtkview.setNeedsDisplay()
        }
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            // preparation, all pure boilerplate
    
            self.mtkview.isOpaque = false // otherwise background is black
            // must have a "device"
            guard let device = MTLCreateSystemDefaultDevice() else {
                return
            }
            self.mtkview.device = device
    
            // mode: draw on demand
            self.mtkview.isPaused = true
            self.mtkview.enableSetNeedsDisplay = true
    
            self.context = CIContext(mtlDevice: device)
            self.queue = device.makeCommandQueue()
    
            self.mtkview.delegate = self
            self.mtkview.setNeedsDisplay()
        }
    
        func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
        }
    
        func draw(in view: MTKView) {
            // run the displayImage thru the CIFilter
            self.vig.setValue(self.displayImage, forKey: "inputImage")
            let val = Double(self.slider.value)
            self.vig.setValue(val, forKey:"inputPercentage")
            var output = self.vig.outputImage!
    
            // okay, `output` is the CIImage we want to display
            // scale it down to aspect-fit inside the MTKView
            var r = view.bounds
            r.size = view.drawableSize
            r = AVMakeRect(aspectRatio: output.extent.size, insideRect: r)
            output = output.transformed(by: CGAffineTransform(
                scaleX: r.size.width/output.extent.size.width, 
                y: r.size.height/output.extent.size.height))
            let x = -r.origin.x
            let y = -r.origin.y
    
            // minimal dance required in order to draw: render, present, commit
            let buffer = self.queue.makeCommandBuffer()!
            self.context!.render(output,
                to: view.currentDrawable!.texture,
                commandBuffer: buffer,
                bounds: CGRect(origin:CGPoint(x:x, y:y), size:view.drawableSize),
                colorSpace: CGColorSpaceCreateDeviceRGB())
            buffer.present(view.currentDrawable!)
            buffer.commit()
        }
    }