2012年 7月 の投稿一覧

Facebook、Quoraのユーザー獲得チームの秘密 まとめ 心構え編

本記事はFacebook Quoraユーザ獲得チームの秘密というセミナーのまとめ、続編です

前回はOpen Network Labというセミナーの具体的施策を重点的にまとめさせて頂きました。今回は、施策ではなく日々の心構えについて書きたいと思います。

ユーザ獲得に必要な心構えは「量」「最重要」「ときどきクレイジー」ということ

「量をこなす」「最重要なことをやる」、「でもたまにはクレイジー」を「繰り返す」ということだと言っていました。
一言で言うと「最重要なことを最速で」ということでしょうか。

量をこなせば勘が養われる。

 10個のうち、2-3個しか成功しないこともある。失敗も成功も勘を養う上で重要である。
 実際彼に見せてもらったチャートは、3ヶ月以上低空飛行を続けてその後徐々にユーザを獲得していったというチャートでした。
 最初は流出が止まらないかもしれませんが、とにかく粘り強くやっていこう。
 そのために、スピードが大事。
 スピードをつけるには、インパクトの次に重要なのが工数が低いものですぐに確かめられるもの、という施策をとることもある。

最重要なこととKPIの関係

 何が最重要なことか?を見極めることが大事という基本的なことを強調していました。
 例えば、最もトラフィック数のあるところから改善を行う、施策の中でも最も総アクティブユーザ数に結びつく事をやるということ。

 ここで重要なのがKPIの設定。
 facebookでは一人あたりのフレンド数を増やす事が総アクティブユーザ数につながるということを発見したようです。
 だから、そのような仕掛けがfacebook上ではてんこもりになっている。

 インパクトが大きいというのは、KPIのインパクトと僕は考えます。

でも、たまにはクレイジーに

 上記のようにロジカルに、最小限の労力で最大限の成果をというだけではなく、クレイジーな手法も20%入れるようにしている、とのことでした。
 Facebookで行ったロシアでの実験、
 「ロシア人の姓名を1000万個アップして、友人を検索してきた人の検索流入を増やす」というもの。
 人の名前をネットに勝手にアップする、というのは大半の国で倫理的に問題があるようだったので、普通にクレイジーですよね。
 しかし、数千万人のユーザを集める大ヒットの施策だったようです。
 

僕自身彼がなぜ、この手法にいきついたのか?を考えてみた

 彼流の施策選択の方法としては、「賢い人を集めてMTGする」というものでした。
 MTGの中では数百のアイデアが出て、「インパクトが高い、工数が現実的」なものを選ぶことで収束させるようです。

 意外に何が有用かは勘で出し合い、決めるときはロジカルに、といった印象です。
 
 僕の見解としては、彼自身データのアプローチを好むのも一度アイデアだけ出過ぎて収集が突かない状況におちいったのではないでしょうか。
 だから、データを武器・盾にして最重要なものを順位付けするという暗黙のルールを作ることで、チームの収拾がつく、
 というアプローチをとっているように思えます。

参考になるまとめ様

■イベント本家のページ
http://onlabandrewjohns.peatix.com/
■ほかのまとめ様の方々
https://twitter.com/#!/search/%23on_lab
http://blog.takejune.com/archives/52288735.html

「Facebook、Quoraのユーザー獲得チームの秘密」 施策編

Facebook→Twitter→Quoraと渡ったユーザ獲得プロフェッショナルのセミナー

本日(7月26日)に恵比寿のデジタルガレージビルでOpenNetworkLabという組織が主催するセミナーに参加しました。
講師のプロフィール(本家様から情報を抜粋しております。)
≪Andrew Johns氏プロフィール≫
Quora Product Manager – User Growth Team

ユーザの登録数を増やすノウハウだけでなく、サービス全体の向上のノウハウが聞けて本当に楽しかった。
しかも、この手のノウハウは考え方は流通しているが実際の事例が少ない分野。
以下のまとめで皆様に少しでも伝われば幸いです。

