iPhone自從推出後就自帶了截屏功能,簡單而易用,所以應用就沒什麼截屏的需求了,不過有些時候我們還是會遇到這個需求。比如,我們開發了一個播放器,用openGL進行video render,此時直接截屏有可能有OSD疊加內容,所以希望能截完全是視頻的幀,這時就需要應用自己來實現了。
從應用角度看,雖說都是截屏,但用不用openGL是不同的,因為openGL是直接寫GPU frame buffer的,如果我們是直接用UIController來用做的界面:
- (void)snapshotScreen { // check the retina screen if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]){ UIGraphicsBeginImageContextWithOptions(self.view.window.bounds.size, NO, [UIScreen mainScreen].scale); } else { UIGraphicsBeginImageContext(self.view.window.bounds.size); } [self.view.window.layer renderInContext:UIGraphicsGetCurrentContext()]; UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); NSData * data = UIImagePNGRepresentation(image); NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *filename = [[path objectAtIndex:0] stringByAppendingPathComponent:@"foo.png"]; [data writeToFile:filename atomically:YES]; }
如果要截openGL的內容,那麼上面的代碼就不能用了,思路是用openGL的API,glReadPixels去讀出內容來。代碼如下:
- (void)getGLScreenShot { int w = self.bounds.size.width; int h = self.bounds.size.height; NSInteger myDataLength = w * h * 4; // allocate array and read pixels into it. GLubyte *buffer = (GLubyte *) malloc(myDataLength); glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, buffer); // gl renders "upside down" so swap top to bottom into new array. // there's gotta be a better way, but this works. GLubyte *buffer2 = (GLubyte *) malloc(myDataLength); for(int y = 0; y < h; y++) { for(int x = 0; x這段代碼是抓下了openGL渲染的一個GLView的所有內容,因為openGL讀出的內容是顛倒的,所以習慣上需要顛倒一下,中間的兩層for循環就是這個用途,當然,這個寫法效率不高,不過一時也沒找到什麼高效的方法,就先用一下。 這段代碼裡面釋放了buffer的內存,但沒有釋放buffer2的內存。因為圖片的存儲是異步的,所以是在存完圖片之後,調用@selector(GLImage:didFinishSavingWithError:contextInfo:)這個方法進行清理,通過這個回調,在UI上也可以做一些限制,防止用戶連續快速的截屏導致系統負載過重。
順便說一下,這裡把存下的圖片按照習慣放到了圖片庫之中。
在iOS7之後,UIView有了UISnapshotting的category,這個真是大大的方便了我們截屏的實現,因為它既可以截屏普通的UIController,也可以截屏openGL的內容,
// only support iOS7 or Above - (void)snapshotScreenWithGL { CGSize size = videoView.bounds.size; UIGraphicsBeginImageContextWithOptions(size, NO, [UIScreen mainScreen].scale); CGRect rec = CGRectMake(videoView.frame.origin.x, videoView.frame.origin.y, videoView.bounds.size.width, videoView.bounds.size.height); [self.view drawViewHierarchyInRect:rec afterScreenUpdates:YES]; UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); NSData * data = UIImagePNGRepresentation(image); NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *filename = [[path objectAtIndex:0] stringByAppendingPathComponent:@"foo.png"]; [data writeToFile:filename atomically:YES]; }綜合起來看,iOS7之後,蘋果考慮到了用戶這方面的需求,提供了API,可以比較方便的實現功能。iOS7之前,需要自己手動實現,根據是否使用了openGL代碼有所不同。