ラベル Core Graphics の投稿を表示しています。 すべての投稿を表示
ラベル Core Graphics の投稿を表示しています。 すべての投稿を表示

2011年6月20日月曜日

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

あなたの瞳は世界をどんなふうに見るの?奇麗な瞳をみられたら写真家にもそんな気持ちになるときがあると思います。というわけで、こんなものをつくります。


Photoshop(CS3)では ”色の校正” プレビュー(5.0.3)では ”プロファイルを使ってソフトプルーフ” 、つまりソフトプルーフ機能を単独で実装します。

NSBitmapImageRep
表示だけのシステムなので、なにかないか探してみますとこんなものがありました。

- (NSBitmapImageRep *)bitmapImageRepByConvertingToColorSpace:(NSColorSpace *)targetSpace
                                                           renderingIntent:(NSColorRenderingIntent)renderingIntent

すてき。長いです。画像を描画意図(rendering intent)で別の色空間に変換してくれます。ここに色校正に使うディスプレイ・プロファイルとアウトプット・プロファイルからカラースペースを作って渡してやればよさそうです。なお 10.6 以降です。

NSColorSpace
NSColorSpace にはプロファイルのクラスごとにカラースペースを生成してくれるメソッドがないので

- (id)initWithColorSyncProfile:(void *)prof

を使ってColorSync プロファイルから作成してやれば良さそうです。引数が (void *)prof となっていますが、リファレンスは CMProfileRef わたすべしとなっています。なるほど。

ColorSync Manager API
目的は CMProfileRef を取得することです。

CMError CMOpenProfile (
   CMProfileRef *prof,
   const CMProfileLocation *theProfile
);

第1引数は戻ったときにプロファイルがはいります。なので第2引数の CMProfileLocation を取得して渡してやればよいですが、長旅になりそうな予感!

少し寄り道 CMIterateColorSyncFolder
インストースされているプロファイルの情報を提供してくれる API はないものでしょうか。そんな僕のために CMIterateColorSyncFolder 関数がありました。すべての利用可能なプロファイルからプロファイルごとの情報を確認できます。

CMError CMIterateColorSyncFolder (
   CMProfileIterateUPP proc,
   UInt32 *seed,
   UInt32 *count,
   void *refCon
);

第1引数は CMIterateColorSyncFolder ルーチンのなかで呼出されるコールバック関数のポインタになります。

第2引数はキャッシュの設定です。CMIterateColorSyncFolder 初めて呼出すときは0をセットしたポインタを渡せとあります。内部の seed と一致しなかったな場合はプロファイルごとに一度のコールバックを呼出すとあります。

第3引数は戻ってきたときに利用可能なプロファイルの数が入れられます。

第4引数は任意のデータへのポインタを指定します。この値は第1引数のコールバックが呼出されるたびに渡されます。なのでコールバック内で参照する必要のあるデータがある場合は設定します。

今度は CMProfileIterateUPP!
CMIterateColorSyncFolder の第1引数 CMProfileIterateUPP は

typedef CMProfileIterateProcPtr CMProfileIterateUPP;

となっていて CMProfileIterateProcPtr は

typedef OSErr (*CMProfileIterateProcPtr)
(
   CMProfileIterateData * iterateData,
   void * refCon
);

です。第1引数にはプロファイルに関連するデータ構造体です。第2引数にはCMIterateColorSyncFolder の第4引数で設定したデータへのポインタがそのままやってきます。

CMProfileLocation を求めて
CMProfileIterateData はどうなっているのでしょうか。

struct CMProfileIterateData {
   UInt32 dataVersion;
   CM2Header header;
   ScriptCode code;
   Str255 name;
   CMProfileLocation location;
   UniCharCount uniCodeNameCount;
   UniChar * uniCodeName;
   unsigned char * asciiName;
   CMMakeAndModel * makeAndModel;
   CMProfileMD5 * digest;
};
typedef struct CMProfileIterateData CMProfileIterateData;

CMProfileLocation ありました。これを CMOpenProfile に渡してやれば CMProfileRef が取得できます。さらに CM2Header 構造体の

