Performance Issues due to Repetition

Hello, I have set up 16 CIFilter effects and for each effect I have created methods with identical functionality and due to the repetition I've been going through performance issues. The build time takes around 10-13 seconds. I have created a method called "applyFilterTo" to integrate the CIFilters under this method but then the filters started not to work anymore. Could you please give me some insight about how I can increase the performance of the app ?


func effectsSliderSepiaTone() {
 
    sliderControl.addTarget(self, action: #selector(sliderSepiaToneDidChange(_:image:_:)), for: .allTouchEvents)
    sliderControl.translatesAutoresizingMaskIntoConstraints = false
    sliderControl.minimumValue = 0
    sliderControl.maximumValue = 1
    sliderControl.isContinuous = true
    sliderControl.tintColor = UIColor.blue
    view.addSubview(sliderControl)
    NSLayoutConstraint.activate([
      sliderControl.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 30),
      sliderControl.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -30),
      sliderControl.bottomAnchor.constraint(equalTo: resetConfirmCollectionView.topAnchor, constant: -50)])
  }

  func imageSepiaTone(imgView: UIImageView, sliderValue: CGFloat, image: UIImage){
  
    let aCGImage = image.cgImage
    let aCIImage = CIImage(cgImage: aCGImage!)
  
    let sepiaToneFilter = CIFilter(name: "CISepiaTone")
    sepiaToneFilter!.setDefaults()
    sepiaToneFilter?.setValue(aCIImage, forKey: kCIInputImageKey)
  
    sepiaToneFilter?.setValue(sliderValue - 0.5, forKey: kCIInputIntensityKey)

    let outputImage = sepiaToneFilter?.outputImage!
    let cgimg = context.createCGImage(outputImage!, from: outputImage!.extent)
    let newUIImage = UIImage(cgImage: cgimg!, scale: image.scale, orientation: image.imageOrientation)
    imgView.image = newUIImage
  
  }

@objc private func sliderSepiaToneDidChange(_ sender: UISlider!, image: UIImage,_ event: UIEvent) {
     
      if let touchEvent = event.allTouches?.first {
        switch touchEvent.phase {
        case .moved:

          labelControl.text = String(displayInPercentage)
         
          if updatedImage == nil {
            photoImageView.image = pickedImage
            imageSepiaTone(imgView: photoImageView, sliderValue: CGFloat(sender.value), image: pickedImage!)
          } else {
            photoImageView.image = updatedImage
            imageSepiaTone(imgView: photoImageView, sliderValue: CGFloat(sender.value), image: updatedImage!)
          }
         
        case .ended:
         
          updatedImage = photoImageView.image
            UserDefaults.standard.setValue(sliderControl.value, forKey: "slider_sepiatone")
         
        default:
          break
        }
      }
     
      labelControl.textAlignment = .center
      labelControl.numberOfLines = 0
      labelControl.textColor = UIColor.gray
      labelControl.font = UIFont.systemFont(ofSize: 16)
      labelControl.translatesAutoresizingMaskIntoConstraints = false
      view.addSubview(labelControl)
      NSLayoutConstraint.activate([labelControl.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 30),
                                   labelControl.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -30),
                                   labelControl.bottomAnchor.constraint(equalTo: resetConfirmCollectionView.topAnchor, constant: -80)])
    }


class Effects {
 
  var context: CIContext! = CIContext(options: [.workingColorSpace: NSNull(), .outputColorSpace: NSNull()])
  var currentFilter: CIFilter!
 
  struct Filter {
    let filterName: String
    var filterEffectValue: Any?
    var filterEffectValueName: String?
   
    init(filterName: String, filterEffectValue: Any?, filterEffectValueName: String?) {
      self.filterName = filterName
      self.filterEffectValue = filterEffectValue
      self.filterEffectValueName = filterEffectValueName
    }

func applyFilterTo(image: UIImage, filterEffect: Filter, sliderValue: CGFloat?) -> UIImage? {
  
    guard let cgImage = image.cgImage else { return nil }
    //    let context = CIContext(options: nil)
  
    let ciImage = CIImage(cgImage: cgImage)
    currentFilter = CIFilter(name: filterEffect.filterName)
  
    currentFilter!.setDefaults()
    if currentFilter == CIFilter(name: "CIUnsharpMask") {
    
      currentFilter?.setValue(ciImage, forKey: kCIInputImageKey)
      currentFilter?.setValue(sliderValue! - 0.5, forKey: kCIInputIntensityKey)
    }
    else if currentFilter == CIFilter(name: "CIGaussianBlur") {
//      currentFilter!.setDefaults()
      currentFilter?.setValue(ciImage, forKey: kCIInputImageKey)
      currentFilter?.setValue(sliderValue! - 0.5, forKey: kCIInputRadiusKey)
    }
    else if currentFilter == CIFilter(name: "CICMYKHalftone") {
//      currentFilter!.setDefaults()
      currentFilter?.setValue(ciImage, forKey: kCIInputImageKey)
      currentFilter?.setValue(sliderValue! - 0.5, forKey: kCIInputWidthKey)
      }
    else {
      currentFilter?.setValue(ciImage, forKey: kCIInputImageKey)
      if let filterEffectValue = filterEffect.filterEffectValue, let filterEffectValueName = filterEffect.filterEffectValueName {
        currentFilter?.setValue(filterEffectValue, forKey: filterEffectValueName)
      }
    }
  
    var filteredImage: UIImage?
  
    if let output = currentFilter?.value(forKey: kCIOutputImageKey) as? CIImage,
      let cgiImageResult = context.createCGImage(output, from: output.extent) {
      filteredImage = UIImage(cgImage: cgiImageResult, scale: image.scale, orientation: image.imageOrientation)
    }
    return filteredImage
  }
}

Replies

You do not show how you call applyFilterTo


Could you test filteredImage:


