Silence-GitHub / BBMetalImage

A high performance Swift library for GPU-accelerated image/video processing based on Metal.
MIT License
989 stars 126 forks source link

Best practice to add/remove consumers #49

Closed mnfro closed 4 years ago

mnfro commented 4 years ago

Hi, I've encountered issues while using your framework. I'm pretty sure I'm missing something.

Let's say that I have several LUT filters which can be selected by the user. I init both the metalView and the metalCamera in viewDidLoad():

    override func viewDidLoad() {
        super.viewDidLoad()

        metalView = BBMetalView(frame: aFrame)

        metalCamera = BBMetalCamera()
        metalCamera.canTakePhoto = true
        metalCamera.photoDelegate = self
        metalCamera.add(consumer: metalView)
    }

as written above, initially no filter is applied.

Now the user selects a filter from a "menu view" (which actually it is a collectionView):

    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {

        metalCamera.removeAllConsumers()

        switch indexPath.item {
        // case 0 corresponds to no filter applied = "Normal"
        case 0:
            bbMetalCamera.add(consumer: metalView)
            currentFilter = nil
        default:
            let selectedFilter = filters[indexPath.item]
            currentFilter = BBMetalLookupFilter(lookupTable: UIImage(named: selectedFilter.file!)!.bb_metalTexture!)
            metalCamera.add(consumer: currentFilter!).add(consumer: metalView)
        }
    }

Until now everything works pretty good, the LUT filter is finely rendered in the metalView. Notice that currentFilter is a variable that holds the current filter that has been selected by the user.

Next step is to take a photo and save it in the Photo Library:

     // Take the photo
    @IBAction func takePhoto(_ sender: Any) {
        metalCamera.takePhoto()
    }

    // Save the output
    func camera(_ camera: BBMetalCamera, didOutput texture: MTLTexture) {
        // In main thread
        let filteredImage = currentFilter?.filteredImage(with: texture.bb_image!) ?? texture.bb_image!

        let imageView = UIImageView(frame: metalView.frame)
        imageView.contentMode = .scaleAspectFill
        imageView.clipsToBounds = true

        imageView.image = filteredImage
        view.addSubview(imageView)

        PHPhotoLibrary.requestAuthorization { (status) in
            if status == .authorized {
                do {
                    try PHPhotoLibrary.shared().performChangesAndWait {
                        PHAssetChangeRequest.creationRequestForAsset(from: filteredImage)
                        print("[PHPhotoLibrary]: a photo has been saved")
                    }
                } catch let error {
                    print("[PHPhotoLibrary]: failed to save photo in library, ", error)
                }
            } else {
                print("[PHPhotoLibrary]: something went wrong with permission...")
            }
        }

        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
            imageView.removeFromSuperview()
        }
    }

aaand now something breaks. Indeed, while the photo is filtered correctly and saved with the LUT filter applied, the metalCamera/metalView freezes showing the last photo captured. The only thing to get things start working again is to select another filter from the collectionView.

So, I'm supposing that I'm doing something wrong, maybe I'm adding / removing consumers in a very bad way. May I ask you some help? :)

Silence-GitHub commented 4 years ago

@mnfro Hi.

In camera(_ camera: BBMetalCamera, didOutput texture: MTLTexture) function, if we use the old filter in the filter chain (from camera to metal view), the camera or metal view freezes. Because filteredImage(with:) function adds more image source to the old filter, changing the old filter chain. We should create a new filter in camera(_ camera: BBMetalCamera, didOutput texture: MTLTexture)

func camera(_ camera: BBMetalCamera, didOutput texture: MTLTexture) {
    let newFilter = ... // base on the collection view selection; same filter class as `currentFilter ` in the filter chain
    let filteredImage = newFilter.filteredImage(with: texture.bb_image!) ?? texture.bb_image!
    ...
}
mnfro commented 4 years ago

@Silence-GitHub thank you! Now everything works quite good. I'll close the issue.