carbon graphics


HOME > TIPS > Cocoa Programming Tips 1001 > With Carbon > carbon graphics

Carbon Graphics

With Carbon - Carbon Graphics

GWorldをNSImageに変換する

Keywords: CGContextDrawImage()

(この記事は、[macosx-dev-jp:04092] Re: ARGBのNSBitmapImageRep作成方法と、CocoaDev: NSImageFromAMovieFrameを参考にさせてもらいました。)

Carbon では、GWorld と呼ばれるオフスクリーングラフィックスが使われるんだ。GWorld は、PixMap という構造体に、画像の生データを持っている。この GWorld を NSImage に変換してみよう。

単純に考えれば、GWorld から生データを取り出して、NSBitmapImageRep を作ればいいじゃん、って思うけど、どうもそう簡単にはいかないらしい。というのは、GWorld のデータのフォーマットが ARGB なのに対して、NSBitmapImageRep は RGBA だからだ。これを変換しなくてはいけない。めんどくせー。

手で変換するのもあれなので、CoreGraphics を使ってみよう。処理の流れは、こうなるらしい。

・GWorld のデータから、CGImageCreate() を使って、alphaInfo に kCGImageAlphaFirst か kCGImageAlphaPremultipliedFirst を指定して、source 画像を作る。

・NSBitmapimageRep の bitmapData から、CGBitmapContextCreate() を使って、alphaInfo に kCGImageAlphaLast か kCGImageAlphaPremultipliedFirst を指定して、destination コンテキストを作る。

・CGContextDrawImage() を使って、source 画像を destination コンテキストに描きだす。

これでオッケーらしい。

では、ソースコードを。GWorld を NSImage に変換する関数だ。

imageFromGworld()

NSImage* imageFromGworld(GWorldPtr gworld)
{
 // PixMapHandleを取得します
 PixMapHandle pixMapH;
 pixMapH = GetGWorldPixMap(gworld);

 // PixMapHandleをロックします
 if (!LockPixels(pixMapH)) {
  return nil;
 }

 // PixMapHandleの情報を取得します
 Rect portRect;
 int pixelsWide;
 int pixelsHigh;
 void* baseAddr;
 long rowBytes;
 GetPortBounds(gworld, &portRect);
 pixelsWide = portRect.right - portRect.left;
 pixelsHigh = portRect.bottom - portRect.top;
 baseAddr = GetPixBaseAddr(pixMapH);
 rowBytes = GetPixRowBytes(pixMapH);

 // Source画像を作成します
 CGColorSpaceRef srcColorSpaceRef;
 CGDataProviderRef dataProviderRef;
 CGImageRef srcImageRef;
 srcColorSpaceRef = CGColorSpaceCreateDeviceRGB();
 dataProviderRef = CGDataProviderCreateWithData(
      NULL, baseAddr, rowBytes * pixelsHigh, NULL);
 srcImageRef = CGImageCreate(
      pixelsWide,
      pixelsHigh,
      8,
      32,
      rowBytes,
      srcColorSpaceRef,
      kCGImageAlphaPremultipliedFirst,
      dataProviderRef,
      NULL,
      NO,
      kCGRenderingIntentDefault);

 // 空のNSBitmapImageのインスタンスを作成します
 NSBitmapImageRep* bitmapImageRep;
 bitmapImageRep = [[[NSBitmapImageRep alloc]
      initWithBitmapDataPlanes:NULL
      pixelsWide:pixelsWide
      pixelsHigh:pixelsHigh
      bitsPerSample:8
      samplesPerPixel:4
      hasAlpha:YES
      isPlanar:NO
      colorSpaceName:NSDeviceRGBColorSpace
      bytesPerRow:NULL
      bitsPerPixel:NULL] autorelease];

 // Destinationのコンテキストを作成します
 CGColorSpaceRef dstColorSpaceRef;
 CGContextRef dstContextRef;
 dstColorSpaceRef = CGColorSpaceCreateDeviceRGB();
 dstContextRef = CGBitmapContextCreate(
      [bitmapImageRep bitmapData],
      pixelsWide,
      pixelsHigh,
      8,
      [bitmapImageRep bytesPerRow],
      dstColorSpaceRef,
      kCGImageAlphaPremultipliedLast);

 // Source画像をdestinationコンテキストに描きます
 CGContextDrawImage(
      dstContextRef,
      CGRectMake(0, 0, pixelsWide, pixelsHigh),
      srcImageRef);

 // 各種データを解放します
 CGColorSpaceRelease(srcColorSpaceRef);
 CGDataProviderRelease(dataProviderRef);
 CGImageRelease(srcImageRef);
 CGColorSpaceRelease(dstColorSpaceRef);
 CGContextRelease(dstContextRef);

 // PixMapHandleをアンロックします
 UnlockPixels(pixMapH);

 // NSImageのインスタンスを作成します
 NSImage* image;
 image = [[NSImage alloc]
    initWithSize:NSMakeSize(pixelsWide, pixelsHigh)];
 [image addRepresentation:bitmapImageRep];
 [image autorelease];

 return image;
}

