HMDT - Logic and Intuition -

about HMDT

Cocoa Programming Tips 1001

With Carbon

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 に kCGImageAlphaFirstkCGImageAlphaPremultipliedFirst を指定して、source 画像を作る。
  • NSBitmapimageRep の bitmapData から、CGBitmapContextCreate() を使って、alphaInfo に kCGImageAlphaLastkCGImageAlphaPremultipliedFirst を指定して、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.dmg
ScreenShotCarbon.dmg

back to top content

Copyright © 2002-2006 HMDT. All rights reserved.