2013年2月17日日曜日

[WebGL][JavaScript][HTML] 第1回 WebGLで遊んでみる(描画領域のクリア)

最近OpenGLにハマってる。
やっぱり自分でアニメーションやエフェクト、3Dポリゴンなどをゴリゴリできるのは楽しい。
ブラウザでも実はWebGLというものがある。
まだ対応しているブラウザが多いわけではないがどんどん増えてきている。
また、CSSでもCSS shadersというものが規格されており、
これもWebGL・・・もっといえばOpenGLがベースとなっているのだ。(2013/02/17現在)

もっとも簡単なWebGL、それは描画領域のクリアである。

描画結果(画像)


描画領域をクリアすると上の画像のように一色で塗りつぶされる。
では、早速ソースコードを見ていこう。

まずはHTML部分から。
//link・・・http://mio-koduki.blogspot.jp/2013/02/webgljavascripthtml-webgl.html
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <script src="webgl.js"></script>
    </head>
    <body>
        <canvas id="canvas" width="500px" height="500px" style="width:500px;height:500px;"></canvas>
    </body>
</html>

まず、ポイントはcanvas要素を作ること。
WebGLはこのcanvas要素に描画されることになる。
なので、このcanvasにidとサイズを設定してあげる。

次にJavaScript側。
//link・・・http://mio-koduki.blogspot.jp/2013/02/webgljavascripthtml-webgl.html
//デバッグ用フラグ
var DEBUG_FLAG=true;

//WebGLのコンテキスト
var gl=null;

var main=function()
{
    //canvas要素を取得する
    var canvas=document.getElementById('canvas');
    //getContextの引数を配列で用意
    var tryContext=
    [
        'webgl'
        ,'experimental-webgl'
        ,'webkit-3d'
        ,'moz-webgl'
    ];
    //順番にgetContextを実行しWebGLのコンテキストを取得を試みる
    for(var i in tryContext)
    {
        gl=canvas.getContext(tryContext[i]);
        //WebGLのコンテキスト取得に成功したらループを抜ける
        if(gl)
        {
            break;
        }
    }
    //WebGLのコンテキストを取得できなかったらコンソールにメッセージを出しreturnでmain関数を抜ける
    if(!gl)
    {
        console.log('no gl');
        return;
    }
    //RGBAの順番にカラーバッファをクリアする色を指定する
    gl.clearColor(0.5,0.5,0.5,1.0);
    //カラーバッファをクリアする
    gl.clear(gl.COLOR_BUFFER_BIT);
    //clearメソッドのエラーチェック
    checkGLError('clear');
};
//ページロード時にmain関数実行
window.onload=main;

var checkGLError=function(errorString)
{
    //デバッグ時のみチェック
    if(DEBUG_FLAG)
    {
        var noError=gl.NO_ERROR;
        var error=noError;
        //エラーを取得しNO_ERROR以外だったらコンソールにメッセージを出す
        while((error=gl.getError())!=noError)
        {
            console.log(error+" : "+errorString);
        }
    }
};

では、詳しく見ていこう。


まず、2行目~3行目にデバッグ用のフラグを用意している。
//デバッグ用フラグ
var DEBUG_FLAG=true;
WebGLに限らずプログラムを書くときにおいてこれは用意しておいたほうがいい。
特にWebGLと言うよりOpenGLは処理を高速化させるために、
本番ではエラーチェック処理を入れないということもしばしばある。
他にもデバッグの時だけしたい処理や逆にプロダクションだけでしたい処理なども出てきたりする。
用意して損はないだろう。


次に5行目~6行目にglという変数を用意している。
//WebGLのコンテキスト
var gl=null;
これはWebGLのコンテキストを代入するのに使う。
WebGLのコンテキストはWebGLの処理をするのに必ず必要な物なので、
グローバル変数として用意している。
なお、実際はあまりグローバル変数は好まれないので、
実際に自分で書くときはクラスのメンバ(JavaScript的にはプロパティ)などとして持たせるといいだろう。
今回はソース簡略化のためにグローバル変数にしている。


