PenSizeViewですが、筆先のような感じで、尖った方を選ぶと細い線、丸まった方を選ぶと太い線が描けるようにします。
用意するdelegateプロトコルはこんな感じ。delegateプロトコル?な人はモーダルビューを表示する(2)を読みましょう。
inSizeが0.0~1.0の範囲で値を指定してくるので、これを元にdelegate継承側は描く線の幅を1ピクセルから30ピクセルといった好きな範囲で変化させればいいわけですわ。
あと、PenColorViewで選択された色に変化するようにしたいっすね。
こんなふうに、赤だったのが指の移動に合わせて色が変わっていくようにしたいわけっす。
そのためにPenSizeViewクラスには以下のメソッドも用意します。
でもって、今あるPenColorViewDelegateのPenColorView:color:メソッドで通知された色に背景色変えてるのをやめて、このメソッドを呼び出すようにするわけです(ここではpenSizeViewってのがPenSizeViewのインスタンスね)。
ま、呼ぶ方は簡単なんですが、こいつをどう実装するか?
筆先の絵を変えずに色だけ変えるわけですが…
一番よさげなのは
方法じゃないかな~とか思うんですが、今回は勉強がてら
てのをやってみます。
画像部分だけを指定された色で塗りつぶして描画できれば
こうなるはず。
これなら画像部分をフルカラーで変更できるしね。なにかと応用できそう。
これは、その(175)のアルファチャネル付き画像の作り方を使って
ってするのが、てっとりばやいんですが、すでにあるアルファチャネル付き画像からアルファチャネルを残したまま画像だけ変更する方法に興味がわいたんで実験してみました。
まずは画像がiPhoneのメモリー上でどう管理されてるかというところから…
さてさて。
コンピュータの画面は格子状に配置した細かい点の集まりで作られとります。
この点を画像(ピクチャー)の細胞(セル)ということでピクセルと呼んでるわけですが、この画面上のピクセルをメモリー上でどういう風にiPhoneが認識しているかというと、結局コンピュータなんで数字しかわからんわけで、数字で表現しとります。
どういう数字かというと、色の三原色、赤、緑、青のそれぞれの強度を表した値ってことになります。
強度は0、1が一番単純だけど、さすがにそれじゃ諧調が得られないんで普通0~255で表現します。
なんで0~255かというとメモリ上の1バイトで表現できる最大の範囲だから。
1バイトってのはパソコンのうたい文句「搭載メモリ1GB」とかで使われてるBという単位です。
256諧調以上の諧調を見分けられる人もそうそういないので実用的な範囲でもあるわけっす。
赤、緑、青は英語のRed,Green,Blueから略してRGBって表記するのが一般的。
で、今回のアルファチャネル付き画像だとどうなってるかというと、R,G,Bに続けてマスク情報のαを持つようにしてるわけですな。マスク情報が?の人はその(175)を読みましょう。
このメモリ上のα値を変更せずにRGBの値だけ変更できれば、今回のミッション、コンプリートなわけですわ。
で、まずはこの画像データ(RGBの数値の配列)のメモリー上の保存場所情報を得ないとどうしようもないんですが、こいつはUIImageでは取り出せません。UIImageが持ってるCGImageRefから取り出す必要があります。
仮にUIImageインスタンスをimageという変数名だとすると以下のようにして取り出せます。
で、こいつから以下の方法で、画像データを管理するCGDataProviderRefてのが取り出せるんですな。
最初に思いついたのが、このCGDataProviderRefを使い新しいCGContextRefを作りそこに描画する方法。
見つかりませんでした。orz…
CGDataProviderRefからCGImageRefは作れるみたいんなんだけどね~。
で、しょうがないのでCGBitmapContextCreateを使うことにしたんですが、この場合、CGDataProviderRefじゃなく、画像データの先頭アドレスを指定する必要があるんですわ。
今までnil指定してシステムにお任せだったんですが、今回は、このデータを使えと指定するわけです。
探したんですが、画像データそのものを渡す方法も見つかりませんでした。
ふう。
結局、以下の方法で画像データ全体のコピーを受け取れるみたいで
こいつを使って
とすることで、新しいCGContextRefを作りました。CFDataGetBytePtrてのがCFDataRefが管理してるデータの先頭を返させるってAPI(Application Program Interface、システムが用意した関数ね)です。
ここで使われてるwidth、height、bitsPerComponent、bytesPerRow、colorSpaceをどうやって得るかというとCGImageRefから専用のAPIを使って取り出します。
width、heightは省略して、その他を軽く説明。
試しに、できあがったCGContextRefに円を描画してみた。
で、表示するために今度はこの複製した画像データからUIImageを作る必要があるんだけど、こいつはCFDataRefを元に新しいCGDataProviderRefを作って、それを元にCGImageRefを作成ってUIImageを作成って手順を踏みました。この詳細は次回。
とりあえず、そんな感じでやってみた結果がこれ。
ごらんのありさまだよ。
どうも、描画するとアルファチャネルも変更されるみたいっす。
アルファチャネルを変更せずにRGB部分だけ書き換えるのは無理みたい。
しょうがないので、もう一つRGB画像のCGContextRefを作って、そこに描画して、そのRGB値を(アルファチャネルをはぶいて)コピーすることにしました。workDataってのがもう一つ作ったRGB画像データの先頭ね。
う~ん、完全、決めうちですな。
アルファチャネルがRGBの後ろじゃなく前に置かれてた場合とか、チェックせずにやっちゃってます。たしか実機とシミュレータじゃRGBの順が変わったりしてたんだよね~。
ここらへんは今後の課題としましょう。
処理の流れとしてはこんな感じ。
作った後で思ったけど、これならアルファチャネルだけ取り出してマスク用グレー諧調のCGImageRefを作ってその(175)でやったように毎回CGImageCreateWithMaskで作る方がスマートだよね。
ま、そこらへんも今回の調査で実現可能ですんで、我と思わん人はチャレンジしてみて下さい。
でわでわ~。
------------
サンプルプロジェクト:color03.zip
用意するdelegateプロトコルはこんな感じ。delegateプロトコル?な人はモーダルビューを表示する(2)を読みましょう。
@protocol PenSizeViewDelegate
-(void)penSizeView:(PenSizeView*)view size:(double)inSize;
@end
-(void)penSizeView:(PenSizeView*)view size:(double)inSize;
@end
inSizeが0.0~1.0の範囲で値を指定してくるので、これを元にdelegate継承側は描く線の幅を1ピクセルから30ピクセルといった好きな範囲で変化させればいいわけですわ。
あと、PenColorViewで選択された色に変化するようにしたいっすね。
こんなふうに、赤だったのが指の移動に合わせて色が変わっていくようにしたいわけっす。
そのためにPenSizeViewクラスには以下のメソッドも用意します。
-(void)setPenColor:(UIColor*)inColor;
でもって、今あるPenColorViewDelegateのPenColorView:color:メソッドで通知された色に背景色変えてるのをやめて、このメソッドを呼び出すようにするわけです(ここではpenSizeViewってのがPenSizeViewのインスタンスね)。
-(void)penColorView:(PenColorView*)view color:(UIColor*)color
{
self.view.backgroundColor = color;
[penSizeview setPenColor:color];
}
{
[penSizeview setPenColor:color];
}
ま、呼ぶ方は簡単なんですが、こいつをどう実装するか?
筆先の絵を変えずに色だけ変えるわけですが…
一番よさげなのは
PNG-8形式のUIImageを用意してパレットの値を指定された色に変える
方法じゃないかな~とか思うんですが、今回は勉強がてら
アルファチャネル付きPNGでアルファチャネル側に筆先の形状を用意しておき、画像部分だけを指定された色で塗りつぶす。
てのをやってみます。
画像部分だけを指定された色で塗りつぶして描画できれば
こうなるはず。
これなら画像部分をフルカラーで変更できるしね。なにかと応用できそう。
私はアルファチャネル付きPNGファイルはPhotoshopやIllustratorで作っとります。ただPhotoshopは高け~ので、Xccの最初に宣言した「野心ぱんぱんの学生さんに送る」という趣旨に反するんで、そこらへんのフォロー。
ただでアルファチャネル付きPNGファイルを作るには
Photoshopのかわりになるフリーソフト10選
や
お気に入りのアプリケーション検索 for Mac OS X>グラフィック・レタッチ
で自分にあったフリーお絵描きソフト探してみましょう。
GIMPは最近、解説本も出てますな。ただしX11上で動くのでちょい起動がかったるい。
MacユーザーにはChocoFlopの方が評判いいような。
Draw系のDrawitもお手軽にアルファチャネル付きPNGが作れる感じ。ライト版がフリーみたいっす。
ただでアルファチャネル付きPNGファイルを作るには
Photoshopのかわりになるフリーソフト10選
や
お気に入りのアプリケーション検索 for Mac OS X>グラフィック・レタッチ
で自分にあったフリーお絵描きソフト探してみましょう。
GIMPは最近、解説本も出てますな。ただしX11上で動くのでちょい起動がかったるい。
MacユーザーにはChocoFlopの方が評判いいような。
Draw系のDrawitもお手軽にアルファチャネル付きPNGが作れる感じ。ライト版がフリーみたいっす。
これは、その(175)のアルファチャネル付き画像の作り方を使って
筆先の形状を描いたグレー諧調のPNG画像を用意、マスク側画像に指定
指定された色で全面を塗りつぶした画像を用意、上記マスク側画像と合わせてマスク付き画像を作成
指定された色で全面を塗りつぶした画像を用意、上記マスク側画像と合わせてマスク付き画像を作成
ってするのが、てっとりばやいんですが、すでにあるアルファチャネル付き画像からアルファチャネルを残したまま画像だけ変更する方法に興味がわいたんで実験してみました。
まずは画像がiPhoneのメモリー上でどう管理されてるかというところから…
さてさて。
コンピュータの画面は格子状に配置した細かい点の集まりで作られとります。
この点を画像(ピクチャー)の細胞(セル)ということでピクセルと呼んでるわけですが、この画面上のピクセルをメモリー上でどういう風にiPhoneが認識しているかというと、結局コンピュータなんで数字しかわからんわけで、数字で表現しとります。
どういう数字かというと、色の三原色、赤、緑、青のそれぞれの強度を表した値ってことになります。
強度は0、1が一番単純だけど、さすがにそれじゃ諧調が得られないんで普通0~255で表現します。
なんで0~255かというとメモリ上の1バイトで表現できる最大の範囲だから。
1バイトってのはパソコンのうたい文句「搭載メモリ1GB」とかで使われてるBという単位です。
256諧調以上の諧調を見分けられる人もそうそういないので実用的な範囲でもあるわけっす。
赤、緑、青は英語のRed,Green,Blueから略してRGBって表記するのが一般的。
で、今回のアルファチャネル付き画像だとどうなってるかというと、R,G,Bに続けてマスク情報のαを持つようにしてるわけですな。マスク情報が?の人はその(175)を読みましょう。
このメモリ上のα値を変更せずにRGBの値だけ変更できれば、今回のミッション、コンプリートなわけですわ。
で、まずはこの画像データ(RGBの数値の配列)のメモリー上の保存場所情報を得ないとどうしようもないんですが、こいつはUIImageでは取り出せません。UIImageが持ってるCGImageRefから取り出す必要があります。
仮にUIImageインスタンスをimageという変数名だとすると以下のようにして取り出せます。
CGImageRef cgImage = image.CGImage;
で、こいつから以下の方法で、画像データを管理するCGDataProviderRefてのが取り出せるんですな。
CGDataProviderRef dataProvider = CGImageGetDataProvider(cgImage);
最初に思いついたのが、このCGDataProviderRefを使い新しいCGContextRefを作りそこに描画する方法。
見つかりませんでした。orz…
CGDataProviderRefからCGImageRefは作れるみたいんなんだけどね~。
で、しょうがないのでCGBitmapContextCreateを使うことにしたんですが、この場合、CGDataProviderRefじゃなく、画像データの先頭アドレスを指定する必要があるんですわ。
CGContextRef CGBitmapContextCreate (
void *data ←こいつ,
size_t width,
size_t height,
size_t bitsPerComponent,
size_t bytesPerRow,
CGColorSpaceRef colorspace,
CGBitmapInfo bitmapInfo
);
void *data ←こいつ,
size_t width,
size_t height,
size_t bitsPerComponent,
size_t bytesPerRow,
CGColorSpaceRef colorspace,
CGBitmapInfo bitmapInfo
);
今までnil指定してシステムにお任せだったんですが、今回は、このデータを使えと指定するわけです。
探したんですが、画像データそのものを渡す方法も見つかりませんでした。
ふう。
結局、以下の方法で画像データ全体のコピーを受け取れるみたいで
CFDataRef data = CGDataProviderCopyData(dataProvider);
こいつを使って
CGContextRef duplicatedContext = CGBitmapContextCreate(
CFDataGetBytePtr(data), width, height,
bitsPerComponent, bytesPerRow, colorSpace,
kCGImageAlphaNoneSkipLast);
CFDataGetBytePtr(data), width, height,
bitsPerComponent, bytesPerRow, colorSpace,
kCGImageAlphaNoneSkipLast);
とすることで、新しいCGContextRefを作りました。CFDataGetBytePtrてのがCFDataRefが管理してるデータの先頭を返させるってAPI(Application Program Interface、システムが用意した関数ね)です。
ここで使われてるwidth、height、bitsPerComponent、bytesPerRow、colorSpaceをどうやって得るかというとCGImageRefから専用のAPIを使って取り出します。
width、heightは省略して、その他を軽く説明。
bitsPerComponent
ピクセルを構成するRGB各要素が何ビットで構成されているかを意味する。1バイトは8ビットで構成されるので、今回の場合8が返されることになる。
ピクセルを構成するRGB各要素が何ビットで構成されているかを意味する。1バイトは8ビットで構成されるので、今回の場合8が返されることになる。
int bitsPerComponent = CGImageGetBitsPerComponent(cgImage);
bitsPerPixel
ピクセル全体は何ビットで構成されているかを意味する。今回は8ビットがRGBα分で8 x 4 = 32ビットとなる。
ピクセル全体は何ビットで構成されているかを意味する。今回は8ビットがRGBα分で8 x 4 = 32ビットとなる。
αが無い場合でも32を返したりする。この場合8ビットが使われずに無駄になっているんだけど、24ビットより32ビットの方がコンピュータにとって扱いやすいためスピード的に有利になったりする。
int bitsPerPixel = CGImageGetBitsPerPixel(cgImage);
bytesPerRow
画像の横1ライン分のデータが、何バイトで構成されているかを意味する。
先頭のメモリから、このバイト数分進んだところが2ライン目の画像データの先頭となる。
今回のRGBα画像なら理論上は
width x 4
となるが、実際にはもう少し大きめのバイト数が設定されている。
これはiPhoneならiPhoneにとって物理的に扱いやすいバイトの切れ目というのがあり、そこに合わせた値となるから。
画像の横1ライン分のデータが、何バイトで構成されているかを意味する。
先頭のメモリから、このバイト数分進んだところが2ライン目の画像データの先頭となる。
今回のRGBα画像なら理論上は
width x 4
となるが、実際にはもう少し大きめのバイト数が設定されている。
これはiPhoneならiPhoneにとって物理的に扱いやすいバイトの切れ目というのがあり、そこに合わせた値となるから。
int bytesPerRow = CGImageGetBytesPerRow(cgImage);
colorSpace
画像の色空間。グレーやCMYKなどいくつかの色空間が存在する。今回はRGB。
画像の色空間。グレーやCMYKなどいくつかの色空間が存在する。今回はRGB。
CGColorSpaceRef colorSpace = CGImageGetColorSpace(cgImage);
kCGImageAlphaNoneSkipLast
こいつは画像データがどういう用途に使われるものかとか、ピクセル各要素の構成順なんかの情報なんですが…
といったように、いろいろな構成順がある
ちょっと特別で、元の画像の情報は
で取り出せるんですが、元の画像から取り出した値をそのまま使うと失敗しました。
とりあえずRGBαを意味してるっぽいkCGImageAlphaNoneSkipLastやkCGImageAlphaPremultipliedLastを使えばいいとは思うんだけど、正確なところは謎。
こいつは画像データがどういう用途に使われるものかとか、ピクセル各要素の構成順なんかの情報なんですが…
といったように、いろいろな構成順がある
ちょっと特別で、元の画像の情報は
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(cgImage);
で取り出せるんですが、元の画像から取り出した値をそのまま使うと失敗しました。
とりあえずRGBαを意味してるっぽいkCGImageAlphaNoneSkipLastやkCGImageAlphaPremultipliedLastを使えばいいとは思うんだけど、正確なところは謎。
試しに、できあがったCGContextRefに円を描画してみた。
で、表示するために今度はこの複製した画像データからUIImageを作る必要があるんだけど、こいつはCFDataRefを元に新しいCGDataProviderRefを作って、それを元にCGImageRefを作成ってUIImageを作成って手順を踏みました。この詳細は次回。
とりあえず、そんな感じでやってみた結果がこれ。
ごらんのありさまだよ。
どうも、描画するとアルファチャネルも変更されるみたいっす。
アルファチャネルを変更せずにRGB部分だけ書き換えるのは無理みたい。
しょうがないので、もう一つRGB画像のCGContextRefを作って、そこに描画して、そのRGB値を(アルファチャネルをはぶいて)コピーすることにしました。workDataってのがもう一つ作ったRGB画像データの先頭ね。
UInt8* dupData = (UInt8*)CFDataGetBytePtr(data);
for (int v = 0; v < height; v++) {
UInt8* dst = dupData + v * bytesPerRow;
UInt8* src = workData + v * bytesPerRow;
for (int h = 0; h < width; h++) {
*dst++ = *src++; // 赤コピー
*dst++ = *src++; // 緑コピー
*dst++ = *src++; // 青コピー
dst++; // αはスキップ
src++; // αはスキップ
}
}
for (int v = 0; v < height; v++) {
UInt8* dst = dupData + v * bytesPerRow;
UInt8* src = workData + v * bytesPerRow;
for (int h = 0; h < width; h++) {
*dst++ = *src++; // 赤コピー
*dst++ = *src++; // 緑コピー
*dst++ = *src++; // 青コピー
dst++; // αはスキップ
src++; // αはスキップ
}
}
う~ん、完全、決めうちですな。
アルファチャネルがRGBの後ろじゃなく前に置かれてた場合とか、チェックせずにやっちゃってます。たしか実機とシミュレータじゃRGBの順が変わったりしてたんだよね~。
ここらへんは今後の課題としましょう。
処理の流れとしてはこんな感じ。
作った後で思ったけど、これならアルファチャネルだけ取り出してマスク用グレー諧調のCGImageRefを作ってその(175)でやったように毎回CGImageCreateWithMaskで作る方がスマートだよね。
ま、そこらへんも今回の調査で実現可能ですんで、我と思わん人はチャレンジしてみて下さい。
でわでわ~。
------------
サンプルプロジェクト:color03.zip