struct CM2Header {
   UInt32 size;
   OSType CMMType;
   UInt32 profileVersion;
   OSType profileClass;
   OSType dataColorSpace;
   OSType profileConnectionSpace;
   CMDateTime dateTime;
   OSType CS2profileSignature;
   OSType platform;
   UInt32 flags;
   OSType deviceManufacturer;
   UInt32 deviceModel;
   UInt32 deviceAttributes[2];
   UInt32 renderingIntent;
   CMFixedXYZColor white;
   OSType creator;
   char reserved[44];
};
typedef struct CM2Header CM2Header;

OSType profileClass でプロファイルのクラスの情報があります。

enum {
   cmInputClass = 'scnr',
   cmDisplayClass = 'mntr',
   cmOutputClass = 'prtr',
   cmLinkClass = 'link',
   cmAbstractClass = 'abst',
   cmColorSpaceClass = 'spac',
   cmNamedColorClass = 'nmcl'
};

cmDisplayClass と cmOutputClass を使ってプロファイルのクラスを選別できそうです。

それでは
戻り値のチェックをしていません。
DrawingImage_9_AppDelegate.h
#import <Cocoa/Cocoa.h>

@class DISourceImageView, DIProofImageView;

@interface DrawingImage_9_AppDelegate : NSObject  {
    NSWindow *window;
    
    DISourceImageView *srcView;
    DIProofImageView *proofView;
    
    NSTextField *srcColorSpaceField;
    
    NSBitmapImageRep *srcImage;
}

#pragma mark Accessor Method
@property (assign) IBOutlet NSWindow *window;
@property (assign) IBOutlet DISourceImageView *srcView;
@property (assign) IBOutlet DIProofImageView *proofView;
@property (assign) IBOutlet NSTextField *srcColorSpaceField;

#pragma mark Action Method
- (IBAction)openImage:(id)sender;
@end

DrawingImage_9_AppDelegate.m
#import "DrawingImage_9_AppDelegate.h"
#import "DISourceImageView.h"
#import "DIProofImageView.h"

@implementation DrawingImage_9_AppDelegate

#pragma mark init & dealloc
- (void) dealloc
{
    [srcImage release];
    [super dealloc];
}

#pragma mark Accessor Method
@synthesize window, srcView, proofView, srcColorSpaceField;

#pragma mark Inner Method
- (void)setViewsWithImage:(NSBitmapImageRep *)bitmapImage
{
    if (bitmapImage) {
        NSColorSpace *srcSpace = [bitmapImage colorSpace];
        [srcColorSpaceField setStringValue:[srcSpace localizedName]];
    }
    else {
        [srcColorSpaceField setStringValue:@""];
    }
    
    [srcView setImage:bitmapImage];
    [proofView setImage:bitmapImage];
}

#pragma mark Action Method
-(IBAction)openImage:(id)sender
{
    NSOpenPanel *opPanel = [NSOpenPanel openPanel];
    
    [opPanel setCanChooseFiles:YES];
    [opPanel setCanChooseDirectories:NO];
    [opPanel setAllowsMultipleSelection:NO];
    
    [opPanel beginSheetModalForWindow:window
                    completionHandler:^(NSInteger result){
                        
                        if (result == NSFileHandlingPanelOKButton) {
                            
                            if (srcImage != nil) {
                                [srcImage release];
                            }
                            
                            NSData *data = [NSData dataWithContentsOfURL:[opPanel URL]];
                            srcImage = [[NSBitmapImageRep alloc] initWithData:data];
                            
                            [self setViewsWithImage:srcImage];
                            
                        }
                        else {
                            // do nothing; 
                        }
                        
                    }];
}
@end

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

@interface DISourceImageView : NSView {

    NSBitmapImageRep *image;

}

#pragma mark Accessor Method
- (void)setImage:(NSBitmapImageRep *)bitmapImage;
@end

DISourceImageView.m
#import "DISourceImageView.h"

@implementation DISourceImageView

#pragma mark init & dealloc
- (id)initWithFrame:(NSRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code here.
    }
    return self;
}

