我试图将OpenGL缓冲区(当前显示在视图中)保存到设备的照片库。下面的代码片段在模拟器上运行良好。但对于实际设备来说,它正在崩溃。我认为我创建从屏幕上捕获的UIImage的方式可能有问题。
- 此操作通过IBAction事件句柄方法发起。
- 我用来保存图像的函数是UIImageWriteToSavedPhotosAlbum(我最近把它改成了alassetlibrary的writeImageToSavedPhotosAlbum)。
- 我已经确保我的应用程序被授权访问照片库。
- 我还确保我的CGImageRed是全局定义的(定义在文件的顶部),我的UIImage是一个(非原子的,保留)属性。
谁能帮我解决这个问题?我想有一个有效的UIImage引用是从glReadPixels数据生成的。
下面是相关的代码片段(调用保存到照片库):-(void)TakeImageBufferSnapshot:(CGSize)dimensions
{
NSLog(@"TakeSnapShot 1 : (%f, %f)", dimensions.width, dimensions.height);
NSInteger size = dimensions.width * dimensions.height * 4;
GLubyte *buffer = (GLubyte *) malloc(size);
glReadPixels(0, 0, dimensions.width, dimensions.height, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
GLubyte *buffer2 = (GLubyte *) malloc(size);
int height = (int)dimensions.height - 1;
int width = (int)dimensions.width;
for(int y = 0; y < dimensions.height; y++)
{
for(int x = 0; x < dimensions.width * 4; x++)
{
buffer2[(height - 1 - y) * width * 4 + x] = buffer[y * 4 * width + x];
}
}
NSLog(@"TakeSnapShot 2");
// make data provider with data.
CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, buffer2, size, NULL);
if (buffer) free(buffer);
if (buffer2) free(buffer2);
// prep the ingredients
int bitsPerComponent = 8;
int bitsPerPixel = 32;
int bytesPerRow = 4 * self.view.bounds.size.width;
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault;
CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;
NSLog(@"TakeSnapShot 3");
// make the cgimage
g_savePhotoImageRef = CGImageCreate(dimensions.width, dimensions.height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent);
NSLog(@"TakeSnapShot 4");
// then make the uiimage from that
self.savePhotoImage = [UIImage imageWithCGImage:g_savePhotoImageRef];
CGColorSpaceRelease(colorSpaceRef);
CGDataProviderRelease(provider);
}
-(void)SaveToPhotoAlbum
{
ALAuthorizationStatus status = [ALAssetsLibrary authorizationStatus];
NSLog(@"Authorization status: %d", status);
if (status == ALAuthorizationStatusAuthorized)
{
[self TakeImageBufferSnapshot:self.view.bounds.size];
// UPDATED - DO NOT proceed to save to album below.
// Instead, set the created image to a UIImageView IBOutlet.
// On the simulator this shows the screen/buffer captured image (as expected) -
// but on the device (ipad) this doesnt show anything and the app crashes.
self.testImageView.image = self.savePhotoImage;
return;
NSLog(@"Saving to photo album...");
UIImageWriteToSavedPhotosAlbum(self.savePhotoImage,
self,
@selector(photoAlbumImageSave:didFinishSavingWithError:contextInfo:),
nil);
}
else
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Access is denied"
message:@"Allow access to your Photos library to save this image."
delegate:nil
cancelButtonTitle:@"Close"
otherButtonTitles:nil, nil];
[alert show];
}
}
- (void)photoAlbumImageSave:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)context
{
self.savePhotoImage = nil;
CGImageRelease(g_savePhotoImageRef);
if (error)
{
NSLog(@"Error saving photo to albums: %@", error.description);
}
else
{
NSLog(@"Saved to albums!");
}
}
* Update *我想我的问题已经缩小了。我开始做试验;错误,我在注释掉代码行后运行应用程序(在设备上),以缩小范围。它看起来像我可能有一个问题与TakeImageBufferSnapshot函数,它采取屏幕缓冲(使用glReadPixels),并创建一个CGImageRef。现在,当我尝试创建一个UIImage的这个(使用[UIImage imageWithCGImage:]方法,这似乎是为什么应用程序崩溃。如果我注释这一行,似乎没有问题(除了事实,我没有一个UIImage引用)。
我基本上需要一个有效的UIImage引用,以便我可以将其保存到照片库(使用测试图像似乎工作得很好)
首先,我应该指出glReadPixels()
的行为可能不像您期望的那样。如果在调用-presentRenderbuffer:
之后尝试使用它从屏幕上读取,结果是未定义的。例如,在iOS 6.0+中,这将返回一个黑色图像。你需要在内容呈现到屏幕之前使用glReadPixels()
(我的建议),或者为你的OpenGL ES上下文启用保留支持(这有不利的性能后果)。
第二,不需要两个缓冲区。你可以直接捕获到一个,并使用它来创建你的CGImageRef。
你的核心问题,问题是你正在释放你的原始图像字节缓冲区,而你的CGImageRef/UIImage仍然依赖于它。这会破坏你的UIImage,并导致你所看到的图像损坏/崩溃。为了解决这个问题,您需要设置一个回调函数,以便在CGDataProvider的释放时触发该函数。这是我在我的GPUImage框架中如何做到的:
rawImagePixels = (GLubyte *)malloc(totalBytesForImage);
glReadPixels(0, 0, (int)currentFBOSize.width, (int)currentFBOSize.height, GL_RGBA, GL_UNSIGNED_BYTE, rawImagePixels);
dataProvider = CGDataProviderCreateWithData(NULL, rawImagePixels, totalBytesForImage, dataProviderReleaseCallback);
cgImageFromBytes = CGImageCreate((int)currentFBOSize.width, (int)currentFBOSize.height, 8, 32, 4 * (int)currentFBOSize.width, defaultRGBColorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaLast, dataProvider, NULL, NO, kCGRenderingIntentDefault);
CGDataProviderRelease(dataProvider);
回调函数采用这种形式:
void dataProviderReleaseCallback (void *info, const void *data, size_t size)
{
free((void *)data);
}
这个函数只会在包含CGImageRef(以及CGDataProvider的扩展)的UIImage被释放时被调用。在此之前,包含图像字节的缓冲区将保留。
您可以查看我如何在GPUImage中这样做,作为一个功能示例。看看GPUImageFilter类,了解我如何从OpenGL ES帧中提取图像,包括使用纹理缓存而不是glReadPixels()
的更快方法。
根据我的经验,你不能直接抓取缓冲区中的像素你需要重建正确的上下文,在最终释放上下文
之前绘制并抓取THEN。=>这主要适用于设备,特别是ios6
EAGLContext* previousContext = [EAGLContext currentContext];
[EAGLContext setCurrentContext: self.context];
[self fillBuffer:sender];
//GRAB the pixels here
[EAGLContext setCurrentContext:previousContext];
或者(这就是我怎么做的)创建一个新的FrameBuffer,填充它并从那里抓取像素
GLuint rttFramebuffer;
glGenFramebuffers(1, &rttFramebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, rttFramebuffer);
[self fillBuffer:self.displayLink];
size_t size = viewportHeight * viewportWidth * 4;
GLubyte *pixels = malloc(size*sizeof(GLubyte));
glPixelStorei(GL_PACK_ALIGNMENT, 4);
glReadPixels(0, 0, viewportWidth, viewportHeight, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
// Restore the original framebuffer binding
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glDeleteFramebuffers(1, &rttFramebuffer);
size_t bitsPerComponent = 8;
size_t bitsPerPixel = 32;
size_t bytesPerRow = viewportWidth * bitsPerPixel / bitsPerComponent;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast;
CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, pixels, size, ImageProviderReleaseData);
CGImageRef cgImage = CGImageCreate(viewportWidth, viewportHeight, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpace, bitmapInfo, provider, NULL, true, kCGRenderingIntentDefault);
CGDataProviderRelease(provider);
UIImage *image = [UIImage imageWithCGImage:cgImage scale:self.contentScaleFactor orientation:UIImageOrientationDownMirrored];
CGImageRelease(cgImage);
CGColorSpaceRelease(colorSpace);
编辑:删除对presentBuffer的调用