ユーザ獲得のキーポイントは、4つの要素からなる総アクティベーションユーザ数

彼は2007年からFacebookでユーザ獲得チームをしてきたが、本場のシリコンバレーでも自分のような職種の人は多くない。
しかし、10年後にはどの企業にも必要になるだろうと予測しているが自分もそう思う。

彼のようなノウハウを持っている人が5年のノウハウを一部でも紹介してくれるのは有り難い!
さて、ユーザ獲得チームがウォッチする数字は、「総アクティブユーザの増える速度」である。
アクティブユーザを増やすには、アクティブユーザ数を構成する4つの要素をハックせよ、とのこと。
具体的に言うと、

 ・登録者を増やす
 ・Churn Rate(あんまり利用していない人)を減らす
 ・リアクティベーションを増やす
・Deactivation(退会)を減らす

である。

※参考までに、こちらのブログ様に構成要素の方程式が掲載されています。

上記の方程式を実現するためには、どうすればいいか?
具体的な施策をここからは紹介していく。

登録者を増やすには、チャネル・ランディングページの最適化

チャネルについてはあまり触れられていなかったが、ランディングしたページをTwitterの例で説明してくれた。
ランディングページに到着した人がログインした人の割合は、以下のように推移した。

・3年前   :15%
・1.5年前  :25%
・最近   :31%

このような改善が得られた施策は、「TOPページに無駄な要素を排除したこと」だと言っていた。
3年前のTwitterの様子がスライドに出ていたが、Twitterの検索ボックスやセレブのアバターなどが表示されていた。
非常ににぎやかでサイトが今よりも流行っているように見えなくもない。
しかし、これが今のページよりも優れていない。

何が悪いか?

にぎやかだが他のページに飛んでいってしまって戻ってこないという現象が起こるのがまずい。
だからログイン前TOPページのコンテンツを極力減らしていく事で、ログイン率31%になったというわけだ。

NiceHack!と僕自身は心の中で思った。
工数を減らしてログイン率を上げられるなんて、すごハックだと思う。

Facebookの「○○さんも使ってます」は20%程度登録率を向上する

これはJust do itだと思う。

流通チャネルによってログイン手段を切り分ける

流通チャネルによって提供するログイン手段を切り分けるにも面白いハックだと思う。

Twitterから流入しているユーザはTwitterのアカウントを持っている、または既にログインしている可能性が高い。
なので、ユーザにとって心理的ハードルが一番低いのは流通チャネルに合わせた登録手段を提供することで登録率、ログイン率は向上するとのこと。
ここは具体的な数字が聞き取れませんでした。。。

最適化の設計図が良さそう

個人的に面白いな、と思ったのはちゃんと最適化の設計図があったこと。
チャネル→ランディングページ→登録→・・・・・というユーザ誘導のサイトマップがあり、どこを最適化していこうかという最適化フローチャートがあった。これは新しく入った人にもすっと共有しやすい。

とりあえず今日はここまで細かい施策を紹介させて頂いた。
次では施策を生み出すためのノウハウだったり、考え方や心構えの部分をまとめさせて頂きます。

続編、Facebook、Quoraのユーザー獲得チームの秘密 まとめ 心構え編も書きました。

参考になるまとめ様

■イベント本家のページ
http://onlabandrewjohns.peatix.com/
■ほかのまとめ様の方々
https://twitter.com/#!/search/%23on_lab
http://blog.takejune.com/archives/52288735.html

電源・ネット使い放題のカフェ、まとめカフェに来ました

PocketWifiを持ってても重宝するカフェ

以前、読書メーターの赤星さんがつぶやいていたので来てみました。
本家サイトはこちら。

カフェよりもwifiが自由に使えることと、飲み放題であることからカフェよりは全然使えると思います。

少人数でちょこっと使うと快適さを実感できる

