ページ

ラベル プリファレンス の投稿を表示しています。 すべての投稿を表示
ラベル プリファレンス の投稿を表示しています。 すべての投稿を表示

2008年12月15日月曜日

指定のプリファレンス設定を開く

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

昨日の続き。
Selection のコンテキストメニューでプリファレンスウィンドウを開いたら、選択範囲のタブを表示させたい。


まず NSMenuItem にどのタブを開くか仕込む。この目的には NSMenuItem#setRepresentedObject: が使える。こんな感じ。

NSMenuItem* item;
 :
[item setRepresentedObject:[NSNumber numberWithInt:2]];

ここでは NSNumberにタブのIndexとして2を持たせている。

メニューが選択するとターゲットのアクションが呼出される。アクションにはこの時の引数 senderに先ほどの NSMenuItemがわたってくるので、representedObject でタブのIndexを取り出せば良い。SimpleCapの実装ではそれをプリファレンスのコントローラへ渡して、プリファレンスウィンドウを表示させる。
- (IBAction)openPereferecesWindow:(id)sender
{
NSInteger tab_index = -1;
if ([sender isKindOfClass:[NSMenuItem class]]) {
id rep_obj = [sender representedObject];
if (rep_obj) {
tab_index = [rep_obj intValue];
}
}
[_preference_controller openAtTabIndex:tab_index];
   :


プリファレンスコントローラは渡されたインデックスのタブを表示させウィンドウを表示する。
- (void)openAtTabIndex:(NSInteger)tab_index
{
switch (tab_index) {
case 0:
[_tab_ivew selectTabViewItemAtIndex:tab_index];
[_toolbar setSelectedItemIdentifier:TOOLBAR_GENERAL];
break;

case 1:
[_tab_ivew selectTabViewItemAtIndex:tab_index];
[_toolbar setSelectedItemIdentifier:TOOLBAR_WINDOWS];
break;

case 2:
[_tab_ivew selectTabViewItemAtIndex:tab_index];
[_toolbar setSelectedItemIdentifier:TOOLBAR_SELECTION];
break;

case 3:
[_tab_ivew selectTabViewItemAtIndex:tab_index];
[_toolbar setSelectedItemIdentifier:TOOLBAR_ADVANCED];
break;

default:
break;
}
[_window makeKeyAndOrderFront:self];
}

タブは NSTabView#selectTabViewItemAtIndex: で変更できる。またタブだけでなくツールバーも忘れずに NSToolBar#setSelectedItemIdentifier: で目的のものに変更しておく。

2008年9月27日土曜日

環境設定(17)

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

今回はキャプチャに効果音を付ける。Cocoaには NSSound が用意されていて、これを使うとたった2行で音が出せる。

 NSSound *sound = [NSSound soundNamed:@"Submarine"];
[sound play];


今回はシステムに付属している /System/Library/Sounds/Submarine.aiff を鳴らしてみた。


音の有無が制御できるよう新たに環境設定に "Play Sounds"を加えた。



- - - -
コードは簡単にできたが、肝心の音が用意できない。
シャッター音はどこで手に入るのだろうか?
自分で作らないとダメだろうか。

2008年9月26日金曜日

環境設定(16)

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

角丸+影+白枠のケースを例にして処理を追ってみる。

まずCGWindowListCreateImage( ) でキャプチャ画像を用意し、NSImageに変換しておく。


次に最終画像を書き込む NSImage *output_imageを用意する。大きさは、影と白枠が付くので元画像よりも大きくなる。※説明しやすいように黄色で表現してある。


白枠用のパス NSBezierPath* frame_path を用意する。白枠が付かない場合も画像と同じ大きさで作っておく。影を描く時にこれを使う。なお今回は角が丸いので #bezierPathWithRoundedRect:xRadius:yRadius: を使う(角が丸くない場合は #bezierPathWithRect: を使う)。


output_imageのフォーカスをロックした上で、frame_pathを使って白く塗りつぶす。

 [output_image lockFocus];
[[NSColor whiteColor] set];
[frame_path fill];



塗りつぶす前に NSShadow*shadow を作り #set しておく。そうすると上記の様に塗りつぶした領域の下に影が付く。

最後に src_image を上に描く。


画像の角を丸く切り抜くには、角丸のパスを作り、これを使って描画時にクリッピングする。こんな感じ。



できあがり。



角丸、影、白枠の有無によって上記の大きさや、処理の有無を調整すればいろいろな組み合わせで加工できる。

ソースコードは SimpleCap の一部として後日公開予定。

2008年9月25日木曜日

環境設定(15)

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

実装してみた。

まずノーマル。


影付き。


白枠付き。


影+角丸。


白枠+影+角丸


解説はまた明日。

2008年9月24日水曜日

環境設定(14)

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

プレビューができたので、実際の実装に入る。手を入れるのは範囲選択処理を司る SelectionHandler。キャプチャ画像を作成するメソッドが1つにまとめられているので、ここに手を入れれば良い。

SelectionHandler.m

- (CGImageRef)capture
{
CGImageRef cgimage = CGWindowListCreateImage(NSRectToCGRect(_rect),
kCGWindowListOptionOnScreenBelowWindow,
[_capture_controller windowID],
kCGWindowImageDefault);}
return cgimage;


他のHandlerと同様、最後のファイル出力メソッドへは CGImage で渡している。一方、画像を加工するには NSImageの方が少なくとも自分にとっては都合が良い。そこで一旦、NSImageへ変換してそれを加工した後、最後にまた CGImageへ変換して返す事にする。そうすればこのメソッド以外に書き直す必要は無い。

こんな感じ。
- (CGImageRef)capture
{
// (1) キャプチャ
CGImageRef cgimage = CGWindowListCreateImage(NSRectToCGRect(_rect),
kCGWindowListOptionOnScreenBelowWindow,
[_capture_controller windowID],
kCGWindowImageDefault);}

// (2) NSImage へ変換
NSBitmapImageRep *bitmap = [[[NSBitmapImageRep alloc] initWithCGImage:cgimage] autorelease];
NSImage* src_image = [[[NSImage alloc] init] autorelease];
[src_image addRepresentation:bitmap];

// (3) 加工
    :
    :
NSImage* output_iamge = 加工 ( src_image );

// (4) CGImageへ戻す
NSBitmapImageRep *out_bitmap = [NSBitmapImageRep imageRepWithData:[output_image TIFFRepresentation]];
CGImageRef out_image = [out_bitmap CGImage];


// (5) 結果を返す
return cgimage;
}


