あなたの瞳は世界をどんなふうに見るの?奇麗な瞳をみられたら写真家にもそんな気持ちになるときがあると思います。というわけで、こんなものをつくります。
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 にかけてやってビットマップを作成すれば良いと思います。また、この間に使用するいくつかの構造体を定義してやらなければなりません。