#pragma mark Drawing Method
- (void)drawRect:(NSRect)dirtyRect {
    
    if (image) {
        
        // ビューのサイズを取得
        CGSize viewSize = CGSizeMake([self bounds].size.width,
                                     [self bounds].size.height);
        
        // NSBitmapImageRep から CGImageRef を取得
        CGImageRef imageRep = [image CGImage];
        
        // 現在のグラフィクス・コンテキストを取得、変更するのでグラフィック状態を保存
        CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
        CGContextSaveGState(context);
        
        //描画意図を Absolute Colorimetric(絶対的な色域)に設定
        CGContextSetRenderingIntent(context, kCGRenderingIntentAbsoluteColorimetric);
        
        // 体裁を整えてイメージを描画
        CGFloat imageWidth = CGImageGetWidth(imageRep);
        CGFloat imageHeight = CGImageGetHeight(imageRep);
        
        CGFloat scale = imageHeight / imageWidth;
        
        BOOL isWide = (imageWidth >= imageHeight) ? YES : NO;
        
        if (isWide == YES) {
            
            CGFloat translate;
            translate = (viewSize.height - (viewSize.width * scale)) / 2.0;
            
            CGContextTranslateCTM(context, 0.0, translate);
            CGContextScaleCTM(context, 1.0, scale);
        }
        else {
            CGFloat translate;
            translate = (viewSize.width - (viewSize.height * (1.0 / scale))) / 2.0;
            
            CGContextTranslateCTM(context, translate, 0.0);
            CGContextScaleCTM(context, 1.0 / scale, 1.0);
            
        }
        
        CGContextDrawImage(context, CGRectMake(0.0,0.0,viewSize.width,viewSize.height), imageRep);
        
        // グラフィクス・コンテキストをもとに戻す。
        CGContextRestoreGState(context);
    }
    else {
        // do nothing;
    }
}

#pragma mark Accessor Method
- (void)setImage:(NSBitmapImageRep *)bitmapImage
{
    image = bitmapImage;
    [self setNeedsDisplay:YES];
}


@end

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

@interface DIProofImageView : NSView {

    NSPopUpButton *outputSpace;
    NSPopUpButton *intent;
    
    NSBitmapImageRep *image;
}

#pragma mark Accessor Method
@property (assign) IBOutlet NSPopUpButton *outputSpace;
@property (assign) IBOutlet NSPopUpButton *intent;
- (void)setImage:(NSBitmapImageRep *)bitmapImage;

#pragma mark Action Method
- (IBAction)selectedColorSpace:(id)sender;
- (IBAction)selectedRenderingIntent:(id)sender;
@end

DIProofImageView.m
#import "DIProofImageView.h"

#pragma mark Profile Iteration Callback Function
OSErr profileIterateProcPtr (CMProfileIterateData *iterateData, void *refCon)
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSMutableArray *colorSpaces = refCon;

    // プロファイルのクラスを取得
    OSType profileClass = (*iterateData).header.profileClass;
    
    // クラスがアウトプット、ディスプレイに該当したら追加する。
    if (profileClass == cmOutputClass || profileClass == cmDisplayClass){

        // プロファイルからカラースペースを作成する。
        CMProfileRef prof;
        CMProfileLocation location = iterateData->location;
        
        // プロファイルを開く
        CMOpenProfile(&prof, &location);
        
        // プロファイルからカラースペースを作成
        NSColorSpace *colorSpace = [[NSColorSpace alloc] initWithColorSyncProfile:prof];
        [colorSpaces addObject:colorSpace];
        
        // もういらない。開いたプロファイルは閉じる。
        [colorSpace release];
        CMCloseProfile(prof);
        
    }
    
    [pool drain];
    return noErr;
}
#pragma mark -
@implementation DIProofImageView

#pragma mark init & dealloc
- (id)initWithFrame:(NSRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code here.
    }
    return self;
}