そして、8行目、次にちょっと飛んで44行目。
var main=function()
{
    /*
    省略
    */
};
//ページロード時にmain関数実行
window.onload=main;
main関数を定義し、ページがロードされた時に呼び出されるようにwindow.onloadに代入する。
こうすることによってDOMや画像が用意され終わった段階で、このmain関数を実行してくれるようになる。


次にmain関数の中を見ていこう。まず、10~11行目。
//canvas要素を取得する
var canvas=document.getElementById('canvas');
HTMLで用意しておいたcanvas要素を取ってくる。
id属性でcanvasと指定したものをとるというソースコードだが、
今回はcanvas要素自体に"canvas"というidを指定してるので、
結果的にcanvas要素が取れる。


さて、ようやくWebGLの第一歩、12行目~29行目。
//getContextの引数を配列で用意
var tryContext=
[
    'webgl'
    ,'experimental-webgl'
    ,'webkit-3d'
    ,'moz-webgl'
];
//順番にgetContextを実行しWebGLのコンテキストを取得を試みる
for(var i in tryContext)
{
    gl=canvas.getContext(tryContext[i]);
    //WebGLのコンテキスト取得に成功したらループを抜ける
    if(gl)
    {
        break;
    }
}
先ほど取得したcanvasのgetContextメソッドを呼びWebGLのコンテキストを取得するのだが、
これらはブラウザや実装状況によって引数の指定が異なる。
故に予め配列で引数の文字列を用意して、ループで回しWebGLのコンテキストが取得できたら、
ループを抜けるようにしている。


そして30行目~35行目。
//WebGLのコンテキストを取得できなかったらコンソールにメッセージを出しreturnでmain関数を抜ける
if(!gl)
{
    console.log('no gl');
    return;
}
WebGL未対応ブラウザだったりなにかしらのエラーやミスで、
WebGLのコンテキストが取得できなかった場合は素直にコンソールにメッセージを出し、
returnしてmain関数を終了している。


36行目~37行目にようやくWebGLのメソッドが登場!
//RGBAの順番にカラーバッファをクリアする色を指定する
gl.clearColor(0.5,0.5,0.5,1.0);
clearColorメソッドを呼び出し、
RGBAの順番にカラーバッファをクリア・・・つまり塗りつぶす色を指定する。
カラーバッファは描画領域のRGBA用バッファとして使われる。
つまりはカラーバッファをクリアすることによって描画領域もクリアできるのだ。
なお、色を指定するときはカラーコードでお馴染みの0~255ではなく、0.0~1.0である。


38行目~39行目。
//カラーバッファをクリアする
gl.clear(gl.COLOR_BUFFER_BIT);
clearメソッドを呼び出し、clearColorメソッドで指定した色を使って、
実際にカラーバッファをクリアする。


さて、40行目~41行目に何か関数がある。
//clearメソッドのエラーチェック
checkGLError('clear');
実はWebGLは・・・というかOpenGLは返り値や例外でエラーをキャッチするのではなく、
都度関数を呼んでエラーチェックを行う。
そのためそれらの処理を自前で関数として用意しまとめている。


それが46行目~59行目だ。
var checkGLError=function(errorString)
{
    //デバッグ時のみチェック
    if(DEBUG_FLAG)
    {
        var noError=gl.NO_ERROR;
        var error=noError;
        //エラーを取得しNO_ERROR以外だったらコンソールにメッセージを出す
        while((error=gl.getError())!=noError)
        {
            console.log(error+" : "+errorString);
        }
    }
};
引数は直前に呼び出したメソッド名。
関数の中で早速行なっているのはデバッグ用のフラグがtrueかどうかのチェックだ。
その理由は少し下のコードにある。
getErrorメソッドを呼び出し、NO_ERRORかどうかを見ている。
もしNO_ERRORでなかったらエラーの回数だけループするわけだが、
その処理をエラーチェックが必要な関数の都度することになる。
多少なら無視出来るレベルだが、ゲームやアニメーションを行う際には、
この多少が積み重なり処理速度を低下させるうる。
故にデバッグの時だけエラーチェックをしておき本番では行わないようにする。
今回はNO_ERRORではないとき、つまりエラーのときはコンソールに、
この関数の引数として渡した直前のメソッド名を、メッセージに含めて出している。
これによって何のメソッドでエラーが起きたかわかる。


