1. Qiita
  2. 投稿
  3. Objective-C

UIGraphicsBeginImageContextの無駄

  • 98
    いいね
  • 3
    コメント
この記事は最終更新日から1年以上が経過しています。

よく、以下のようなコードでオフスクリーンコンテキストを作成し、
描画の後画像を得るサンプルを見ます。
コードが単純なためか、非常に広く使われていると思います。

    UIGraphicsBeginImageContextWithOptions(CGSizeMake(1024, 1024), NO, 1.0);
    CGContextRef context = UIGraphicsGetCurrentContext();

    CGContextFillEllipseInRect(context, CGRectMake(0, 0, 1024, 1024));

    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

ですが、これは本来やらなくていい処理をしていると思いませんか?
というのも、
描画のためのコンテキストを一度作成し、UIGraphicsGetImageFromCurrentImageContextでわざわざコピーしてきているのです。
メモリを用意して、そこに描画して、それをそのまま画像にすれば、
この無駄なコピーは本来発生しないはずなんです。
その点を考慮に入れると、本来最小限のコードは例えば以下のようになるかと思います。

補助関数

static void bufferFree(void *info, const void *data, size_t size)
{
    free((void *)data);
}
static size_t align16(size_t size)
{
    if(size == 0)
        return 0;

    return (((size - 1) >> 4) << 4) + 16;
}

メイン処理

    size_t width = 1000;
    size_t height = 1000;
    size_t bitsPerComponent = 8;
    size_t bytesPerRow = align16(4 * width);
    size_t bufferSize = bytesPerRow * height;
    uint8_t *bytes = malloc(bufferSize);
    CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrderDefault;

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

    CGContextRef context = CGBitmapContextCreate(bytes, width, height, bitsPerComponent, bytesPerRow, colorSpace, bitmapInfo);
    CGContextClearRect(context, CGRectMake(0, 0, width, height));
    CGContextFillEllipseInRect(context, CGRectMake(0, 0, width, height));
    CGContextRelease(context);
    context = NULL;

    CGDataProviderRef dataProvider = CGDataProviderCreateWithData(NULL, bytes, bufferSize, bufferFree);
    size_t bitsPerPixel = 32;
    CGImageRef image = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpace, bitmapInfo, dataProvider, NULL, NO, kCGRenderingIntentDefault);
    CGDataProviderRelease(dataProvider);
    dataProvider = NULL;
    CGColorSpaceRelease(colorSpace);
    colorSpace = NULL;

    CGImageRelease(image);

ポイントはCGBitmapContextCreateに自分で用意したメモリを使ってもらう点です。
描画が終われば、即座にコンテキストは捨ててしまい、
ラスターデータだけが残るので、そのままCGImageのバッファとして、CGDataProviderCreateWithDataを通して使用します。
bytesPerRowを16バイトアラインメントが推奨されているので、対応してあげる事、
など少し気を配っておいた方がいい点もあります。

というわけで、これにより無駄なコピーを避ける事ができます。
若干コードが多いため、
使い回すなら、関数やクラスに加工して使うと良いでしょう。

画像の扱いは負荷が高いため、
気づくとCPU負荷があがりすぎていたりすると思いますので、気になる場合は試してみてはいかがでしょうか。