从Uiview获取快照的最佳方法

发布于 2025-02-12 13:26:02 字数 1282 浏览 0 评论 0原文

我正在通过拍摄快照(每秒30次)制作视频,我真的需要找到最好的方法,以最佳的性能在后台线程中获得快照。

我有两种方法在Uiview扩展程序中,

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)
        }
    }
}

第一个snapshotinmain在主线程中完全执行,它冻结了应用程序,但是屏幕截图本身更快地拍摄了,我从中获得了一个更平稳的视频

snapshotinbackground,该图层和边界是在主线程中计算的,但随后将在后台执行,但比在MAIN中执行的层较慢(首先 一)。 它的工作就像

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)
     }
}

我的工作确实对此非常不满意,我很呆在这里,真的需要帮助。请帮助我找到最好的选择。我的主要要求是 1-应用不应冻结 2-拍摄快照应该很快,我可以每秒钟30次。

谢谢你!期待您的帮助

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

痴情 2025-02-19 13:26:02

您可以使用计时器在主线程上节拍快照。这是一个示例

    @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)
        }
    }

,该示例

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

TimeInterval降低到0.01

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

在此期间的CPU使用率约为20%左右,并且该应用程序在我的琐碎示例中根本没有落后。

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.

开始看清了 2025-02-19 13:26:02

而不是采用快照我使用发电机是一个不错的选择,而是需要CPU,而不是导致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)
    }
}

    

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)
    }
}

    
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文