#pragma mark awakeFromNib
- (void)awakeFromNib
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    
    // インストールされているプロファイルからカラースペースを作成して、Arrayに追加する。
    NSMutableArray *colorSpaces = [NSMutableArray array];
    CMIterateColorSyncFolder(profileIterateProcPtr, NULL, NULL, colorSpaces);
    
    // メニューを作成してポップアップボタンに設定する。
    NSMenu *outputMenu = [[NSMenu alloc] init];
    
    for (NSColorSpace *colorSpace in colorSpaces) {
        
        NSMenuItem *item = [[NSMenuItem alloc] init];
        
        // メニューアイテムにタイトルとカラースペースを設定、メニューに追加
        [item setTitle:[colorSpace localizedName]];
        [item setRepresentedObject:colorSpace];
        [outputMenu addItem:item];
        
        [item release];
    }
    
    [outputSpace setMenu:outputMenu];
    
    [outputMenu release];
    [colorSpaces removeAllObjects];

    
    // 描画意図(Rendering intent)をポップアップボタンに設定する。
    NSMutableDictionary *intentDic = [NSMutableDictionary dictionary];
    
    [intentDic setValue:[NSNumber numberWithInteger:1] forKey:@"Absolute Colorimetric"];
    [intentDic setValue:[NSNumber numberWithInteger:2] forKey:@"Relative Colorimetric"];
    [intentDic setValue:[NSNumber numberWithInteger:3] forKey:@"Perceptual"];
    [intentDic setValue:[NSNumber numberWithInteger:4] forKey:@"Saturation"];
    
    NSMenu *intentMenu = [[NSMenu alloc] init];
    
    for (NSString *key in [intentDic allKeys]) {
        
        NSMenuItem *item = [[NSMenuItem alloc] init];
        
        // メニューアイテムにタイトルとカラースペースを設定、メニューに追加
        [item setTitle:key];
        [item setRepresentedObject:[intentDic valueForKey:key]];
        [intentMenu addItem:item];
        
        [item release];
    }
    
    [intent setMenu:intentMenu];
    
    [intentMenu release];
    [intentDic removeAllObjects];
    
    [intent selectItemWithTitle:@"Perceptual"];
    
    [pool drain];
}

#pragma mark Drawing Method
- (void)drawRect:(NSRect)dirtyRect {
    
    if (image) {
        
        // 選択されているカラースペースと描画意図からビットマップを変換する
        NSColorSpace *colorSpace;
        NSColorRenderingIntent renderingIntent;
        NSBitmapImageRep *outputImage;
        
        colorSpace = [[outputSpace selectedItem] representedObject];
        renderingIntent = [[[intent selectedItem] representedObject] integerValue];
        outputImage = [image bitmapImageRepByConvertingToColorSpace:colorSpace
                                                    renderingIntent:renderingIntent];
        
        // ビューのサイズを取得
        CGSize viewSize = CGSizeMake([self bounds].size.width,
                                     [self bounds].size.height);
        
        // NSBitmapImageRep から CGImageRef を取得
        CGImageRef imageRep = [image CGImage];
        
        // 現在のグラフィクス・コンテキストを取得、変更するのでグラフィック状態を保存
        CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
        CGContextSaveGState(context);
        
        //描画意図を Absolute Colorimetric(絶対的な色域)に設定
        CGContextSetRenderingIntent(context, kCGRenderingIntentAbsoluteColorimetric);
        
        // 体裁を整えてイメージを描画
        CGFloat imageWidth = CGImageGetWidth(imageRep);
        CGFloat imageHeight = CGImageGetHeight(imageRep);
        
        CGFloat scale = imageHeight / imageWidth;
        
        BOOL isWide = (imageWidth >= imageHeight) ? YES : NO;
        
        if (isWide == YES) {
            
            CGFloat translate;
            translate = (viewSize.height - (viewSize.width * scale)) / 2.0;
            
            CGContextTranslateCTM(context, 0.0, translate);
            CGContextScaleCTM(context, 1.0, scale);
        }
        else {
            CGFloat translate;
            translate = (viewSize.width - (viewSize.height * (1.0 / scale))) / 2.0;
            
            CGContextTranslateCTM(context, translate, 0.0);
            CGContextScaleCTM(context, 1.0 / scale, 1.0);
            
        }
        
        [outputImage drawInRect:[self bounds]];
        
        // グラフィクス・コンテキストをもとに戻す。
        CGContextRestoreGState(context);
    }
    else {
        // do nothing;
    }
}

#pragma mark Accessor Method
@synthesize outputSpace, intent;

- (void)setImage:(NSBitmapImageRep *)bitmapImage
{
    image = bitmapImage;
    [self setNeedsDisplay:YES];
}

#pragma mark Action Method
- (IBAction)selectedColorSpace:(id)sender
{
    [self setNeedsDisplay:YES];
}
- (IBAction)selectedRenderingIntent:(id)sender
{
    [self setNeedsDisplay:YES];
}
@end

Interface Builder での作業



その他
指定したカラースペースに変換したビットマップを生成するのに ColorSync Manager API だけでやろうとするとけっこうめんどうです。NCWConcatColorWorld か CWConcatColorWorld を使って CMWorldRef を作成してやります。それを CWMatchBitmap にかけてやってビットマップを作成すれば良いと思います。また、この間に使用するいくつかの構造体を定義してやらなければなりません。

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.

だそうです。