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 <w * 4; x++) {
buffer2[(h -1 - y) * w * 4 + x] = buffer[y * 4 * w + x];
}
}
// make data provider with data.
CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, buffer2, myDataLength, NULL);
// prep the ingredients
int bitsPerComponent = 8;
int bitsPerPixel = 32;
int bytesPerRow = 4 * w;
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault;
CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;
// make the cgimage
CGImageRef imageRef = CGImageCreate(w, h, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent);
// then make the uiimage from that
UIImage *myImage = [UIImage imageWithCGImage:imageRef];
UIImageWriteToSavedPhotosAlbum(myImage, self, @selector(GLImage:didFinishSavingWithError:contextInfo:), buffer2);
CGImageRelease(imageRef);
CGDataProviderRelease(provider);
CGColorSpaceRelease(colorSpaceRef);
free(buffer);
}
这段代码是抓下了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代码有所不同。