変換のオーバーヘッドはあるが、これで扱いやすくなった。実際の加工処理を入れていこう。

2008年9月23日火曜日

環境設定(13)

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

プレビューを実装してみた。

最初はこう。


白枠をつける。


それに影を追加。


最後に角丸。


白枠を外す。組み合わせは自由。



プレビューはこれでいいだろう。実際の機能を実装しよう。

2008年9月22日月曜日

環境設定(12)

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

環境設定は続く。次は選択範囲のオプション設定。

範囲選択で切り出した画像を出力時に加工する。白枠(White Frame)、影(Shadow)、角丸(RoundRect)の3つを用意する。


今回はプレビューの作成から入ろう。


おおっと。今日は時間切れでここまで。続きは明日。

2008年9月21日日曜日

環境設定(11)

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

プレビューの続き。


まずはマウスカーソルの表示。これは NSCursorから画像を取得して重ね合わせれば良い。

 [frame_image lockFocus];
   :
if ([[UserDefaults valueForKey:UDKEY_MOUSE_CURSOR] boolValue]) {
NSPoint cursor_point = NSMakePoint(50, 60);
[[[NSCursor arrowCursor] image] compositeToPoint:cursor_point operation:NSCompositeSourceOver];
}


マウスカーソルを描きたい NSViewのフォーカスをロックし(lockFocus)、いつものごとく#compositeToPoint:operation:で合成する。

するとこうなる。



次は影付け。NSShadow を使えば簡単にできる。
[NSGraphicsContext saveGraphicsState];          // (*0)

// Window Shadow
if ([[UserDefaults valueForKey:UDKEY_WINDOW_SHADOW] boolValue]) {

NSShadow* shadow = [[NSShadow alloc] init]; // (*1)
[shadow setShadowOffset:NSMakeSize(10.0, -10.0)];
[shadow setShadowBlurRadius:10.0];
[shadow setShadowColor:[[NSColor blackColor] colorWithAlphaComponent:0.3]];
[shadow set]; // (*2)

}
// Compsite Image
[example_image compositeToPoint:draw_point operation:NSCompositeSourceOver]; // (*3)

