1

OpenGL バッファ (現在ビューに表示されているもの) をデバイスのフォト ライブラリに保存しようとしています。以下のコード スニペットは、シミュレーターで正常に動作します。しかし、実際のデバイスではクラッシュしています。画面からキャプチャされた UIImage を作成する方法に問題がある可能性があると思います。

  • この操作は、IBAction イベント ハンドル メソッドを介して開始されます。
  • 画像を保存するために使用する関数は UIImageWriteToSavedPhotosAlbum です (最近、これを ALAssetsLibrary の writeImageToSavedPhotosAlbum に変更しました)。
  • アプリが写真ライブラリへのアクセスを承認されていることを確認しました。
  • また、CGImageRed がグローバルに定義されている (ファイルの先頭で定義されている) ことと、UIImage が (非アトミック、保持) プロパティであることも確認しました。

誰かがこの問題を解決するのを手伝ってくれますか? glReadPixels データから生成された有効な UIImage 参照が必要です。

以下は、関連するコード スニペットです (フォト ライブラリに保存するための呼び出し)。

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

* 更新 * 問題を絞り込むことができたと思います。コード行をコメントアウトした後、(デバイス上で)アプリを実行して、物事を絞り込む試行錯誤を始めました。(glReadPixels を使用して) 画面バッファーを取得し、CGImageRef を作成するTakeImageBufferSnapshot関数に問題があるようです。ここで、これから UIImage を作成しようとすると ([UIImage imageWithCGImage:] メソッドを使用して)、これが原因でアプリがクラッシュするようです。この行をコメントアウトすると、問題はないようです (という事実以外は)私はUIImage参照を持っていません)。

写真ライブラリに保存できるように、基本的に有効な UIImage 参照が必要です (テスト画像を使用しても問題なく動作するようです)。

4

2 に答える 2

2

glReadPixels()まず、期待どおりに動作しない可能性があることを指摘しておきます。が呼び出された後に画面から読み取るために使用しようとすると-presentRenderbuffer:、結果は未定義です。たとえば、iOS 6.0 以降では、これは黒い画像を返します。glReadPixels()コンテンツが画面に表示される直前に使用するか(私の推奨事項)、OpenGL ES コンテキストの保持されたバッキングを有効にする必要があります (パフォーマンスに悪影響を及ぼします)。

次に、2 つのバッファーは必要ありません。1 つに直接キャプチャし、それを使用して 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 内でこれを行う方法を調べることができます。OpenGL ES フレームから画像を抽出する方法については GPUImageFilter クラスを参照してくださいglReadPixels()

于 2013-05-18T15:37:08.343 に答える