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代碼有所不同。