[NSGraphicsContext restoreGraphicsState]; // (*4)


NSShadowのインスタンスを作成した後(*1)、set メッセージを投げてやる(*2)。するとこの後の描画には影が自動的に付く様になる。サンプル画像を描画する(*3)。ポイントはNSGraphicsContextの保持(*0)と復帰(*4)を前後に挟むこと。これらが無いと、この後の描画全てに影が付いてしまうことになる。

結果はこんな感じ。


なおサンプルの画像は次のように左上に余白があるもの使う。これは余白が無いと右上と左下の影が切れてしまう為。



最後に背景。下のような画像を用意して合成するだけ。


if ([[UserDefaults valueForKey:UDKEY_BACKGROUND] boolValue]) {
[[NSImage imageNamed:@"output_background"] compositeToPoint:NSZeroPoint operation:NSCompositeSourceOver];
}



するとこうなる。


いい感じだ。

2008年9月20日土曜日

環境設定(10)

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

今回はプレビューを作る。前回までで、影、マウスカーソル、背景とオプション設定ができるようになったが、これを直感的に分かる様に例を表示したい(プレビュー)。

まずは InterfaceBuilderで NSImageView を配置する。


PreferenceContoller にアウトレットを用意し、配置したNSImageViewへ接続する。

PreferenceController.h

@interface PreferenceController : NSObject {
   :
IBOutlet NSImageView* _image_view;
   :
}





つづいて例で使う画像を用意する。これは手作業で加工して(地道に?)作る。ファイル名は output_example.tiff



最後にコーディング。まずは表示確認を目的として描画してみる。下記のメソッドを呼出して描画する。

PreferenceController.m
- (void)drawOutputExample
{
NSSize frame_size = NSMakeSize(120, 120);
NSImage* frame_image = [[NSImage alloc] initWithSize:frame_size];
NSImage* example_image = [NSImage imageNamed:@"output_example"];
NSPoint draw_point = NSMakePoint(0, frame_size.height - [example_image size].height);

[frame_image lockFocus];
[example_image compositeToPoint:draw_point operation:NSCompositeSourceOver];
[frame_image unlockFocus];
[_image_view setImage:frame_image];
}


120x120ピクセルの NSImageを作り、そこへ output_example.tiff を合成し NSImageViewで表示する。


実行するとこんな感じ。



- - - -
この画像に影を付けたり、マウスカーソルを重ねたりしてプレビューの役割をまかせる。

2008年9月18日木曜日

環境設定(9)

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

前回の続き。

SimpleCapではキャプチャ処理をハンドラの基底クラスにあたる HandlerBase で行っている。

※クラス構成は過去のブログをどうぞ:SimpleCap (2) - 基本機能実装


具体的には各ハンドラはキャプチャ実行時に、Windowクラスのリスト(CGWindowIDを含む)と矩形領域(CGRect)を HandlerBase#cgimageWihWindowList:cgrectへ渡して、CGImageを取得する。

HandlerBase.m