WebGLに対応しているブラウザで上記のソースコードを実行させると、
描画結果の画像のようにcavas内全体が灰色に塗りつぶされて表示される。
なぜならカラーバッファを灰色でクリアすることにより結果、描画領域も灰色でクリアされたからだ。
OpenGLひいてはWebGLではエラーの時真っ黒に表示される。
その場合はこのカラーバッファクリアも効かずに真っ黒になるので、
黒以外でクリアしておくとエラーが起きてるのかパラメータミスなのかの問題の切り分けがしやすい。
なので、デバッグのときは黒以外の色でクリアしておくといいかもしれない。


第2回 WebGLで遊んでみる(点の描画)へ

2012年11月16日金曜日

[PHP] PHPで特定のURLのHTTPステータスコードを取得する

リンク集のリンクが残念ながらサイト閉鎖や、URL変更などで404になることがある。
10個ぐらいなら手でチェックしてもいいが、100個、200個となるとさすがにうんざりする。
プログラムで調べるには、そのURLのHTTPステータスコードが404になるかどうか調べればよい。
なので、PHPでURLのHTTPステータスコードを返す関数をちょちょっと書いてみた。

//link・・・http://mio-koduki.blogspot.jp/2012/11/php-phpurlhttp.html
//第一引数・・・HTTPステータスコードを調べたいURL
//返り値・・・成功したらそのURLのHTTPステータスコード、失敗したらfalse
function get_http_status_code($url)
{
    if(($fp=fopen($url,'r',false,stream_context_create(array('http'=>array('ignore_errors'=>true)))))!==false)
    {
        fclose($fp);
        return preg_match('#^HTTP/\d\.\d (\d+) .+$#',$http_response_header[0],$matches)===1?(int)$matches[1]:false;
    }
    else
    {
        return false;
    }
}

とこんな感じになる。
file_get_contentsの代わりにfopenをつかってるのは、
file_get_contentsの場合はHTTPレスポンスボディを文字列に格納して返してくるので、
多少なりともメモリの消費が多くなるため、明示的に受け取る必要がないfopenを使ってる。
ただ、fopenでもfile_get_contentsでも、そのままだと401や404を返した際に返り値にfalseを返してくる。
それを防ぐために、fopenの第四引数にstream_context_createで作ったストリームコンテキストを渡してる。
この関数に連想配列で['http']['ignore_errors']にtrueを指定することによって
401や404の時にもきちんとファイルポインタリソースを返してくれるようにする。
また、突然初期化してない変数$http_response_headerを使ってるように見えるが、
じつはこれはPHPが自動的に代入してくれる変数で、
fopenなどの関数を使って通信が起こった際に(厳密にはHTTPラッパを使用する際に)、
HTTPレスポンスヘッダを配列で格納してくれる。
後は正規表現でHTTPレスポンスヘッダからHTTPステータスコードを取得して返しているだけである。

また、上と同じであるが、ちょっと見やすく整形したのがこっち。

//link・・・http://mio-koduki.blogspot.jp/2012/11/php-phpurlhttp.html
//第一引数・・・HTTPステータスコードを調べたいURL
//返り値・・・成功したらそのURLのHTTPステータスコード、失敗したらfalse
function get_http_status_code($url)
{
    $options=array
    (
        'http'=>array
        (
            'ignore_errors'=>true
        )
    );
    $fp=fopen($url,'r',false,stream_context_create($options));
    if($fp)
    {
        fclose($fp);
        $pattern='#^HTTP/\d\.\d (\d+) .+$#';
        if(preg_match($pattern,$http_response_header[0],$matches))
        {
            return (int)$matches[1];
        }
        else
        {
            false;
        }
    }
    else
    {
        return false;
    }
}

どちらがいいかはお好みで。

ポイントとなる変数・関数

2012年8月31日金曜日

[PHP][Qdmail] Qdmailで簡単に添付ファイル付きメール送信をする

