【问题标题】:Displaying a screen shot generated UIImage is not displaying in UIImageView (for device only)显示屏幕截图生成的 UIImage 不在 UIImageView 中显示(仅适用于设备)
【发布时间】:2013-05-16 09:57:07
【问题描述】:

我正在尝试将 OpenGL 缓冲区(视图中当前显示的内容)保存到设备的照片库中。下面的代码 sn-p 在模拟器上运行良好。但对于实际设备,它正在崩溃。我相信我创建从屏幕捕获的 UIImage 的方式可能存在问题。

  • 此操作通过 IBAction 事件句柄方法启动。
  • 我用来保存图像的函数是 UIImageWriteToSavedPhotosAlbum(我最近将其更改为 ALAssetsLibrary 的 writeImageToSavedPhotosAlbum)。
  • 我已确保我的应用有权访问照片库。
  • 我还确保我的 CGImageRed 是全局定义的(在文件顶部定义)并且我的 UIImage 是一个(非原子,保留)属性。

有人可以帮我解决这个问题吗?我想要一个从 glReadPixels 数据生成的有效 UIImage 引用。

下面是相关代码sn-p(调用保存到照片库):

-(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!");
   }
}

* 更新 * 我想我已经设法缩小我的问题范围。我开始进行反复试验,在注释掉代码行后我运行应用程序(在设备上),以缩小范围。看起来我可能对 TakeImageBufferSnapshot 函数有问题,该函数获取屏幕缓冲区(使用 glReadPixels)并创建一个 CGImageRef。现在,当我尝试从中创建 UIImage 时(使用 [UIImage imageWithCGImage:] 方法,这似乎就是应用程序崩溃的原因。如果我注释掉这一行,似乎没有问题(除了事实上我没有 UIImage 参考)。

我基本上需要一个有效的 UIImage 参考,以便我可以将其保存到照片库(使用测试图像似乎可以正常工作)。

【问题讨论】:

  • 发布代码可能比内存警告窗口更有帮助。
  • 你是在主线程上做这个吗?
  • 尝试全局断点:google.co.in/…
  • 请启用僵尸并告诉我说了什么
  • @lducool。我没有对线程做任何明确的事情。我有一个 IBAction,我从那里调用方法。所以我猜一切都在主线程上。对吗?

标签: iphone uiimage opengl-es-2.0 glreadpixels cgimageref


【解决方案1】:

首先,我应该指出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() 的更快方法。

【讨论】:

  • 你怎么知道我在 presentRenderbuffer 之前或之后捕获缓冲区字节?我没有在任何地方直接调用 presentRenderbuffer。
  • 另外,我不明白你在做什么(除了 dataProviderReleaseCallback)。在我的代码中,直到保存到库完成后,我才释放 UIImage 或 CGImageRef(它永远不会被调用,因为函数在尝试保存之前返回)。
  • @AlvinfromDiaspar - 关于 glReadPixels() 的注释是一个警告,因为我看到这里有很多人被它绊倒了。我在上面所说的是,您在 UIImage 或 CGImageRef 被释放之前就在上面的代码中释放了 bufferbuffer2CGDataProviderCreateWithData() 不会在内部复制这些字节,因此当您释放该缓冲区并尝试从基于它的 CGImageRef 创建 UIImage 时,您将看到垃圾结果或崩溃。
  • @AlvinfromDiaspar - 要验证这一点,您可以在上面的代码中注释掉 if (buffer2) free(buffer2); 行。我打赌您的应用程序将不再因创建图像而崩溃。但是,它现在会有内存泄漏,这是我上面代码中的回调函数负责处理的。我遇到了与您描述的相同行为的完全相同的事情,这就是我解决它的方法。
  • 是的,我能够通过崩溃。我已将此问题更新为另一个问题(与我读为黑色图像的图像有关)。刚才我通过使用 GLKView 的内置快照功能解决了这个问题,该功能目前适用于模拟器和设备。
【解决方案2】:

嗯 - 根据我的经验,您现在不能只抓取缓冲区中的像素 你需要重新建立正确的上下文,在最终释放上下文之前绘制并抓取 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 的调用

【讨论】:

  • 请注意,在-presentRenderbuffer: 之后使用glReadPixels(),正如您在上面第一个代码段中所描述的那样,在 iOS 6.0+ 上会失败,并且在早期的 iO​​S 版本上不受支持。
  • 是的,它确实在某处失败了。我写了第一个内联。问题是你必须做我写的事情,而不是尝试读取当前“onceen”的当前缓冲区上下文
  • 我想重申一件事 - 如果我将 TakeImageBufferSnapshot 创建的 UIImage 应用到 UIImageView,我可以看到从模拟器运行时显示的图像。但是当我从我的 iPad 设备运行时,图像不会显示在图像视图中。
猜你喜欢
  • 1970-01-01
  • 2011-09-20
  • 1970-01-01
  • 2023-03-05
  • 2020-06-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多