   var filteredImage: UIImage?

    if let output = currentFilter?.value(forKey: kCIOutputImageKey) as? CIImage,
      let cgiImageResult = context.createCGImage(output, from: output.extent) {
      filteredImage = UIImage(cgImage: cgiImageResult, scale: image.scale, orientation: image.imageOrientation)
    }
    print("filteredImage", filteredImage)

    return filteredImage

Hi Claude, thank you for your response. Regarding the call of "applyFilterTo".


@objcprivatefunc sliderNoiseDidChange(_ sender: UISlider!, image: UIImage,_ event: UIEvent) {
   
    if let touchEvent = event.allTouches?.first {
      switch touchEvent.phase {
      case .moved:

        labelControl.text = String(displayInPercentage)
       
        if updatedImage == nil {
         
          updatedImage = effects.applyFilterTo(image: pickedImage!, filterEffect: Effects.Filter(filterName: "CIUnsharpMask", filterEffectValue: nil, filterEffectValueName: nil), sliderValue: CGFloat(sender.value))

        } else {
          updatedImage = effects.applyFilterTo(image: updatedImage!, filterEffect: Effects.Filter(filterName: "CIUnsharpMask", filterEffectValue: nil, filterEffectValueName: nil), sliderValue: CGFloat(sender.value))
        }
       
      case .ended:
        if segmentedControl.selectedSegmentIndex == 3 {

          updatedImage = photoImageView.image
          UserDefaults.standard.setValue(sliderControl.value, forKey: "slider_noise")
        }
      default:
        break
      }
    }
   
    labelControl.textAlignment = .center
    labelControl.numberOfLines = 0
    labelControl.textColor = UIColor.gray
    labelControl.font = UIFont.systemFont(ofSize: 16)
    labelControl.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(labelControl)
    NSLayoutConstraint.activate([labelControl.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 30),
                                 labelControl.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -30),
                                 labelControl.bottomAnchor.constraint(equalTo: resetConfirmCollectionView.topAnchor, constant: -80)])
  }

You may need to modify some other parts of your code, but comparisons in your if-statement has no meaning.

    if currentFilter == CIFilter(name: "CIUnsharpMask") { 

The right hand side of `==` generates newly created instance of `CIFilter`, which can never the same as existing `currentFilter`.

So, the result is always false. You can confirm that with putting the following code in the Playground.

        let currentFilter = CIFilter(name: "CIUnsharpMask")
        if currentFilter == CIFilter(name: "CIUnsharpMask") {
            print("currentFilter is CIUnsharpMask")
        } else {
            print("currentFilter is NOT CIUnsharpMask") //<- This `print` runs.
        }


You may need to update the comparisons in your `applyFilterTo(image:filterEffect:sliderValue:)`.

        currentFilter!.setDefaults()
        if filterEffect.filterName == "CIUnsharpMask" {
            currentFilter?.setValue(ciImage, forKey: kCIInputImageKey)
            currentFilter?.setValue(sliderValue! - 0.5, forKey: kCIInputIntensityKey)
        }
        else if filterEffect.filterName == "CIGaussianBlur" {
            currentFilter?.setValue(ciImage, forKey: kCIInputImageKey)
            currentFilter?.setValue(sliderValue! - 0.5, forKey: kCIInputRadiusKey)
        }
        else if filterEffect.filterName == "CICMYKHalftone" {
            currentFilter?.setValue(ciImage, forKey: kCIInputImageKey)
            currentFilter?.setValue(sliderValue! - 0.5, forKey: kCIInputWidthKey)
        }
        else {
            currentFilter?.setValue(ciImage, forKey: kCIInputImageKey)
            if let filterEffectValue = filterEffect.filterEffectValue, let filterEffectValueName = filterEffect.filterEffectValueName {
                currentFilter?.setValue(filterEffectValue, forKey: filterEffectValueName)
            }
        }

I have not checked other parts of your code (I cannot because you are not showing enough), but at lease you need the fix above.

Thank you very much, have a great weekend !