メールを送信する際に添付ファイルをつけたいときがある。
そのときにも前回紹介したQdmailの出番だ。
高機能日本語PHPメール送信ライブラリ Qdmail
http://hal456.net/qdmail/
添付ファイルをつけて送信する方法も非常に簡単で
関数を使う方法

//件名に「件名」本文に「本文」というテキストメールを「to@example.com」へと「from@example.com」から「photo.png」を添付して送る方法
qd_send_mail
(
    'text',
    'to@example.com',
    '件名',
    '本文',
    'from@example.com',
    'photo.png',
);

とするだけ添付ファイル付きで送ることができる。
また、オブジェクトを使う方法

//件名に「件名」本文に「本文」というテキストメールを「to@example.com」へと「from@example.com」から「photo.png」を添付して送る方法
$mail=new Qdmail();
$mail->to('to@example.com');
$mail->subject('件名');
$mail->text('本文');
$mail->from('from@example.com');
$mail->attach('photo.png');
$mail->send();

として添付ファイル付きで送ることが可能だ。
もちろんどちらの方法でもOK。
このように非常に簡単に添付ファイルを送ることができる。

[PHP][Qdmail] Qdmailで簡単にメール送信をする

メールを送信するというのは要件定義としてよくあることだろう。
例えばショッピングカートなら注文確認メールを送ったり、
タスクスケジューラなら、アラートメールを送ったりなど。

もちろんPHPのデフォルトの関数であるmb_send_mailを使ってメール送信をしてもいいのだが、ちょっと複雑なことをしようとしたら(例えば添付メールなど)結構面倒くさい。
また、エンコードの問題で文字化けすることもあるため、
エンコード周りでも結構神経を使う。

そこでライブラリを使う人も多い。PEARなどが代表的だが、個人的にはQdmailを推したい。
高機能日本語PHPメール送信ライブラリ Qdmail
http://hal456.net/qdmail/
というのも、様々なエンコード問題に対応してくれ、HTMLメール、デコメール、添付ファイル、テンプレート機能などなど・・・。
様々な機能を提供してくれる。さらに使い方も非常に簡単だ。

とりあえず今回は簡単なメールの送信からしてみようと思う。
まず、関数として使う方法

//件名に「件名」本文に「本文」というテキストメールを「to@example.com」へと「from@example.com」から送る方法
qd_send_mail
(
    'text',
    'to@example.com',
    '件名',
    '本文',
    'from@example.com',
);

とするだけ送ることができる。
また、オブジェクトとして使う方法

//件名に「件名」本文に「本文」というテキストメールを「to@example.com」へと「from@example.com」から送る方法
$mail=new Qdmail();
$mail->to('to@example.com');
$mail->subject('件名');
$mail->text('本文');
$mail->from('from@example.com');
$mail->send();

として送ることも可能だ。
この辺は好みがあるだろうが、
オブジェクトを使った方法の方が後々様々な複雑なことができるので、
今のうちになれておくことをお勧めする。
とはいえ、関数を使った方法でも結構複雑なことができるので、
用途や好みに応じて使い分けるのもありだろう。

Qdmailで簡単に添付ファイル付きメール送信をする

2012年8月27日月曜日

[PHP][cURL] PHPのcURLを使ってGoogleにログインする

YAHOO! 知恵袋に(どなたか本当にお願いします!phpのcurlに関して教えて頂きたいです。)というPHPのcURLを使って、Googleにログインする方法が聞かれていたのでちょっと組んでみた。

実はこういうのは意外と面倒くさくて、
POSTデータでIDとパスワードを飛ばせばいいというものではない。
もちろんそれでログインできる(できてしまう)サイトもあるのだが、
セキュリティポリシーの高いサイトではそうはいかない。
不正なログインを防ぐためにフォーム内にトークンを埋め込み、
かつCookieにもそのトークンを埋め込んでおき、
サブミットされた際にフォームから飛んできたPOSTとCookieを比較しているのだ。
ちなみにGoogleとPixivはこの方式を採用している(2012/08/27現在)。

とりあえず早速ソースを見ていこう。