少人数で打ち合わせをする際には誰かのwifiルータを使ってネットを使うという打ち合わせがよくあると思います。
しかし、全員Wifiルータを使うと回線速度が遅くなったりするので、Wifiルータよりも無線LANのほうが全然快適です。
wifiを使おうと思ってカフェを探す時の問題点は以下の2つで、それを見事に解決してくれます。

・電源を使えるカフェを探す
・探しても空いていないことがある
・Wifiを使えるカフェを探す

少人数で使える理由は上記の理由ですが、料金面から「がっつり」よりも「ちょこっと」、というのがいいと思います。

もちろん、一人でも使えます

一人でもwifiと電源が両方使えるカフェを探すのは面倒です。
そういう場合には、wifiルータとHyperJuiceなどをフル装備しておけば問題ありません。

ただ、そういう変態はあまりいないと思うので一人でも大部分の人には使えると見ていいと思います。

料金面での他施設との比較

料金面でどうか?というと、マックより高くカフェとは良い勝負といった印象です。
長く使うのであれば、4人で数千円違ってくるのでがんばってwifiを使うという選択肢も出てきます。
ですので、ちょこっと寄って使うときがメインな使い道だと思います。

他施設との料金
マクドナルド 100円〜300円 時間無制限 wifiはyahooに加入していれば使える
ルノアール 600円〜800円 時間無制限 wifi無・電源一部有り
まとめカフェ 500円〜1300円 2時間+延長可 電源・wifi保証

改善した方がいいところ

あまり大きな声で打ち合わせができなさそうです。
おしゃれなオープンな雰囲気を醸し出していますが、そのおかげで声が丸聞こえだと思います。
とはいえ、団体様お断りにするとメリットが少なくなってしまうので、無音にしたい人向けにヘッドホンの貸し出しや耳栓の貸し出しをしてもいいと思います。

最後に

電源とwifiを使えるカフェが増えていますが、なかなか両方を少人数チームで使えるところがないので重宝しそうです。

参考までに僕の装備は以下です

PC:Macbook air 11インチ
wifi:Softbankのwifiルータ
外付けバッテリー:無

node.jsでiPhoneやAndroidの実機のUser-Agentを確認する方法

node.jsだと数行のコードを書くだけで、UserAgentもSourceIpも確認できる

iPhoneやAndoridの開発をしていて、User-Agentが必要なときってどうしてもあると思います。
しかし、ネットで探しても最新のは出てこない可能性もあるので、実際自分の実機でやるのが確実です。

むしろ、実機でやらない事のリスクのほうが全然大きいです。
こういう用途ではnode.jsが非常に速くて大好きですね。
以下のコードを書いて、node server.jsみたいなコマンド1発で立てることができます。

さぁ、皆にアクセスしてもらおう

調べたい機種を持っている友達がいれば、アクセスしてもらえるだけで最新のユーザエージェントが手に入ります。
Androidやガラケーみたいに細分化しているUser-Agentを取得するにはいい方法だと思います。

■Good!

[javascript]
var http = require(‘http’);
http.createServer(function (req, res) {

//ソースIPの取得
console.log("address is "+req.connection.remoteAddress);
//User-Agentの取得
console.log("ua is "+JSON.stringify(req.headers[‘user-agent’]));
//他ヘッダー
console.log("headers is "+JSON.stringify(req.headers));
     //レスポンスを返す
res.send("thx");

}).listen(8000);
[/javascript]

変態エンジニアの生態 その1僕が採用したくなる変態エンジニアの生態 その1

今までに会ったエンジニアの中で、変態的にできるエンジニアの生態を散文的に紹介します。
僕も彼らのようになれるよう、なんとかならんかなと日々精進しております。

■プログラミングについて

コードを書いているときにはなにもしゃべらない

黙々、淡々と納期を守る、まさにサムライ。
あまりにも淡々とやりすぎていて簡単なことをやっているように見えるが、客の無理難題に応えている最中だったりする。

大手志向もいるし、ベンチャー志向もいる

