ページ

2009年9月13日日曜日

Snow Leopard で CGWindowListCreateImage に CGRectInfinite を渡すと固まる場合がある

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

SimpleCap ユーザの方から Snow Leopard でスクリーンキャプチャを実行した時、タイマーが0になった後に固まってしまうとの報告をうけた。コンソール.app のログをもらったところ下記のエラーが出ているのがわかった。

SimpleCap[1199] : CGImageCreate: invalid image bits/pixel or bytes/row.


自分の所の Snow Leopard では発生しないので何らかの条件が揃うと起きるようだ。

ソースを見たところ特に問題が見つらなかったので、"snow leopard CGWindowListCreateImage" でネットを検索したところ下記の記事が見つかった。

Re: CGWindowListCreateImage fails on Snow Leopard with kCGNullWindowID

CGWindowListCreateImage() は CGRectInfinite を渡すと、自動的にスクリーン全体の大きさを割り出して CGImage を作ってくれる。この情報によると、本来 CGRectInfinite はその名の通りシステム内で無限相当の矩形範囲なのだが、Snow Leopardの 64bit化に伴い 32bitアプリでこの値を使うと「無限相当」ではなく「非常に大きな範囲」として認識されてしまうのが原因とのこと。つまり 32bitでは無限と見なせていた大きな値が、64bit化によってそこまで大きな値ではなくなったということらしい。その結果 CGWindowListCreateImage() はその巨大な矩形範囲の CGImage を生成してしまおうとする。先のエラーの場合、巨大すぎるが為に CGImage の生成に失敗したようだ。

確かに自分のMacではキャプチャで固まることは無いのだが(Leopardの時よりも)長い時間がかかる。この場合は、時間はかかったが運良く(運悪く?)画像の生成に成功していた為に問題が出ていなかったようだ。


対策は先の記事にも掲載してあった通り CGRectInfinite を使わず明示的に範囲指定すること。
(例)
NSRect desktopRect = NSZeroRect;
for (NSScreen *screen in [NSScreen screens])
{
desktopRect = NSUnionRect(desktopRect, [screen frame]);
}



SimpleCap の場合はマルチスクリーンに対応するため、専用のクラス Screen を用意しており、そこから上記と同じ範囲値を取得できる。
(旧)
CGImageRef cgimage = CGWindowListCreateImage(CGRectInfinite,
option,
[_capture_controller windowID],
kCGWindowImageDefault);


(新)
CGRect cgrect = NSRectToCGRect([[Screen defaultScreen] frame]);
CGImageRef cgimage = CGWindowListCreateImage(cgrect,
option,
[_capture_controller windowID],
kCGWindowImageDefault);



32bit->64bit移行期に発生する不具合はまだまだありそうな気もする。


※上記修正を加えた 1.1.0 は今月リリースの予定です。少しお待ちください。


- - - -
(Special Thanks )
今回の件は 感じ通信 さんからの報告で発覚しました。ありがとうございました!