Home > Software engineering >  Best approach to get snapshot from UIVIew
Best approach to get snapshot from UIVIew

Time:07-01

I'm making video from taking snapshots (30 times per sec), and I really need to find the best approach to get snapshot in the background thread with the best possible performance.

I have two approach in UIView extension

extension UIView {
 
    func snapshotInMain(view: UIView) -> UIImage {
        let render = UIGraphicsImageRenderer(size: view.bounds.size)
        let image = render.image { (context) in
            view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
        }
        return image
    }
    
    
    func snapShotInBackground(viewLayer: CALayer, viewBounds: CGRect) -> UIImage {
        let renderer = UIGraphicsImageRenderer(bounds: viewBounds)
        return renderer.image { rendererContext in
            viewLayer.render(in: rendererContext.cgContext)
        }
    }
}

The first one snapshotInMaincompletely execute in main thread, it froze the app but the screenshot itself is taken faster and I have a smoother video from it

the second one snapShotInBackground, the layer and bounds are calculated in the main thread, but then will execute in background, but it's way slower from the the one that execute in main (first one). it works like that

DispatchQueue.main.async {
   let layer = self.selectedView.layer
   let bounds = self.selectedView.bounds
   DispatchQueue.global(qos: .background).async {
       let image = self.selectedView.snapShotInBackground(viewLayer: layer, viewBounds: bounds)
     }
}

My job is really depand on it, I'm pretty stuck here and really need help. please help me to find the best option possible. My main requests are 1- App should not freeze 2- The taking snapshot should be fast that I can do it 30 times per sec.

Thank you! Looking forward to your help

CodePudding user response:

You can use a Timer to throttle the snapshots on the main thread. Here's an example

    @objc
    private func beginTapped() {
        print("Start time \(Date.now)")
        frameCount = 0
        let timer = Timer.scheduledTimer(timeInterval: 0.03, target: self, selector: #selector(takeSnapshot), userInfo: nil, repeats: true)
        
        DispatchQueue.main.asyncAfter(deadline: .now()   5, execute: {
            timer.invalidate()
            print("Finished at \(Date.now) with \(self.frameCount) frames captured")
        })
    }
    
    @objc
    private func takeSnapshot() {
        frameCount  = 1
        let render = UIGraphicsImageRenderer(size: selectedView.bounds.size)
        let _ = render.image { (context) in
            selectedView.drawHierarchy(in: selectedView.bounds, afterScreenUpdates: true)
        }
    }

which results in

Start time 2022-07-01 04:20:01  0000
Finished at 2022-07-01 04:20:07  0000 with 180 frames captured

Decreasing the timeInterval to 0.01 yields

Start time 2022-07-01 04:25:30  0000
Finished at 2022-07-01 04:25:36  0000 with 506 frames captured

CPU usage peaked around 20% during this time and the app did not lag at all in my trivial example.

CodePudding user response:

Rather than taking the snapshot I thing use generator is a good option here it takes cpu but not memory which cause smoother ui/ux

class VideoController:UIViewController {
    
    var timer = Timer()
    var secs:Double = 0
    
    var urls = [URL?]() {
        didSet {
            print(urls.count)
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        everSecond()
    }
    
    func everSecond() {
        // every second trigger
        timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(generate), userInfo: nil, repeats: true)
    }
    
    
    @objc
    func generate() {
        let url = Bundle.main.url(forResource: "Collection", withExtension: "mov")
        let videoAsset = AVAsset(url: url!)
        let duration = videoAsset.duration.seconds // duration of video
        if secs < duration {
            var timesArray:[NSValue] = []
            for i in 1...30 {
                let value:Double = Double(31 - i) // invers
                let t = CMTime(value: CMTimeValue(1 / value)   Int64(secs), timescale: 1) // getting time
                timesArray.append(NSValue(time: t)) // appending array
            }
            let generator = AVAssetImageGenerator(asset: videoAsset)
            generator.requestedTimeToleranceBefore = .zero
            //Optional generator.requestedTimeToleranceAfter = .zero //Optional
            generator.generateCGImagesAsynchronously(forTimes: timesArray) { requestedTime, image, actualTime, result, error in
                    if let img = image {
                        let m = UIImage(cgImage: img) // uiimage
                        let url = ViewController.saveImageInDocumentDirectory(image: m, fileName: "\(Date().timeStamp()).jpeg") //saved in documents with unique name
                        self.urls.append(url) // append url to array urls for save refrecnce
                    }
                
            }
            secs  = 1
        } else {
            timer.invalidate()
        }
    }
    
    
    public static func saveImageInDocumentDirectory(image: UIImage, fileName: String) -> URL? {

            let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!;
            let fileURL = documentsUrl.appendingPathComponent(fileName)
            if let imageData = image.pngData() {
                try? imageData.write(to: fileURL, options: .atomic)
                return fileURL
            }
            return nil
        }

    public static func loadImageFromDocumentDirectory(fileName: String) -> UIImage? {

            let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!;
            let fileURL = documentsUrl.appendingPathComponent(fileName)
            do {
                let imageData = try Data(contentsOf: fileURL)
                return UIImage(data: imageData)
            } catch {}
            return nil
        }
}
extension Date {
    
    func timeStamp() -> String {
        let fomatter = DateFormatter()
        fomatter.dateFormat = "yyyy-MM-dd HH:mm:ss:SSS"
        return fomatter.string(from: self)
    }
}

    
  • Related