プログラミングができれば皆独立の道をいくのがいいのでは、と思うかもしれないが、
コードを書く以外の時間が多くなるのを嫌がるのか営業が嫌いなのか、
独立する人は非常に稀である。

僕の会った中では2パターン。
大手志向の人は僕らが高校で麻雀をやっている間もひらすらやっていて、フレームワークを作ったり研究に就いたりするタイプ。
ベンチャー志向の人はとにかく手が早くて、できたと言ったときの完成度が高い傾向にある。

僕の勝手ではあるが観察した結果は以下。

なぜ速いのか

・着手が速い
・実装方法を聞くとすぐに整理された状態で返ってくる
・いつもトラブルを抱えていないので、精神的にも良好(納期前以外)

なぜ完成度が高いのか

・空いた時間で遊ばずに、問題が起きそうな箇所は未然に防ぐようなことをしている
・コードの書き直しを何度も裏でやっている

メインは軽い言語だと口では言うが、Cも全然書ける

僕みたいな軽い言語から入った人との一番大きな違いは、彼らはC言語が書ける。
なぜかというと、画像処理とかゲームとかそっち系を小学生・中学生あたりから書いている。
彼らに我々からするとちょっと難解な問題でも、彼らに任せれば意外に簡単に解決してくれるのは、この基礎力の違いなんだろうと思う。

HTML・CSSはきれいだが見た目は汚い

ソースコードはきれいなのだが、見た目が残念な場合が多い。
なんとなく分かる気がする。

■趣味について

筋肉好きか、ニコニコ好き

体を鍛えるのがなぜか好きな人種か、ニコニコだとのゲームを変態的に見尽す人種の2種がいるような気がする。
僕みたいに食に走る人間は少ない気がする。
ここは共通項が全く分からないので、どなたか教えて頂きたい。

ローカルファイルがUIWEBViewで読み込まれない場合の対処法 拡張子編

UIWEBViewは拡張子が.comだと読み込まれない

前提

様々なページをWEBから取得する場合に拡張子を得ておきたいと思うのが常だと思います。
拡張子を取るための一般的な解決策は以下のコード。

[objc]
NSURL *url = [NSURL URLWithString:@"http://b.hatena.ne.jp/entry/lab.xxxxx.com/?p=123"];
NSString *extension = [[url path] pathExtension];
[/objc]

しかし、このようなコードでは拡張子が.comになってしまいます。
これをそのままUIWebViewに以下のように読み込ませるコードは以下。
以下のコードでは、リクエストに失敗してエラーが出力されます。
(エラーを取得するデリゲートメソッド:webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error)

[objc]
UIWebView *webView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
[webView setDelegate:self];
NSString *bodyPath = @"/テキトーパス/test.com";
NSFileManager *fm = [NSFileManager defaultManager];
GHAssertTrue([fm fileExistsAtPath:bodyPath],@"body path is not exited");
NSURLRequest *req = [NSURLRequest requestWithURL:[NSURL fileURLWithPath:bodyPath]];
[/objc]

解決策

拡張子を.htmlに書き換えれば何も問題なくロードされます。
もし、GHTestUnitを利用している方には、以下のコードをコピってみると、ちょこっと面白いです。

■エラー内容

