2011年6月6日月曜日

イメージを描画する(7)

ことが後先になりますが Core Graphics について。

Core Graphics or Quartz

The Quartz 2D API is part of the Core Graphics framework, so you may see Quartz referred to as Core Graphics or, simply, CG.

といわけで、Quartz(Quartz 2D)と Core Graphics はおおよそ同義で使われます。

何をどうすればいいの?

A graphics context is an opaque data type (CGContextRef) that encapsulates the information Quartz uses to draw images to an output device, such as a PDF file, a bitmap, or a window on a display. The information inside a graphics context includes graphics drawing parameters and a device-specific representation of the paint on the page. All objects in Quartz are drawn to, or contained by, a graphics context.

グラフィクス・コンテキストはQuartz がイメージを PDF ファイル、ビットマップ、ディスプレイ上のウィンドウのような、出力先のデバイスに描画するために使う情報をカプセル化する不透明なデータ型(opaque data type)です。Quartz によるすべてのオブジェクトはグラフィクス・コンテキストに描画され、グラフィクス・コンテキストに含まれます。なるほど。

ところでリファレンスやガイドによく出てくる opaque data type って何でしょうか。わからなかったので、すこし寄り道。

In computer science, an opaque data type is a data type that is incompletely defined in an interface, so that ordinary client programs can only manipulate data of that type by calling procedures that have access to the missing information.

計算機科学において不透明な(opaque)データ型とはインターフェイスで不完全に定義されたデータ型のことです。通常クライアントプログラムは隠蔽されている情報にアクセスをもつプロシージャを呼出すことによってのみデータを操作することができます。

たとえば C でオブジェクト指向を実装しようとした場合に、オブジェクト自体はこの opaque data type にしておいて、プロシージャとともにヘッダで公開する。そして実装はライブラリとかで提供すれば、提供者は利用者に安全に使ってもらえる、利用者は実際のデータ構造が解らなくてもプロシージャを通して便利に使えるってことでしょうか(間違ってたらごめん)。実際に CGContextRef は CGContext.hで

typedef struct CGContext *CGContextRef;

CGContext 構造体を typedef したポインタとしてのみ宣言されていて、その他 CGContextRef を引数にとる CGContext* 関数のプロトタイプが宣言されています。

戻りまして、

When you draw with Quartz, all device-specific characteristics are contained within the specific type of graphics context you use. In other words, you can draw the same image to a different device simply by providing a different graphics context to the same sequence of Quartz drawing routines. You do not need to perform any device-specific calculations; Quartz does it for you.

Quartz を使用して描画するとき、すべてのデバイス固有の特性は使用するグラフィクス・コンテキストの特定の型の中に含まれています。いいかえれば、Quartz の描画ルーチンの同じシーケンスに異なるグラフィクス・コンテキストを提供することによって、異なるデバイスに同じイメージを描くことができます。デバイス固有の計算を実行する必要はありません。Quartz がやってくれます。

つまり、描画先のグラフィクス・コンテキストを取得または作成して、描画ルーチンにコンテキストを渡してやるだけで、あとは Quartz がやってくれるということです。

それでは
同じ描画ルーチンに異なるコンテキストを渡してそれぞれのコンテキストに描画してみます。
  • ビューに描画
  • ビットマップに描画
  • PDF ファイルに描画
Xcode で新規プロジェクトを Cocoa Application オプションは何もチェックせずに作成します。カスタム・ビューを使うので、新規ファイルで subclass of を NSView で追加しました。

ソース

DrawingImage_7_AppDelegate.h
#import <Cocoa/Cocoa.h>

@class CustomView;

@interface DrawingImage_7_AppDelegate : NSObject <NSApplicationDelegate> {
    NSWindow *window;
    CustomView *aView;
}
@property (assign) IBOutlet NSWindow *window;
@property (assign) IBOutlet CustomView *aView;

- (IBAction)createTIFF:(id)sender;
- (IBAction)createPDF:(id)sender;

@end

DrawingImage_7_AppDelegate.m
#import "DrawingImage_7_AppDelegate.h"
#import "CustomView.h"

@implementation DrawingImage_7_AppDelegate

@synthesize window, aView;

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
 // Insert code here to initialize your application 
}

- (IBAction)createTIFF:(id)sender
{
    [aView createTIFF];
}
- (IBAction)createPDF:(id)sender
{
    [aView createPDF];
}
@end