けっこう長くてめんどくさいでしょ。でもやりたいことは CGContextDrawImage() を呼び出すことで、ほとんどはその準備だ。このソースコードを使った例は、次のスクリーンショットを撮るで紹介しよう。



With Carbon - Carbon Event

スクリーンショットを撮る

Keywords: GetQDGlobalsScreenBits()

(この記事は、keta さんの掲示板での書き込みを参考にさせてもらいました。ありがとうございました。)

現在のスクリーンショットを撮る方法を考えてみよう。

まず、現在のスクリーンの画像データにアクセスルにはどうすればいいか?これは簡単で、 GetQDGlobalsScreenBits() を呼び出せばいいらしい。でも、こいつを使うと古い BitMap 形式で取得される。そこで、CopyBits() を使って GWorld に書き出してやる。GWorld に書き出してしまえば、さっきの GWorldをNSImageに変換する を使えば NSImage として Cocoa で使えるし、Carbon ならそのまま使うことができる。

GWorld に CopyBits() するコードは、次のようになるんだ。

ScreenShotCocoa/main.m

// ScreenBits(ディスプレイ)を取得します
BitMap bitmap;
Rect bounds;
GetQDGlobalsScreenBits(&bitmap);
bounds = bitmap.bounds;

// 32Bitsの色深度でGWorldを作成します
GWorldPtr gworld;
OSErr result;
result = NewGWorld(&gworld, 32, &bounds, 0, 0, 0);
if (result != noErr) {
 return 1;
}

// ScreenBitsをGWorldにコピーします
GWorldPtr saveGWorld;
GDHandle saveGDH;
GetGWorld(&saveGWorld, &saveGDH);
SetGWorld(gworld, NULL);
EraseRect(&bounds);

LockPortBits(gworld);
CopyBits (
    &bitmap,
    GetPortBitMapForCopyBits(gworld),
    &bounds,
    &bounds,
    srcCopy,
    NULL);
UnlockPortBits(gworld);

SetGWorld(saveGWorld, saveGDH);

Cocoa なら、この GWorld を NSImage に変換するなりしてやればいい。

また、Carbon なら、QuickTime を使ってもっと手っ取り早く、こうできる。

ScreenShotCarbon/main.m

// ScreenBits(ディスプレイ)を取得します
PixMap pixMap;
GetQDGlobalsScreenBits((BitMap*)&pixMap);

// PNGで書き出します
GraphicsExportComponent exporter;
PixMapPtr pixMapP;
FSSpec spec;

pixMapP = &pixMap;
FSMakeFSSpec(0, 0, "\pScreenShot.PNG", &spec);

OpenADefaultComponent(
    GraphicsExporterComponentType,
    kQTFileTypePNG,
    &exporter);
GraphicsExportSetInputPixmap(exporter, &pixMapP);
GraphicsExportSetDepth(exporter, 24);
GraphicsExportSetOutputFile(exporter, &spec);
GraphicsExportDoExport(exporter, NULL);

CloseComponent(exporter);

ファイルに書き出すなら、こっちの方が楽だね。

■サンプルダウンロード:
ScreenShotCocoa.zip
ScreenShotCarbon.zip