{NSErrorFailingURLKey=file:///*********/test.com, NSErrorFailingURLStringKey=file:///Users/pig100pork/Desktop/tmp/loadRequest/test.com, NSLocalizedDescription=Frame load interrupted}
2012-07-23 13:20:56.619 GablielTest[7624:15e03] *** WebKit discarded an uncaught exception in the webView:didFailProvisionalLoadWithError:forFrame: delegate: ‘((error) == nil)’ should be FALSE. err not nil. err:Error Domain=WebKitErrorDomain Code=102 “Frame load interrupted” UserInfo=0x99e0140 {NSErrorFailingURLKey=file:///*******/test.com, NSErrorFailingURLStringKey=file:///*****loadRequest/test.com,

■bad

[objc]

-(void)test1_loadRequestTest{

UIWebView *webView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
[webView setDelegate:self];
NSString *bodyPath = @"/テキトーパス/test.com";
NSFileManager *fm = [NSFileManager defaultManager];
GHAssertTrue([fm fileExistsAtPath:bodyPath],@"body path is not exited");
NSURLRequest *req = [NSURLRequest requestWithURL:[NSURL fileURLWithPath:bodyPath]];
GHAssertTrue([[req URL] isFileURL],@"url is not fileUrl");

[self prepare];

[webView loadRequest:req];
[self waitForStatus:kGHUnitWaitStatusSuccess timeout:60];

}

-(void)webViewDidStartLoad:(UIWebView *)webView{

NSLog(@"start");
GHAssertNotNil([[webView request] URL],@"webView req is nil");
}

-(void)webViewDidFinishLoad:(UIWebView *)webView{

NSLog(@"finish");
GHAssertNotNil([webView request],@"webView req is nil");
[self notify:kGHUnitWaitStatusSuccess forSelector:@selector(test1_loadRequestTest)];
}

-(void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error{

NSLog(@"failure");
NSLog(@"error is %@",[error description]);
GHAssertNil(error,@"err not nil. err:%@",[error description]);
[self notify:kGHUnitWaitStatusFailure];
}
[/objc]

■Good

[objc]
-(void)test1_loadRequestTest{

UIWebView *webView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
[webView setDelegate:self];
NSString *bodyPath = @"/テキトーパス/test.html";
NSFileManager *fm = [NSFileManager defaultManager];
GHAssertTrue([fm fileExistsAtPath:bodyPath],@"body path is not exited");
NSURLRequest *req = [NSURLRequest requestWithURL:[NSURL fileURLWithPath:bodyPath]];
GHAssertTrue([[req URL] isFileURL],@"url is not fileUrl");

[self prepare];

[webView loadRequest:req];
[self waitForStatus:kGHUnitWaitStatusSuccess timeout:60];

}

-(void)webViewDidStartLoad:(UIWebView *)webView{

NSLog(@"start");
GHAssertNotNil([[webView request] URL],@"webView req is nil");
}

-(void)webViewDidFinishLoad:(UIWebView *)webView{

NSLog(@"finish");
GHAssertNotNil([webView request],@"webView req is nil");
[self notify:kGHUnitWaitStatusSuccess forSelector:@selector(test1_loadRequestTest)];
}

-(void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error{

NSLog(@"failure");
NSLog(@"error is %@",[error description]);
GHAssertNil(error,@"err not nil. err:%@",[error description]);
[self notify:kGHUnitWaitStatusFailure];
}

[/objc]

アスクルがヒットした理由は価値の設計にあったと思う カンブリア宮殿より 

2012年7月19日、カンブリア宮殿の「アスクル創業話」を見ました。
小池栄子と村上龍が出ている番組です。

マーケットの穴を見抜く目と、障壁に立ち向かっていく実行力に感動したため、
僕が感じたアスクルのヒットした理由を簡潔にまとめさせて頂きました。
例えるなら、高校の頃に習ったニュートンの方程式のようです。
F=ma

アスクルとは?

アスクルは法人向けのカタログ通販会社です。
文房具やバインダー、電卓に消臭剤などオフィスに必要な物をカタログから選んで注文するだけで、当日あるいは翌日に届きます。
創業時のアスクルはプラス株式会社という数百億円売り上げていた文房具メーカーで、文房具屋におろしていました。

何が革新的なのか?

僕の中で革新的だと思った点は顧客の選択が一番革新的だったと思いました。

なぜ、中小企業を選ぶ事が革新的なのか?

中小企業を選ぶと、手間がかかって仕方がないというのがBtoBの常識だったりします。
だから、当時のマーケットは以下のようになっていました。

・大企業の法人
 大量な発注ができる大企業は卸から「安い価格で、出かけなくても届ける」というサービスレベルでした。

・個人
 家できれたら町の文房具屋で買ってました。

・中小企業
 発注量がそこまででもない中小企業は個人と同じように、「定価で、女性が在庫のある店を探し歩いて」買ってました。

中小企業、大変ですよね。
ここで岩田社長が目を付けたのが、中小企業が大企業と同じように「安い価格で出かけなくてもオンデマンドで買える」を実現すれば、
中小企業の人達が買ってくれるのではないかと。

(実は現代でもIT産業もこういう構造になってます。
 某メーカーさんは○億以下は子会社、○千万以下は話を聞きもしない。)

待ち受けていた障壁

ここまでは考えついた人は多いと思うのですが、行動に移した岩田社長は本当にすごい。

従来のお客様からの圧力

メーカーから小売りへ立場が変わった事により、文房具店と競合する。
数百億売上を上げていた従来のお客様からクレームが来るのは必至だったと思ってます。

社内からの圧力

小売りはメーカーにこだわらず、お客様がほしがる売れ筋をそろえる必要があります。
当時競合だったコクヨの商品を取り揃えることで、普段コクヨと場所取り合戦を強いられている営業マンからクレームが来るというのも、
これも必至だったと思います。
さらに、今まで扱ってくれてた文房具店も扱わなくなった店もあるでしょうから、さらなりという感じです。

配送網の整備

文房具がすぐ切れたときにでも届けられるよう、オンデマンドに届けることが理想だと思ってます。
当時、通販のノウハウも少ないところから翌日配送をするのはとても大変だったと思います。

まとめ

・大企業の人は厚遇を受けていたが、中小企業の人はそうでもなかったため、ここにマーケットの空白があった。
・しかも、中小企業は全体の95%で見捨てられた割にはでかいマーケットであった。

美しいすぎる。
ボリュームと空白が一致するのはそうそうないですよね。

おまけ

マーケットの空白と番組では紹介されていましたが、ITの起業家を集めたFounders at workにも同じような内容が載っていたので、のせさせて頂きます。

「優れたテクノロジーを組み立てて、それがどちらに向かっていくか様子を見る」というスタンスを私はとったことはありません。
テクノロジーは多かれ少なかれ市場にぽっかり空いている穴、もっと正確に言えば将来の市場に開いている穴が何かを直感的につかむところから(アイデアは)生まれます。

by レイ・オジー
(LotusNotesの開発者。microsoft のビルゲイツの跡を継いだ主席ソフトウェア設計者となる。)

最後に

本当に美しい、顧客への価値設計ですよね。
頭の良さだけではなく、困っている人がいたらそのためにがんばる、マーケットの穴を見つけてそこに身を投じる社長の姿勢にも感動。
今日、僕は「星野さんはくだらないことが好きなとこがいい」と言われましたが、僕も自分のそういうところが気に入ってます。

僕が信じているマーケットの空白を埋めるには、この機能の精度がなくちゃダメなんだ、
アスクルだったら明日じゃなくて三日後でもいいじゃない?競合いないからなんて思ってはダメなんだ、
なんて勝手に思ってます。

カンブリア宮殿、今日は本当によかった!

Objective-C CGColorRef CGColorSpaceCreateDeviceRGB()が見つからないときの対処法

Objective-C CGColorRef CGColorSpaceCreateDeviceRGB()と言うコードをよく見ますが、iosではdeprecatedと言われます。
その場合は、以下のように書く事で解決されます。
前回のUIWebViewでスクリーンショットを取るで紹介したコードの中で、
UIWEBViewのピクセル色を取得するために使っています。

■Good for ios

[objc]
CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(CGColorSpaceCreateDeviceRGB());
[/objc]

■Bad for ios

[objc]
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
[/objc]

iOSでUIWebViewのスクリーンショットを取る方法

■問題

iOSでisLoadingが常にYESを返す場合があり、完全にロードしたかどうかを判別できない場合があります。

■解決策

webViewDidFinishLoadが呼ばれた時点で、スクリーンショットを取り、白・黒の割合が90%未満だった場合にはロードされていると認識する。
※この方法では完全にとれない場合もあります。
※100%ロードしたものを表示したい場合には、timer等で以下の関数を処理を実行させて下さい。

■予備知識

UIViewからサムネイルを取得する方法
http://araking56.blog134.fc2.com/blog-entry-184.html

画像のピクセルから色を判別する方法
http://www.markj.net/iphone-uiimage-pixel-color/

■good

[objc]
– (void)webViewDidFinishLoad:(UIWebView *)webView {

BOOL isLoaded = [self isLoaded:webView];
if(isLoaded){
[self.imageView setHidden:NO];
}
}

-(BOOL)isLoaded:(UIWebView *)webView{

//画像を送る
CGRect screenRect = webView.frame;
UIGraphicsBeginImageContext(webView.frame.size);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextFillRect(ctx, screenRect);
[webView.layer renderInContext:ctx];

UIImage *screenImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

//画像の内容を解析する
float percentage = [self getPercentageOfWhiteInImage:screenImage];

if (percentage > 0.9) {

NSLog(@"ほぼ真っ白 or 真っ黒なのでロードされていない");

}else{

NSLog(@"色がついているのでロード済み");

}
}

-(float)getPercentageOfWhiteInImage:(UIImage *)image{

CGImageRef inImage = image.CGImage;
CGContextRef cgctx = [self createARGBBitmapContextFromImage:inImage];
if (cgctx == NULL) { return 1.0; /* error */ }

size_t w = CGImageGetWidth(inImage);
size_t h = CGImageGetHeight(inImage);
CGRect rect = {{0,0},{w,h}};
CGContextDrawImage(cgctx, rect, inImage);

int startW = 0;
int startH = 0;
unsigned char* data = CGBitmapContextGetData (cgctx);

float result1 = [self getPercentageOfWhiteInPix:startW startH:startH data:data w:w h:h];
float result2 = [self getPercentageOfWhiteInPix:w/2-_estimateLengthstartH:h/2-_estimateLengthdata:data w:w h:h];
float result3 = [self getPercentageOfWhiteInPix:w-_estimateLengthstartH:h-_estimateLengthdata:data w:w h:h];

// When finished, release the context
CGContextRelease(cgctx);
// Free image data memory for the context
if (data) { free(data); }

return (result1+result2+result3)/(float)3;

}

-(float)getPercentageOfWhiteInPix:(int)startW startH:(int)startH data:(unsigned char *)data w:(size_t)w h:(size_t)h{

// Now we can get a pointer to the image data associated with the bitmap
// context.
int whiteCounter =0;
int blackCounter = 0;
int notWhiteCounter = 0;
//unsigned char* data = CGBitmapContextGetData (cgctx);
for (int width =0; width < _estimateLength ; width++) {

for (int height=0;height < _estimateLength; height++) {

if (data != NULL) {

//offset locates the pixel in the data from x,y.
//4 for 4 bytes of data per pixel, w is width of one row of data.
int offset = 4*((w*round(startH+height))+round(startW+width));
int alpha = data[offset];
int red = data[offset+1];
int green = data[offset+2];
int blue = data[offset+3];

if (red == 255 && green == 255 && blue == 255) {
whiteCounter++;
}else if(red ==0 && green==0 && blue==0){
blackCounter++;
}else {
notWhiteCounter++;
}
NSLog(@"offset: %i colors: RGB A %i %i %i %i",offset,red,green,blue,alpha);
//color = [UIColor colorWithRed:(red/255.0f) green:(green/255.0f) blue:(blue/255.0f) alpha:(alpha/255.0f)];

}

}

}

//大きい方を返す

if (whiteCounter > blackCounter) {

return (float)whiteCounter/(float)(notWhiteCounter+whiteCounter+blackCounter);

}else {

return (float)blackCounter/(float)(notWhiteCounter+whiteCounter+blackCounter);

}

}

– (CGContextRef) createARGBBitmapContextFromImage:(CGImageRef) inImage {

CGContextRef context = NULL;
CGColorSpaceRef colorSpace;
void * bitmapData;
int bitmapByteCount;
int bitmapBytesPerRow;

size_t pixelsWide = CGImageGetWidth(inImage);
size_t pixelsHigh = CGImageGetHeight(inImage);

bitmapBytesPerRow = (pixelsWide * 4);
bitmapByteCount = (bitmapBytesPerRow * pixelsHigh);

colorSpace = CGColorSpaceCreateDeviceRGB();
if (colorSpace == NULL)
{
fprintf(stderr, "Error allocating color space\n");
return NULL;
}

bitmapData = malloc( bitmapByteCount );
if (bitmapData == NULL)
{
fprintf (stderr, "Memory not allocated!");
CGColorSpaceRelease( colorSpace );
return NULL;
}

context = CGBitmapContextCreate (bitmapData,
pixelsWide,
pixelsHigh,
8, // bits per component
bitmapBytesPerRow,
colorSpace,
kCGImageAlphaPremultipliedFirst);
if (context == NULL)
{
free (bitmapData);
fprintf (stderr, "Context not created!");
}

// Make sure and release colorspace before returning
CGColorSpaceRelease( colorSpace );

return context;
}
[/objc]

node.js ejs にてpartial HTMLがエスケープされて出力される場合の対処法

今回はrailsのpartial的な使い方をEJSを使って行う方法を紹介するとともに、
HTMLが正しくエスケープされなかった場合の対処法を紹介します。

■正しくエスケープされない場合の対処法

(エラーになった人向け)
<%=body %>ではエスケープされないので、<-%body %>を使います。

[javascript]
<%-body %>
<div style="padding-top:30px;padding-bottom:30px;">http://<%=host %>/</div>
[/javascript]

■テンプレートを利用してメールを送る方法

「node.jsでHTMLメールを送る」では、EJSを使ってHTMLを送る方法を紹介しています。

■partialを埋め込む方法

フッターをつけたlayout.ejsに、partialで「今日は特売日!」な文章を埋め込みます。

[javascript]

var fs = require("fs");
var ejs = require("ejs");

fs.readFile("simpleMail.ejs","utf8", function(err,data){

var locals = {"userName":"Yoshihiko Hoshino"};
var partialResult = ejs.render(data,{"locals":locals});
fs.readFile("layout.ejs","utf8",function(err,partialData){

var layoutLocal = {"body":partialResult,"host":"shisuh.com"};
var completePage = ejs.render(partialData,{"locals":layoutLocal});
console.log("complete page is "+completePage);

});

});
[/javascript]

layout EJSファイル(layout.ejs)

[javascript]
<%-body %>
<div style="padding-top:30px;padding-bottom:30px;">http://<%=host %>/</div>
[/javascript]

partial EJSファイル(simpleMail.ejs)

[javascript]
<%=userName %>さん、こんにちわ<br><br>
CSSで
<span style="font-size:20px;background-color:yellow;color:red;font-weight:bold;">
スーパーの特売!!
</span>も表現できます。
<br>
[/javascript]

■Good

上記のコマンドのコンソールにHTMLが出力されれば成功です。
[bash]
complete page is Yoshihiko Hoshinoさん、こんにちわ<br><br>

CSSで
<span style="font-size:20px;background-color:yellow;color:red;font-weight:bold;">
スーパーの特売!!
</span>も表現できます。
<br>

[/bash]

■Bad

layout.ejsの<%-body %>を<%=body %>にした場合の出力を紹介します。
フッターはエスケープされずに出力されていますが、partial部分がエスケープされてしまっています。

[bash]
complete page is Yoshihiko Hoshinoさん、こんにちわ&lt;br&gt;&lt;br&gt;

CSSで
&lt;span style=&quot;font-size:20px;background-color:yellow;color:red;font-weight:bold;&quot;&gt;
スーパーの特売!!
&lt;/span&gt;も表現できます。
&lt;br&gt;

[/bash]