//URLを指定する
$url='https://accounts.google.com/ServiceLoginAuth';
//POST用のデータを作っておく
$data=array
(
    //ID部分(適宜置き換え)
    'Email'=>'',
    //パスワード部分(適宜置き換え)
    'Passwd'=>'',
    //ログインを維持するかのチェックボックス部分
    //'PersistentCookie'=>'yes',
);
//テンポラリファイルを作成する
$cookie=tempnam(sys_get_temp_dir(),'cookie_');

//cURLを初期化して使用可能にする
$curl=curl_init();
//オプションにURLを設定する
curl_setopt($curl,CURLOPT_URL,$url);
//文字列で結果を返させる
curl_setopt($curl,CURLOPT_RETURNTRANSFER,true);
//クッキーを書き込むファイルを指定
curl_setopt($curl,CURLOPT_COOKIEJAR,$cookie);
//URLにアクセスし、結果を文字列として返す
$html=curl_exec($curl);
//cURLのリソースを解放する
curl_close($curl);

//Document初期化
$dom=new DOMDocument();
//html文字列を読み込む(htmlに誤りがある場合エラーが出るので@をつける)
@$dom->loadHTML($html);
//XPath初期化
$xpath=new DOMXPath($dom);
//inputのtypeがhiddenの要素をとってくる
$node=$xpath->query('//input[@type="hidden"]');
foreach($node as $v)
{
    //POST用のデータに追加する
    $data[$v->getAttribute('name')]=$v->getAttribute('value');
}

//cURLを初期化して使用可能にする
$curl=curl_init();
//オプションにURLを設定する
curl_setopt($curl,CURLOPT_URL,$url);
//メソッドをPOSTに設定
curl_setopt ($curl,CURLOPT_POST,true);
//POSTデータ設定
curl_setopt($curl,CURLOPT_POSTFIELDS,$data);
//クッキーを読み込むファイルを指定
curl_setopt($curl,CURLOPT_COOKIEFILE,$cookie);
//Locationをたどる
curl_setopt($curl,CURLOPT_FOLLOWLOCATION,true);
//URLにアクセスし、結果を表示させる
curl_exec($curl);
//cURLのリソースを解放する
curl_close($curl);

//テンポラリファイルを削除
unlink($cookie);

という感じになる。
さて、実際に何をしているか見ていくと

//URLを指定する
$url='https://accounts.google.com/ServiceLoginAuth';
//POST用のデータを作っておく
$data=array
(
    //ID部分(適宜置き換え)
    'Email'=>'',
    //パスワード部分(適宜置き換え)
    'Passwd'=>'',
    //ログインを維持するかのチェックボックス部分
    //'PersistentCookie'=>'yes',
);
//テンポラリファイルを作成する
$cookie=tempnam(sys_get_temp_dir(),'cookie_');

の部分で必要な変数を用意している。
ID部分とパスワード部分は適宜置き換えて欲しい。

//cURLを初期化して使用可能にする
$curl=curl_init();
//オプションにURLを設定する
curl_setopt($curl,CURLOPT_URL,$url);
//文字列で結果を返させる
curl_setopt($curl,CURLOPT_RETURNTRANSFER,true);
//クッキーを書き込むファイルを指定
curl_setopt($curl,CURLOPT_COOKIEJAR,$cookie);
//URLにアクセスし、結果を文字列として返す
$html=curl_exec($curl);
//cURLのリソースを解放する
curl_close($curl);

の部分でフォーム内のトークンを分析するためのHTMLを取得する。

//Document初期化
$dom=new DOMDocument();
//html文字列を読み込む(htmlに誤りがある場合エラーが出るので@をつける)
@$dom->loadHTML($html);
//XPath初期化
$xpath=new DOMXPath($dom);
//inputのtypeがhiddenの要素をとってくる
$node=$xpath->query('//input[@type="hidden"]');
foreach($node as $v)
{
    //POST用のデータに追加する
    $data[$v->getAttribute('name')]=$v->getAttribute('value');
}

の部分でHTML解析を実際に行いトークンを取得し、POST用データ配列に入れる。