- (CGImageRef)cgimageWithWindowList:(NSArray*)list cgrect:(CGRect)cgrect
{
   :


この中で画像キャプチャを行う CGWindowListCreateImageFromArray( ) を呼出している。つまりここを押さえれば出力の画像を自由にできる。今回はここに背景画像の合成処理を加えた。といって背景(壁紙)の CGWindowID を CGWindowListCreateImageFromArray( )に渡す配列に加えるだけで簡単にできる。

   :
BOOL is_background = [[UserDefaults valueForKey:UDKEY_BACKGROUND] boolValue];   // (1)
   :
if (is_background) {
windowIDs[widx++] = [Window desktopWindowID];      // (2)
}
   :
CFArrayRef windowIDsArray = CFArrayCreate(kCFAllocatorDefault, (const void**)windowIDs, widx, NULL);
CGImageRef cgimage = CGWindowListCreateImageFromArray(cgrect, windowIDsArray, option);
free(windowIDs);


UserDefaultsから設定を取り出し、それによって背景のCGWindowIDを Window#desktopWindowID から取得して、CGWindowListCreateImageFromArray( )へ渡す配列へ加えている(1)(2)。

背景のCGWindowIDは WindowList(自作ソフト)を使って調べると"DeskTop"という名前がついていて、"Window Server"という名前のプロセスによって作成されているのが分かる。


Window#desktopWindowID は安易にこの名前を使って CGWindowIDを決定する(もっといい方法があれば是非教えて下さい)。

Window.m
static _desktop_windowID = -1;
+ (CGWindowID)desktopWindowID
{
if (_desktop_windowID != -1) {
return _desktop_windowID;
}

CFArrayRef ar = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
CFDictionaryRef window;
CFIndex i;

_desktop_windowID = kCGNullWindowID;
for (i=0; i < CFArrayGetCount(ar); i++) {
window = CFArrayGetValueAtIndex(ar, i);
NSString* name = (NSString*)CFDictionaryGetValue(window, kCGWindowName);
NSString* owner_name = (NSString*)CFDictionaryGetValue(window, kCGWindowOwnerName);
if ([name isEqualToString:@"Desktop"] && [owner_name isEqualToString:@"Window Server"]) {
CFNumberGetValue(CFDictionaryGetValue(window, kCGWindowNumber),
kCGWindowIDCFNumberType, &_desktop_windowID);
break;
}
}
return _desktop_windowID;
}


これで、できあがり。

これが。


こうなる。


- - - -
ほ。今日もブログはぎりぎり間に合った。
最近は仕事が忙しくてかなわない。

環境設定(8)

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

今回は "Background"オプション。背景(壁紙)をキャプチャに含める。
これは初実装。

いつもはこんな感じ。


オプションをONにするとこうなる。


メニューもこの通り。


時間が無いので解説はまた明日。

おお、なんとか今日に間に合った?

2008年9月17日水曜日

環境設定(7)

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

次はマウスカーソル。



マウスカーソルはファイルへ書き出す前の最後の段階で合成している。ここを抑えればいいだろう。その責務は CaputreControllerクラスが担っている。

CaptureContoller.m

- (void)saveImage:(CGImageRef)cgimage withMouseCursorInRect:(NSRect)rect
{
:
int is_mouse_cursor = [[UserDefaults valueForKey:UDKEY_MOUSE_CURSOR] intValue];

if (is_mouse_cursor && ...
// マウスカーソル画像の合成処理
:


マウスカーソルあり。


マウスカーソルなし。



よし。

2008年9月16日火曜日

環境設定(6)

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

今度はツールバーを増やし出力オプションを追加する。



まずはウィンドウの影の制御(Winow Shadow)から。

これは SimpleCap内部で使っている CGWindowListCreateImageFromArray( ) 関数の第三引数で制御できる。

CGWindow.h

enum {
kCGWindowImageDefault = 0, /* Default behavior: If a rect of CGRectNull is used
* bounds computation includes the framing effects, such as a shadow. */
kCGWindowImageBoundsIgnoreFraming = (1 << 0), /* If a rect of CGRectNull is used,
* ignore framing effects for bounds computation */
kCGWindowImageShouldBeOpaque = (1 << 1), /* The captured image should be
* opaque. Empty areas are white */
kCGWindowImageOnlyShadows = (1 << 2)
};


オプションの種類は全部で4つ。挙動は以前ブログで紹介したことがある。
CGWindowListCreateImage のオプション

今回はこのうちの kCGWindowImageDefault(影あり)とkCGWindowImageBoundsIgnoreFraming(影無し)を使おう。以前リファクタリングした時に CGWindowListCreateImageFromArray( ) の利用はハンドラのベースクラス HandlerBase#cgimageWithWindowList:cgrect へまとめてある。ここだけを変更すれば良い。

HandlerBase.m
- (CGImageRef)cgimageWithWindowList:(NSArray*)list cgrect:(CGRect)cgrect
{ CGWindowImageOption option;

if ([[UserDefaults valueForKey:UDKEY_WINDOW_SHADOW] intValue] == 1) {
option = kCGWindowImageDefault;
} else {
option = kCGWindowImageBoundsIgnoreFraming;
}
:
CGImageRef cgimage = CGWindowListCreateImageFromArray(cgrect, windowIDsArray, option);
:


実行してみる。

影あり


影無し



良さそうだ、

2008年9月15日月曜日

環境設定(5)

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

次はファイルの保管場所。


"Choose..."を押すとダイアログが開いて保存先を選べる。



ダイアログのオープンには NSPanel#runModalForDirectory:file:types: を使う。

PreferenceController.m

- (void)chooseImageLocation:(id)sender
{
NSOpenPanel *openPanel = [NSOpenPanel openPanel];
[openPanel setCanChooseFiles:NO];
[openPanel setCanChooseDirectories:YES];
[openPanel setCanCreateDirectories:YES];
[openPanel setAllowsMultipleSelection:NO];

int result = [openPanel runModalForDirectory:NSHomeDirectory()
file:nil
types:nil];

if (result == NSOKButton) {
NSArray* filenames = [openPanel filenames];
if ([filenames count] > 0) {
NSString* new_path = [filenames objectAtIndex:0];
[UserDefaults setValue:new_path forKey:UDKEY_IMAGE_LOCATION];
}
}
}


上記の UserDefaults は NSDefaultsController を初期化したりするラッパー。パネルでフォルダが選択されたらユーザでフォルトへ書き込む。

NSDefaultsController 経由の書き込みは #values で取り出したオブジェクトに対して #setValue:forKey: を投げる。するとNSDefaultsControllerとバインドされたビュー上のオブジェクトも値が更新される。

2008年9月14日日曜日

環境設定(4)

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

試しに一つ設定を追加してみる。


InterfaceBuilderへラベルとポップアップボタンを配置し、NSUserDefaultsControllerへ接続するたけでユーザインターフェイスはできあがり。

コードでは SimpleCap内でファイル保存を担当する FileManagerクラスに手を入れる。

FileManager.m

- (void)saveImage:(NSBitmapImageRep*)bitmap_rep
{
int image_format_tag = [[[[NSUserDefaultsController
sharedUserDefaultsController] values]
valueForKey:@"image_format"] intValue];
NSData* data;

switch (image_format_tag) {
case 1:
// GIF
data = [bitmap_rep representationUsingType:NSGIFFileType
properties:[NSDictionary dictionary]];
break;

case 2:
// JPEG
data = [bitmap_rep representationUsingType:NSJPEGFileType
properties:[NSDictionary dictionary]];
break;

case 0:
defaults:
// PNG
data = [bitmap_rep representationUsingType:NSPNGFileType
properties:[NSDictionary dictionary]];
break;

}
[data writeToFile:[self nextFilename] atomically:YES];
}


"image_format" という名前で NSUserDefaultsから値を取り出して利用する。


PNG形式。ブログの画像では分かりづらいが影の部分はアルファチャネルが保存されいる。


JPEG形式。背景が白色となっている。


GIF形式。影の部分が真っ黒になってしまった。


- - - -
GIFの問題はあるが、環境設定はこんな感じで使っていけることがわかった。
どしどし追加していこう。

2008年9月13日土曜日

環境設定(3)

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

動作確認ができたので一つづつ作り込んでいく。まずは一般のタブから。




- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag
{
NSToolbarItem *toolbar_item = [[[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier] autorelease];

if ([itemIdentifier isEqualToString:TOOLBAR_GENERAL]) {
[toolbar_item setLabel:NSLocalizedString(@"General", @"")];
[toolbar_item setImage:[NSImage imageNamed:NSImageNamePreferencesGeneral]];
[toolbar_item setTarget:self];
[toolbar_item setAction:@selector(click:)];
} else {
toolbar_item = nil;
}

return toolbar_item;
}


文字列は多言語対応できるようにリソースに定義したものを使う。

初期状態はべた書き。
- (void)awakeFromNib
{
_toolbar = [[NSToolbar alloc] initWithIdentifier:PREFERENCE_TOOLBAR];
[_toolbar setDelegate:self];
[_window setToolbar:_toolbar];
[_toolbar setSelectedItemIdentifier:TOOLBAR_GENERAL];
[_window setTitle:NSLocalizedString(@"General", @"")];
}


Mail.app や Safari では選択したツールバーの名称をウィンドウのタイトルにしていたので、それに見習う。