CustomView.h
#import <Cocoa/Cocoa.h>

@interface CustomView : NSView {
}
- (void)createTIFF;
- (void)createPDF;
@end

CustomView.m
#import "CustomView.h"


@implementation CustomView

- (id)initWithFrame:(NSRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code here.
    }
    return self;
}

- (void)drawWithContext:(CGContextRef)context
{
    CGContextSaveGState(context);
    
    CGContextSetFillColorSpace(context, [[[self window] colorSpace] CGColorSpace]);
    
    CGRect rect;
    rect.origin = CGPointMake(0.0, 0.0);
    rect.size = CGSizeMake([self visibleRect].size.width, [self visibleRect].size.height);
    
    CGContextSetRGBFillColor(context, 1.0, 1.0, 1.0, 1.0);
    CGContextFillRect(context, rect);
    
    CGContextSetRGBFillColor(context, 1.0, 0.0, 0.0, 1.0);
    CGContextFillRect(context, CGRectMake(0.0, 0.0, 200.0, 100.0));
    
    CGContextSetRGBFillColor(context, 0.0, 0.0, 1.0, 0.5);
    CGContextFillRect(context, CGRectMake(0.0, 0.0, 100.0, 200.0));
    
    CGContextRestoreGState(context);
}

- (void)drawRect:(NSRect)dirtyRect {
    
    CGContextRef viewContext = [[NSGraphicsContext currentContext] graphicsPort];
    
    [self drawWithContext:viewContext];
}

- (void)createTIFF
{
    NSRect viewRect = [self visibleRect];
    size_t width = (size_t)viewRect.size.width;
    size_t height = (size_t)viewRect.size.height;
    
    
    CGContextRef bitmapContext;
    CGColorSpaceRef colorSpace;
    
    colorSpace = [[[self window] colorSpace] CGColorSpace];
    
    bitmapContext = CGBitmapContextCreate(NULL,
                                          width,
                                          height,
                                          8,
                                          width * 4,
                                          colorSpace,
                                          kCGImageAlphaPremultipliedLast);
        
    [self drawWithContext:bitmapContext];
    
    CGImageRef imageRef = CGBitmapContextCreateImage(bitmapContext);
    
    CGContextRelease(bitmapContext);
    
    NSBitmapImageRep *imageRep = [[NSBitmapImageRep alloc] initWithCGImage:imageRef];
    
    CGImageRelease(imageRef);
    
    NSData *tiffData = [imageRep TIFFRepresentation];
    
    [tiffData writeToFile:[NSHomeDirectory() stringByAppendingPathComponent:@"Desktop/test.tif"]
               atomically:YES];
    
    [imageRep release];
    
}

- (void)createPDF
{   
    CGRect mediaBox;
    mediaBox.origin = CGPointMake(0.0, 0.0);
    mediaBox.size = CGSizeMake([self visibleRect].size.width, [self visibleRect].size.height);
    
    NSURL *URL;
    CGContextRef PDFContext;
    
    URL = [NSURL fileURLWithPath:[NSHomeDirectory() stringByAppendingPathComponent:@"Desktop/test.pdf"]];
    PDFContext = CGPDFContextCreateWithURL((CFURLRef)URL, &mediaBox, NULL);
    
    CGContextBeginPage(PDFContext, &mediaBox);
    
    [self drawWithContext:PDFContext];
    
    CGContextEndPage(PDFContext);

    CGContextRelease(PDFContext);
}
@end

Pop Up Button のメニューをそれぞれ TIFF と PDF を設定してアクションにそれぞれ接続します。

結果は?
同じルーチンでそれぞれのコンテキスト描画できました。TIFF は問題ありませんでしたが、PDFは色が違いました。Quartz で PDF に描画する場合のカラースペースの設定はどうすればいいのでしょうか。コンテキスト作成時の辞書に NULL を渡しています。ここできちんと情報を渡せばよい気がしますが Auxiliary Dictionary Keys を見て適当に kCGPDFContextOutputIntent や kCGPDFXDestinationOutputProfile を設定して渡してみましたが特に変化せずでしたので保留。


NSRect と CGRect
余談ですが、Release 時のビルドのときに少しはまりましたので。NSSize, NSPoint も同様です。

When building for 64 bit systems, or building 32 bit like 64 bit, NSRect is typedef’d to CGRect.

だそうです。

0 件のコメント:

コメントを投稿