//cURLを初期化して使用可能にする
$curl=curl_init();
//オプションにURLを設定する
curl_setopt($curl,CURLOPT_URL,$url);
//メソッドをPOSTに設定
curl_setopt ($curl,CURLOPT_POST,true);
//POSTデータ設定
curl_setopt($curl,CURLOPT_POSTFIELDS,$data);
//クッキーを読み込むファイルを指定
curl_setopt($curl,CURLOPT_COOKIEFILE,$cookie);
//Locationをたどる
curl_setopt($curl,CURLOPT_FOLLOWLOCATION,true);
//URLにアクセスし、結果を表示させる
curl_exec($curl);
//cURLのリソースを解放する
curl_close($curl);

の部分で今まで作ったPOSTデータを飛ばしログインしている。

//テンポラリファイルを削除
unlink($cookie);

の部分でcookie保持用に用意したファイルを消しておく。
ただ、実際は放っておけばそのうち勝手に消えてしまうファイルなので、
わざわざ消す必要がないと言えばないのだが・・・

ポイントとなる関数
  • curl_init ・・・ cURL セッションを初期化する
  • curl_setopt ・・・ cURL 転送用オプションを設定する
  • curl_exec ・・・ cURL セッションを実行する
  • curl_close ・・・ cURL セッションを閉じる
  • DOMDocument クラス ・・・ HTML ドキュメントあるいは XML ドキュメント全体を表し、 ドキュメントツリーのルートとなります。
  • DOMXPath クラス ・・・ XPath 1.0 をサポートします。

2012年8月23日木曜日

[PHP][cURL] cURLでリダイレクトをたどるようにする

PHPはサイト解析するためにURLから情報を取得する方法がたくさんある。
その一つにcURLというのがある。
cURLではHTTPステータスコードが300系などで既に移転してしまったサイトなどは
リクエストのヘッダにLocationがあればリダイレクトしてたどっていく機能がある。

//URLを指定する
$url='google.co.jp';

//cURLを初期化して使用可能にする
$curl=curl_init();

//オプションにURLを設定する
curl_setopt($curl,CURLOPT_URL,$url);
//Locationをたどる
curl_setopt($curl,CURLOPT_FOLLOWLOCATION,true);
//最大何回リダイレクトをたどるか
curl_setopt($curl,CURLOPT_MAXREDIRS,10);
//リダイレクトの際にヘッダのRefererを自動的に追加させる
curl_setopt($curl,CURLOPT_AUTOREFERER,true);

//URLにアクセスし、結果を表示させる
curl_exec($curl);

//cURLのリソースを解放する
curl_close($curl);

とすればよい。
意外と自分でリダイレクトをたどり続ける処理を書くのは一手間なので、
こういう細かな機能はありがたい。

ポイントとなる関数
  • curl_init ・・・ cURL セッションを初期化する
  • curl_setopt ・・・ cURL 転送用オプションを設定する
  • curl_exec ・・・ cURL セッションを実行する
  • curl_close ・・・ cURL セッションを閉じる

[PHP][cURL] cURLで結果を文字列として受け取る

PHPは数多くの関数やクラスがあり、URLから結果を取得することができるものもある。
その一つにcURLがある。
cURLはデフォルトでは取得した結果をそのままブラウザやコンソールなどに出力するが、
文字列として受け取りたい場合がある。
その際には

//URLを指定する
$url='www.google.co.jp';

//cURLを初期化して使用可能にする
$curl=curl_init();

//オプションにURLを設定する
curl_setopt($curl,CURLOPT_URL,$url);
//文字列で結果を返させる
curl_setopt($curl,CURLOPT_RETURNTRANSFER,true);

//URLにアクセスし、結果を文字列として返す
$result=curl_exec($curl);

//cURLのリソースを解放する
curl_close($curl);

となる。
この後にDOM解析したり、特定文字列を検知したりとすることになるだろう。

ポイントとなる関数
  • curl_init ・・・ cURL セッションを初期化する
  • curl_setopt ・・・ cURL 転送用オプションを設定する
  • curl_exec ・・・ cURL セッションを実行する
  • curl_close ・・・ cURL セッションを閉じる