Welcome to TiddlyWiki created by Jeremy Ruston, Copyright © 2007 UnaMesa Association
Psychlopsは、できるだけ簡単に正確でフレキシブルな画面描画をするためのツールです。
Psychlopsを使うと、Windows/MacのCGアプリケーションを作成することができます。
"C++言語ライブラリ"の形で提供されていますが、C++言語の複雑な構造の知識はほとんど必要ありません。
C++プログラミングの事前の知識がなくても、できる限り簡単に使用できるように設計されています。C++の文法については[[1.6節|1.6 Psychlopsを使ったプログラミング]]にまとめてある程度の知識があれば一通りの機能を利用することができるでしょう。
以下の項目ではPsychlopsの概要についてもう少し詳しく説明します。とにかく使ってみたい方は[[1節|1. インストールを行う]]に進んでください。
! Psychlopsの主な特徴
* 複雑な操作を正確に行えることをコンセプトとしています。
* 刺激提示に特化し、視覚実験での使いやすさを追求しています。
* Mac OS XでもWindowsでも使える汎用性があります。
* 無償のツールのみで使用可能です。
* 他のC/C++コードと混在させることができます。
! 複雑な操作を正確に行う
Psychlopsはプログラミング初心者でも取り扱いやすいライブラリをコンセプトとしています。習得難易度は各種ソフト付属のスクリプト程度を目標としています。
C/C++言語で刺激を記述しなければならない場合、どうしても初期化処理が煩雑になり、制御の正確さまで手が回りません。例えば、OpenGL標準の拡張ライブラリGLUTは、時間の制御精度は全く保証されていません。Psychlopsは初期化処理を全自動で行いますが、その制御処理は逐一検証して作成しています。
* PCの機能の最小単位で視覚刺激を操作できます
** 画素単位で操作が可能です。
** 垂直同期ひとつひとつにフレームを埋め込むことができます。
* プログラム言語を直接うつことで、パラメタをシステマティックに変えるなどの複雑な実験パラダイムを組むことも可能です。
! 刺激提示に特化した内容
刺激提示の準備処理を自動化し、刺激に必要な部分だけを書けば動かすことができます。以下の例は、画像ファイルを読み込んで提示する最短のプログラム例です。
{{{
#include <psychlops.h>
using namespace Psychlops; // Psychlopsの呼び出し
void psychlops_main() {
Canvas display( Canvas::fullscreen ); // フルスクリーン表示領域の確保
Image natural_iamge;
natural_iamge.load(“Natural Image.png”) // 自然画像の読み込み
while(Input::get(Keyboard::esc)) { // ESCキーが押されるまで刺激提示
natural_iamge.centering().draw(); // 自然画像の位置あわせと提示
display.flip(); // フレームの提示
}
}
}}}
! 汎用性
Psychlopsの基本コンセプトは、OpenGL等のOSやドライバに直結した機能を用い、汎用ビデオカードを正確に制御することを目標としています。このため、特定のソフトや機械に依存せず、様々な環境で実行することが可能です。
* Mac OS XとWindowsで同一コードで動作可能です。
* 特定のソフトウェア・機器に依存しません。
** 一定以上古い環境はサポートしておりません。[[1.1 必要な環境]]をご確認ください。
* 生成した実行ファイルを頒布可能です。
! 無償のツールのみで使用可能
Psychloplsプログラムの作成実行は、OS以外はすべて無償のソフトを利用して行うことができます。
* Psychlops自体はオープンソースで公開されています。
* Mac OS X PPC mac, intel mac
** Apple社より無償提供のXcode上でPsychlopsプログラムを作成できます
* Windows 2000, XP, Vista
** 他ベンダ製無償の開発ツール上でPsychlopsプログラムを作成できます
Psychlopsによるアプリケーション作成には以下の2つのステップがあります。
* プログラム本体(cppの拡張子がついたテキストファイル)の作成
* 作成されたプログラムの実行(アプリケーション)ファイルへの変換(コンパイル)
これを実現するには、Psychlops本体のインストールとは別に、変換を行うためのコンパイラと呼ばれるプログラムのインストールが必要です。さらに、この2つのステップをスムーズに行うための支援プログラム(開発環境)をインストールすることを強く推奨します。
以下では、Macintosh, Windowsの各環境でこれらのアプリケーションをインストールしてPsychlopsによるC++アプリケーションの作成が可能な環境の準備を行う方法を説明します。
[[1.1 必要な環境]]
[[1.1.1 ハードウェア環境]]
[[1.1.2 ソフトウェア環境]]
[[1.2 インストール作業(Mac) ]]
OSX 10.4の場合は[[こちら|1.2 インストール作業(Mac OS X 10.4) ]]
[[1.3 インストール作業(Windows) ]]
Borland + Reloの場合は[[こちら|1.3 インストール作業(Windows + BCC) ]]
[[1.4 更新とアンインストール]]
[[1.5 Psychlopsの基本構造]]
この節では、ここまでのまとめとして、これまで説明してきた関数群を用いて簡単な恒常法の実験プログラム作成例について説明します。
一般的な恒常法を用いた心理学実験のプログラム上の大まかな流れは以下のようになります。
# 実際の試行に先立つ計算
## 実験計画のプログラミング:独立変数の範囲設定と、ラテン格子法に基づいた各試行への割り振り
## 実験刺激の事前描画(ある場合のみ):リアルタイム描画が不可能な精密な刺激の描画とバッファリング
# 各試行の反復実行
## 各試行における描画(刺激の提示):各試行における独立変数の読み込みと、それらを用いた実際の描画
## 反応の取得と 配列への記録
# 終了処理:反応が記録された配列の保存
この節では例としてGaborパターンの方位判断を恒常法で行わせる実験プログラムを考えます。
この実験の実験計画は以下のようなものであるとしましょう。
独立変数はGaborパターンの傾き量(2度, 4度, 8度, 16度, 32度の5水準)とパターンの提示時間(100, 250, 500 msの3水準)です。
この5水準のいずれかの量だけ垂直から右・左いずれかの方向に傾いたGaborパターンが100-500 msのうちのいずれかの時間だけ提示されます。
被験者の課題は提示された刺激パターンが右・左のいずれに傾いていたかをfキー(左)かjキー(右)を使って反応することです。
この試行をランダム順で各10回ずつ行うと1セッションが終わりです。
この実験計画を上の一般的なプログラムの流れにあわせてみると以下のようになります。
# 実際の試行に先立つ計算
## 実験計画のプログラミング:Gabor方位の範囲設定と、ラテン格子法に基づいた各試行への割り振り
## 実験刺激の事前描画:各方位のGaborパターンのオフスクリーン(Image)への描画
# 各試行の反復実行
## 各試行における描画(刺激の提示):該当するGaborが描画されたImageインスタンスを用いた描画
## 反応の取得と 配列への記録
# 終了処理:反応が記録された配列の保存:各試行の独立変数条件と被験者の反応(正答: 1, 誤答: 0)をファイルに記録
このそれぞれのパーツをC++のプログラムとして実装していきます。
[[1.1 実験計画のプログラミング]] (1-a)
[[1.2 Gaborパッチの事前描画]] (1-b)
[[1.3 各試行部分のプログラミング]] (2)
[[1.4 終了処理とまとめ]](3)
!ラテン格子法の実装
はじめに、実験計画の実装を行います。
ここはPsychlopsの使用とは直接関係ありませんが、初心者が実験プログラムを書くときに最も戸惑うところかもしれません。
このマニュアルでは、汎用性を意識したラテン格子法を実行する独立変数のクラスIndependentVariablesを追加して、使用してみます。
ここでは、このクラスの詳細な解説は行いません。
C++のプログラミングに慣れていない方はここで作成したクラスを単にプログラムに追加して使ってみることをお勧めします。
!!~IndependentVariablesクラスの概要
このクラスには、基本的な情報(メンバ)として
# 独立変数の数(int ~MaxVarNum)と、独立変数の水準数の最大値(int ~MaxStepNum)
#それぞれの水準の値(double * ~VariableStepNumber)
# 各水準ごとの繰り返しの数(int repeatNumber)
# ある特定の試行時の各独立変数の情報(Matrix ~CondMtx)
が格納されています。
次に実際に恒常法の実験プログラムで使用する命令(メソッド)としては、
* これらの値を設定するためのset()命令
* 各試行において必要な独立変数を取り出すためのget()命令
* ラテン格子法を実行するrandomize()命令
があります。
[[IndependentVariablesクラスのソースコード|IndependentVariables]]
上のリンクのソースコードを実際の宣言が起こる前(普通は、グローバル変数の宣言直後)にコピーアンドペーストして、プログラムに追加してください。さらに、このクラスは標準の入出力ライブラリを使用しているので、プログラムの一番始めに
{{{
#include <stdio.h>
}}}
と書いて、標準の入出力ライブラリを使用できるようにしてください。
以上の処理を行うと、このクラスが使用できるようになります。
!!IndependentVariablesクラス変数の宣言
このクラスのコードをプログラムに追加したら、次はこのクラスのインスタンス(変数)を宣言します。
宣言の書式は以下の通りです
|~IndependentVariablesの宣言|~IndependentVariables(int varnumber, int maxstep, int iteration)|
|~|~|int ~varnumber:独立変数の数を指定|
|~|~|int maxstep:独立変数の水準の最大値を指定|
|~|~|int iteration:各水準の繰り返し回数|
ここで作成する実験の計画では
# 独立変数は2個だったので、第1引数は2
# 水準数の最大値はGaborの方位の5水準なので、第2引数は5
# 繰り返しは各10回なので、第3引数は10
となります。
インスタンス名をinvarにして宣言すると、以下のようになります。
{{{
IndependentVariables invar(2,5,10) ;
}}}
!!独立変数の各水準の設定
次に宣言したインスタンスinvarに各変数の水準を設定していきます。
はじめに各水準の値をあらかじめ1次元のdouble型配列に格納しておきます。配列名は適当でかまいません。
{{{
double gaborOrientation[5]={0, 1, 2, 3, 4};
//傾き量(絶対値) 0: 2 deg, 1: 4 deg, 2: 8 deg 3: 16 deg 4: 32 deg
double duration[3]={100, 250, 500}; //Stimulus Duration
}}}
Gaborの方位については実際の値を入れても良いのですが、後々のために各方位に番号を振ってその番号を格納する形になっています。
次に、これらの配列名を使ってinvarに変数の水準数と各水準の値を登録します。
これにはset()命令を使います。
|int ~IndependentVariables::set()|set(int vnum, int arraynum, double *array)|
|~|~|int vnum:独立変数の識別番号|
|~|~|int arraynum:独立変数の水準数|
|~|~|double *array:登録する値が格納された配列の名前(ポインタ)|
独立変数の識別番号は適当でかまいませんが、各独立変数に異なる値を割り振る必要があります。さらに、この識別番号の値の範囲は0~varnumber(インスタンスの宣言時に設定した独立変数の数)でなくてはいけません。この関数の返り値は登録が正常に行われた時は設定された独立変数の水準数、失敗したときは-1です。
Gaborの方位に0,提示時間に1を割り振ると、以下のようになります。
{{{
invar.set(0, 5, gaborOrientation);
invar.set(1, 3, duration);
}}}
!!ラテン格子を組む
最後に登録した値を使って、ラテン格子を組みます。これにはrandomize()命令を使用します。
|int ~IndependentVariables::randomize()|randomize([char *dataname])|char *dataname:組み上がったラテン格子の出力名 <br> (省略可能。省略時は出力されません)|
ここでは、"~ConditionMatrix.dat"というデータファイル(タブ区切りテキスト形式)を出力させることにしましょう。
{{{
invar.randomize("ConditionMatrix.dat");
}}}
!!計画部のプログラムのまとめと組み上がったラテン格子の利用
これで、実験計画のプログラミングは完了です。ここまでの所をまとめてみましょう。
{{{
IndependentVaribales invar(2,5,10) ;
double gaborOrientation[5]={0, 1, 2, 3, 4};
//傾き量(絶対値) 0: 2 deg, 1: 4 deg, 2: 8 deg 3: 16 deg 4: 32 deg
double duration[3]={100, 250, 500}; //Stimulus Duration
invar.set(0, 5, gaborOrientation);
invar.set(1, 3, duration);
invar.randomize("ConditionMatrix.dat");
}}}
この手続きによって計算されたラテン格子から各試行における独立変数の値を取得するにはget()命令を使用します。
|int ~IndependentVariables::get()|set(int vnum, int trial_now)|
|~|~|int vnum: 独立変数の識別番号|
|~|~|int trial_now: 試行番号|
例えば第5試行における刺激の提示時間を取得してdouble型の変数dura_nowに格納するには以下の様に書きます。
{{{
double dura_now;
dura_now=invar.get(1,5);
}}}
Psychlopsの実行には、Mac OS X 10.4またはWindows 2000以降がインストールされているコンピュータが必要です。なるべく最近のコンピュータの使用をお勧めしますが、2003年以降に製造されたものであれば概ね動作します。
[[1.1.1 ハードウェア環境]]
[[1.1.2 ソフトウェア環境]]
! Mac OS X
!! 最低動作環境
* Mac OS X 10.4以降
* 1GHz以上の~PowerPC G4を搭載した以降の世代のMacintosh
* (Power Macの場合)~OpenGL 1.4以降に対応したビデオカード
** Mac OS X10.4の動作環境は[[アップル社のサイト|<a class="linkification-ext" href="<a class="linkification-ext" href="<a class="linkification-ext" href="<a class="linkification-ext" href="<a class="linkification-ext" href="<a class="linkification-ext" href="<a class="linkification-ext" href="<a class="linkification-ext" href="<a class="linkification-ext" href="<a class="linkification-ext" href="<a class="linkification-ext" href="<a class="linkification-ext" href="http://www.apple.com/jp/macosx/upgrade/requirements.html" title="Linkification: http://www.apple.com/jp/macosx/upgrade/requirements.html">http://www.apple.com/jp/macosx/upgrade/requirements.html</a>" title="Linkification: <a class="linkification-ext" href="http://www.apple.com/jp/macosx/upgrade/requirements.html" title="Linkification: http://www.apple.com/jp/macosx/upgrade/requirements.html">http://www.apple.com/jp/macosx/upgrade/requirements.html</a>"><a class="linkification-ext" href="http://www.apple.com/jp/macosx/upgrade/requirements.html" title="Linkification: http://www.apple.com/jp/macosx/upgrade/requirements.html">http://www.apple.com/jp/macosx/upgrade/requirements.html</a></a>" title="Linkification: <a class="linkification-ext" href="http://www.apple.com/jp/macosx/upgrade/requirements.html" title="Linkification: http://www.apple.com/jp/macosx/upgrade/requirements.html">http://www.apple.com/jp/macosx/upgrade/requirements.html</a>"><a class="linkification-ext" href="http://www.apple.com/jp/macosx/upgrade/requirements.html" title="Linkification: http://www.apple.com/jp/macosx/upgrade/requirements.html">http://www.apple.com/jp/macosx/upgrade/requirements.html</a></a>" title="Linkification: <a class="linkification-ext" href="http://www.apple.com/jp/macosx/upgrade/requirements.html" title="Linkification: http://www.apple.com/jp/macosx/upgrade/requirements.html">http://www.apple.com/jp/macosx/upgrade/requirements.html</a>"><a class="linkification-ext" href="http://www.apple.com/jp/macosx/upgrade/requirements.html" title="Linkification: http://www.apple.com/jp/macosx/upgrade/requirements.html">http://www.apple.com/jp/macosx/upgrade/requirements.html</a></a>" title="Linkification: <a class="linkification-ext" href="http://www.apple.com/jp/macosx/upgrade/requirements.html" title="Linkification: http://www.apple.com/jp/macosx/upgrade/requirements.html">http://www.apple.com/jp/macosx/upgrade/requirements.html</a>"><a class="linkification-ext" href="http://www.apple.com/jp/macosx/upgrade/requirements.html" title="Linkification: http://www.apple.com/jp/macosx/upgrade/requirements.html">http://www.apple.com/jp/macosx/upgrade/requirements.html</a></a>" title="Linkification: <a class="linkification-ext" href="http://www.apple.com/jp/macosx/upgrade/requirements.html" title="Linkification: http://www.apple.com/jp/macosx/upgrade/requirements.html">http://www.apple.com/jp/macosx/upgrade/requirements.html</a>"><a class="linkification-ext" href="http://www.apple.com/jp/macosx/upgrade/requirements.html" title="Linkification: http://www.apple.com/jp/macosx/upgrade/requirements.html">http://www.apple.com/jp/macosx/upgrade/requirements.html</a></a>" title="Linkification: <a class="linkification-ext" href="http://www.apple.com/jp/macosx/upgrade/requirements.html" title="Linkification: http://www.apple.com/jp/macosx/upgrade/requirements.html">http://www.apple.com/jp/macosx/upgrade/requirements.html</a>"><a class="linkification-ext" href="http://www.apple.com/jp/macosx/upgrade/requirements.html" title="Linkification: http://www.apple.com/jp/macosx/upgrade/requirements.html">http://www.apple.com/jp/macosx/upgrade/requirements.html</a></a>" title="Linkification: <a class="linkification-ext" href="http://www.apple.com/jp/macosx/upgrade/requirements.html" title="Linkification: http://www.apple.com/jp/macosx/upgrade/requirements.html">http://www.apple.com/jp/macosx/upgrade/requirements.html</a>"><a class="linkification-ext" href="http://www.apple.com/jp/macosx/upgrade/requirements.html" title="Linkification: http://www.apple.com/jp/macosx/upgrade/requirements.html">http://www.apple.com/jp/macosx/upgrade/requirements.html</a></a>" title="Linkification: <a class="linkification-ext" href="http://www.apple.com/jp/macosx/upgrade/requirements.html" title="Linkification: http://www.apple.com/jp/macosx/upgrade/requirements.html">http://www.apple.com/jp/macosx/upgrade/requirements.html</a>"><a class="linkification-ext" href="http://www.apple.com/jp/macosx/upgrade/requirements.html" title="Linkification: http://www.apple.com/jp/macosx/upgrade/requirements.html">http://www.apple.com/jp/macosx/upgrade/requirements.html</a></a>" title="Linkification: <a class="linkification-ext" href="http://www.apple.com/jp/macosx/upgrade/requirements.html" title="Linkification: http://www.apple.com/jp/macosx/upgrade/requirements.html">http://www.apple.com/jp/macosx/upgrade/requirements.html</a>"><a class="linkification-ext" href="http://www.apple.com/jp/macosx/upgrade/requirements.html" title="Linkification: http://www.apple.com/jp/macosx/upgrade/requirements.html">http://www.apple.com/jp/macosx/upgrade/requirements.html</a></a>" title="Linkification: <a class="linkification-ext" href="http://www.apple.com/jp/macosx/upgrade/requirements.html" title="Linkification: http://www.apple.com/jp/macosx/upgrade/requirements.html">http://www.apple.com/jp/macosx/upgrade/requirements.html</a>"><a class="linkification-ext" href="http://www.apple.com/jp/macosx/upgrade/requirements.html" title="Linkification: http://www.apple.com/jp/macosx/upgrade/requirements.html">http://www.apple.com/jp/macosx/upgrade/requirements.html</a></a>" title="Linkification: <a class="linkification-ext" href="http://www.apple.com/jp/macosx/upgrade/requirements.html" title="Linkification: http://www.apple.com/jp/macosx/upgrade/requirements.html">http://www.apple.com/jp/macosx/upgrade/requirements.html</a>"><a class="linkification-ext" href="http://www.apple.com/jp/macosx/upgrade/requirements.html" title="Linkification: http://www.apple.com/jp/macosx/upgrade/requirements.html">http://www.apple.com/jp/macosx/upgrade/requirements.html</a></a>]]よりご確認ください
! Windows
!!最低動作環境
* Windows 2000 sp4、XP sp2、Vista
* ~OpenGL 1.4以降に対応したビデオカード
** [[Tips: OpenGL 1.4に対応した一般向けビデオチップ]]を参照してください。
** ノートPCなどグラフィックチップ内臓の場合、VRAMサイズを32MB以上にしてください
** (注)Windows Vistaではビデオカードメーカーより提供されている~OpenGLドライバがVistaに対応している必要があります。現状(2007年9月現在)、nVidia社の製品は対応していることを確認しています。
!! 推奨動作環境
* 1GHz以上の速度を持つCPU
* 512MB以上のメモリ
* なるべく最新のビデオカード、なるべく多めのVRAM
PsychlopsはC++のライブラリとして提供されます。つまり、Psychlopsを使ったプログラミングには、C++の開発環境が必要です。これには様々なものがありますが、以下のものの使用を強くおすすめします。入手・インストール方法の詳細は1.2以降で説明します。
! Mac OSX
* Xcode 1.2 以降
** [[Apple社が提供している純正開発環境|<a class="linkification-ext" href="<a class="linkification-ext" href="<a class="linkification-ext" href="<a class="linkification-ext" href="http://www.apple.com/jp/macosx/developertools/" title="Linkification: http://www.apple.com/jp/macosx/developertools/">http://www.apple.com/jp/macosx/developertools/</a>" title="Linkification: <a class="linkification-ext" href="http://www.apple.com/jp/macosx/developertools/" title="Linkification: http://www.apple.com/jp/macosx/developertools/">http://www.apple.com/jp/macosx/developertools/</a>"><a class="linkification-ext" href="http://www.apple.com/jp/macosx/developertools/" title="Linkification: http://www.apple.com/jp/macosx/developertools/">http://www.apple.com/jp/macosx/developertools/</a></a>" title="Linkification: <a class="linkification-ext" href="http://www.apple.com/jp/macosx/developertools/" title="Linkification: http://www.apple.com/jp/macosx/developertools/">http://www.apple.com/jp/macosx/developertools/</a>"><a class="linkification-ext" href="http://www.apple.com/jp/macosx/developertools/" title="Linkification: http://www.apple.com/jp/macosx/developertools/">http://www.apple.com/jp/macosx/developertools/</a></a>" title="Linkification: <a class="linkification-ext" href="http://www.apple.com/jp/macosx/developertools/" title="Linkification: http://www.apple.com/jp/macosx/developertools/">http://www.apple.com/jp/macosx/developertools/</a>"><a class="linkification-ext" href="http://www.apple.com/jp/macosx/developertools/" title="Linkification: http://www.apple.com/jp/macosx/developertools/">http://www.apple.com/jp/macosx/developertools/</a></a>]]を参考にApple社のサイトからダウンロードしてください。
** ファイルサイズがとても大きいので、注意してください。
! Windows
* コンパイラ本体:~MinGW 3.4 or later, VC Toolkit 2003 with Platform SDK, ~BCC5.5など
* フリーの開発環境である[[Code::blocks|<a class="linkification-ext" href="http://www.codeblocks.org/" title="Linkification: http://www.codeblocks.org/">http://www.codeblocks.org/</a>]], [[Relo|<a class="linkification-ext" href="http://www.fifsoft.com/relo/" title="Linkification: http://www.fifsoft.com/relo/">http://www.fifsoft.com/relo/</a>]] を使用することをおすすめします。これら2つのためのプロジェクトテンプレート(psychlopsを利用した実行ファイルを作成するためのテンプレート)がPsychlopsのインストールキットに含まれています。
** Code::blocksを使用する場合は、~MinGWが同時にインストールされます。
** ~BCC5.5 + Reloはインストールが比較的簡単で、初心者の型はこちらをお勧めします。
** BCC 5.5 (Borland C++Compiler / Turbo Debugger) はhttp://www.codegear.com/jp/downloads/free/cppbuilderから入手してください。
この節では実験刺激のGaborパッチを事前描画するプログラムを作ります。
Gaborパッチとは、以下の図のような正弦波にGauss関数によるエンベロープをかけて、刺激中心から周辺に離れるに従って正弦波のコントラストが低くなっていく様な刺激です。
|[Img[image/gabor.png]]| [Img[image/gabor3d.png]]|
この刺激は複雑な2次元パターンなので、Canvas::pix()を使ってリアルタイム描画をするよりは、Imageにあらかじめ描画を行った方がいいでしょう。
Imageを使った描画については[[3章|3. 複雑な描画を行う1(オフスクリーンへの描画)]]で既に触れましたが、この節では[[1.1節|1.1 実験計画のプログラミング]]と同様に汎用性を意識してGaborパッチを描画するためのクラス~GaborImageを作成して、これを利用した刺激描画を行います。
!!~GaborImageクラスの概要
[[GaborImageクラスのソースコード|GaborImage]]
[[1.1節|1.1 実験計画のプログラミング]]のIndependentVariablesクラス同様上のリンクのソースコードを実際の宣言が起こる前にコピーアンドペーストして、プログラムに追加するとこのクラスを使用することができます。
このクラスはdraw()命令のみから構成されています。この命令は指定したImageにImageサイズの1/6をσとするエンベロープのGaborパッチを描画します。
|!~GaborImage::draw()|draw(Image &area, double ori, double freq, double phase, double Lmean, double *contrast)|カラーのGaborパッチをImageに描画します。|
|~|draw(Image &area, double ori, double freq, double phase, double Lmean, double contrast)|グレースケールのGaborパッチをImage上に描画します。|
|~|!引数|>|
|~|Image &area: Gaborパッチを描画するImageの名前|>|
|~|double freq: キャリアの空間周波数(pixel)|>|
|~|double phase: キャリアの位相(度)|>|
|~|double Lmean: パッチの平均輝度 |>|
|~|double *contrast: <br> キャリアのR,G,B各チャンネルのコントラストの配列名 (double[3]/範囲0.0-1.0)|double contrast: <br> キャリアのコントラスト <br> (範囲0.0-1.0)|
!!~GaborImageクラスを用いた刺激の事前描画
ここでは、上で説明した~GaborImageクラスを使って刺激の事前描画を行います。
まず、ここで追加したクラスのインスタンスを作成することにします。インスタンス名はgaborIMGにします。
{{{
GaborImage gaborIMG;
}}}
次にGaborパッチを描画するImageを配列gIMGとして宣言します。
{{{
Psychlops::Image gIMG[10];
}}}
このImageの10枚分の配列に(傾き量5水準) x (傾きの方向:右/左の2水準)の10枚のGaborパッチを書けばよいのですが、どの配列にどの方位のGaborパッチを書けば良いかを考えなくてはいけません。6.1でGaborパッチの傾き量の水準は0~4の番号を振られていたことを思い出してください。各試行ではこの番号を元にで、各試行でランダムに与えられた傾きの方向を考慮して、呼び出すgIMGの番号を決める必要があります。
少し複雑になってきたので、与えられた傾き量の番号と傾きの方向に対して、どの方位のGaborパッチが呼び出されるべきかを表にしておきましょう。垂直方位は90度なのでこれに対して傾き量が加減されていることに気をつけてください。
|>|!傾き量の番号| 0 | 1 | 2 | 3 | 4 |
|!傾きの方向|右|90-2|90-4|90-8|90-16|90-32|
|~|左|90+2|90+4|90+8|90+16|90+32|
| (単位は度)|c
ここでは、以下の表の様にgIMGの番号とGaborパッチの方位を対応させることにします。ついでに、傾きの方向に対しても番号を振っておきます。右を0左を1としてみましょう。
|>|!傾き量の番号|>| 0 |>| 1 |>| 2 |>| 3 |>| 4 |>|
|!傾きの方向|右(0)|90-2|bgcolor(#a0ffa0):gIMG[0]|90-4|bgcolor(#a0ffa0):gIMG[1]|90-8|bgcolor(#a0ffa0):gIMG[2]|90-16|bgcolor(#a0ffa0):gIMG[3]|90-32|bgcolor(#a0ffa0):gIMG[4]|
|~|左(1)|90+2|bgcolor(#a0ffa0):gIMG[5]|90+4|bgcolor(#a0ffa0):gIMG[6]|90+8|bgcolor(#a0ffa0):gIMG[7]|90+16|bgcolor(#a0ffa0):gIMG[8]|90+32|bgcolor(#a0ffa0):gIMG[9]|
| (単位は度)|c
さらに、これをgIMGの番号順に並べ替えて、各番号に対する計算式を考えます。以下のようになるでしょう。
|!gIMGの配列番号|gIMG[0]|gIMG[1]|gIMG[2]|gIMG[3]|gIMG[4]|gIMG[5]|gIMG[6]|gIMG[7]|gIMG[8]|gIMG[9]|
|!(傾き量, 方向) 番号|(0,0)|(1,0)|(2,0)|(3,0)|(4,0)|(0,1)|(1,1)|(2,1)|(3,1)|(4,1)|
|!Gaborパッチの方位(度)|90-2|90-4|90-8|90-16|90-32|90+2|90+4|90+8|90+16|90+32|
|!配列番号iに対する方位の計算式|>|>|>|>| 90-pow(2, i) |>|>|>|>| 90+pow(2,i-4) |
| ^^Pow(a,b)はaのb乗を示す^^|c
これで、10枚のGaborパッチを各準備が整いました。~GaborImage::draw()命令を使って実際のプログラムを組んでみます。ここでは最終引数に単なる数値を入れて、グレースケールのGaborパッチを描画することにします。
最後にdraw()命令を使ってGaborパッチの描画を行います。刺激サイズは100 pixel四方、キャリアの波長は30 pix (空間周波数は1.0/30.0), キャリアのコントラストは0.2 (20%), 位相は0, パッチの平均輝度は0.5にします。
また、あらかじめ刺激提示位置(画面中央)にイメージをシフトしておきます。
{{{
double gIMGsize=100, ori;
for(int i=0; i<10; i++){
if(i<5){ ori=90.0-pow(2.0, i);}
else{ ori=90.0+pow(2.0,i-4);}
gIMG[i].set(gIMGsize, gIMGsize);
gaborIMG.draw(gIMG[i], ori, 1.0/30.0, 0.0, 0.5, 0.2);
gIMG[i].centering();
}
}}}
この節のコードをまとめると以下のようになります。
{{{
GaborImage gaborIMG;
Psychlops::Image gIMG[10];
double gIMGsize=100, ori;
for(int i=0; i<10; i++){
if(i<5){ ori=90.0-pow(2.0, i);}
else{ ori=90.0+pow(2.0,i-4);}
gIMG[i].set(gIMGsize, gIMGsize);
gaborIMG.draw(gIMG[i], ori, 1.0/30.0, 0.0, 0.5, 0.2);
gIMG[i].centering();
}
}}}
!! Xcodeのインストール
PsychlopsのOSXでのインストールには、まずXcodeをインストールする必要があります。XcodeはApple社が提供する標準の開発環境で、無償で利用できます(ダウンロードにはApple Developers Connectionへの登録が必要です)。
* [[ダウンロード|http://developer.apple.com/tools/xcode/]]
* [[機能紹介|http://www.apple.com/jp/macosx/features/xcode/]]
''Mac OS X 10.4向けのXcode 2.x系はApple社よりの配布がすでに終了しています。OSX 10.4のインストール/リカバリディスク中に開発用ツールキットがない場合、10.6へのアップグレードをご検討ください''
''xcode.mpkg''をダブルクリックしてインストールを始めます。
[img[Xcodeインストール|image/OSX/Xcode2_10.4.png]]
インストーラの指示に従ってインストールを完了します。
[img[Xcodeインストール|image/OSX/Xcode3_10.4.png]]
!! Psychlopsの本体をインストール
* [[Psychlops Download|http://psychlops.l.u-tokyo.ac.jp/?Download]]
ダウンロードページへ行き、Xcode版の最新版をクリックします。
[img[Psychlopsダウンロード|image/OSX/Psychlops_OSX1png]]
zipファイルを解凍するとパッケージファイルが現れますので、これをダブルクリックしてインストールを開始します。
[img[Psychlopsインストール|image/OSX/Psychlops_OSX2.png]]
インストーラの指示に従ってインストールを完了します。
[img[Psychlopsインストール|image/OSX/Psychlops_OSX3.png]]
!! 新しいプロジェクト
Xcodeを起動し、''メニューパー → ファイル → 新しいプロジェクト''を選択します。
[img[新しいプロジェクト|image/OSX/Psychlops_OSX_10.4_New1.png]]
新しいプロジェクトのウィザードが開いたら、Applicationカテゴリにある''Psychlops C++ Application''を選択します。
[img[新しいプロジェクト|image/OSX/Psychlops_OSX_10.4_New2.png]]
新しいプロジェクトを作成する場所と名前を選びます。作成後に場所は変更可能ですが、名前は作り直す以外に変更できませんのでご注意ください。また、パスに日本語を含んでいると実行に失敗することがありますので、ASCII文字のみのフォルダの中に作成してください。
[img[新しいプロジェクト|image/OSX/Psychlops_OSX_10.4_New3.png]]
プログラムはSourcesカテゴリの中にある(プロジェクト名).cppにあります。標準のサンプルプログラムが入っていますので、まずはダブルクリックしてエディタを開き、中身を見てみます(最初から開いていることもあります)。
[img[新しいプロジェクト|image/OSX/Psychlops_OSX_10.4_New5.png]]
下のようなウィンドウが開いたでしょうか。
[img[ウインドウ説明|image/OSX/Psychlops_OSX_10.4_New6.png]]
プログラム編集エディタでブログラムを編集することができます。実際にはこのサンプルを消して、新しく自分のプログラムを書いていくわけですが、まず最初は正しくインストールされているかどうかを確かめるために、標準のサンプルをそのまま実行してみましょう。ツールバーの実行ボタンをおすと、プログラムが機械語に翻訳されて実行ファイルの作成・実行が行われます。
出来上がった実行ファイルは、''プロジェクトのあるフォルダ > buildフォルダ > Releaseフォルダ '' の中にあります。この実行ファイルをコピーすることで頒布が可能ですが、メールなどに添付する際は必ずzipファイル等に圧縮してください。(ここで見えている実行プログラムは実際はフォルダです。Macのプログラムの仕様で、実際の実行プログラムは複数存在して、実行プログラムに見えるフォルダに格納されています。これを他のマシンに頒布するためにアップロードしたり、メールに添付したりするときには圧縮して実際に一つのファイルにまとめる必要があります。)
[img[ウインドウ説明|image/OSX/Psychlops_OSX_10.4_New7.png]]
!! Xcodeのインストール
PsychlopsのOSXでのインストールには、まずXcodeをインストールする必要があります。XcodeはApple社が提供する標準の開発環境で、無償で利用できます(ダウンロードにはApple Developers Connectionへの登録が必要です)。対応バージョンについては、2010/01/31現在、Xcode2.5と3.x系に対応しておりますが、最新の対応状況につきましては[[http://psychlops.l.u-tokyo.ac.jp/?Environment]]からご確認ください。スクリーンショットはマニュアル作成時のものですので、バージョンが更新されていると異なる場合があります。ご了承ください。
* [[ダウンロード|http://developer.apple.com/technology/xcode.html]]
* [[機能紹介|http://www.apple.com/jp/macosx/features/xcode/]]
!!! OSXインストールディスクを利用する場合
OSXインストールディスクに開発環境のインストーラがあります。ディスクの内容は購入時期によって異なる可能性があります。ここでは10.5を例に説明いたします。
[img[Xcodeインストール|image/OSX/Xcode_DVD_Screen.png]]
OSXインストールディスク中をFinderで開くと、「オプションインストール」フォルダがあります。このなかの「Xcode Tools」フォルダを開き、「XcodeTools.mpkg」(拡張子は見えない場合があります)をダブルクリックすると、Xcodeインストーラが実行されます。
!!! ダウンロードする場合
Xcodeをダウンロードするには、まず[[Appleのダウンロードサイト|http://developer.apple.com/technology/xcode.html]]へ行きます。その際、初めての方はまずApple Developers Connectionへの登録をする必要があります。
[img[Xcodeインストール|image/OSX/Xcode_01.png]]
「ADC membership」と書かれたリンクをクリックすると「Join Now」というボタンがありますので、それをクリックして
[img[Xcodeインストール|image/OSX/Xcode_02.png]]
もう一度[[ダウンロードページ|http://developer.apple.com/technology/xcode.html]]へ行き下側の「Xcode for Mac-only Development」の「Download now」をクリックしてください。その後のページでアカウントを入力すると、ダウンロードリンクが現れますので、本体のdmgファイルをダウンロードしてください。
[img[Xcodeインストール|image/OSX/Xcode_03.png]]
ダウンロードが終わると自動的にディスクイメージが展開されますので、''XcodeTools.mpkg''をダブルクリックしてインストールを始めます。
[img[Xcodeインストール|image/OSX/Xcode2.png]]
インストーラの指示に従ってインストールを完了します。
[img[Xcodeインストール|image/OSX/Xcode3.png]]
!! Psychlopsの本体をインストール
* [[Psychlops Download|http://psychlops.l.u-tokyo.ac.jp/?Download]]
ダウンロードページへ行き、Xcode版の最新版をクリックします。
[img[Psychlopsダウンロード|image/OSX/Psychlops_OSX1png]]
zipファイルを解凍するとパッケージファイルが現れますので、これをダブルクリックしてインストールを開始します。
[img[Psychlopsインストール|image/OSX/Psychlops_OSX2.png]]
インストーラの指示に従ってインストールを完了します。
[img[Psychlopsインストール|image/OSX/Psychlops_OSX3.png]]
!! 新しいプロジェクト
Xcodeを起動し、''メニューパー → ファイル → 新しいプロジェクト''を選択します。
[img[新しいプロジェクト|image/OSX/Psychlops_OSX_New1.png]]
新しいプロジェクトのウィザードが開いたら、Applicationカテゴリにある''Psychlops C++ Application''を選択します。
[img[新しいプロジェクト|image/OSX/Psychlops_OSX_New2.png]]
新しいプロジェクトを作成する場所と名前を選びます。作成後に場所は変更可能ですが、名前は作り直す以外に変更できませんのでご注意ください。また、パスに日本語を含んでいると実行に失敗することがありますので、ASCII文字のみのフォルダの中に作成してください。
[img[新しいプロジェクト|image/OSX/Psychlops_OSX_New3.png]]
プログラムはSourcesカテゴリの中にある(プロジェクト名).cppにあります。標準のサンプルプログラムが入っていますので、まずはダブルクリックしてエディタを開き、中身を見てみます(最初から開いていることもあります)。
[img[新しいプロジェクト|image/OSX/Psychlops_OSX_New5.png]]
下のようなウィンドウが開いたでしょうか。
[img[ウインドウ説明|image/OSX/Psychlops_OSX_New6.png]]
プログラム編集エディタでブログラムを編集することができます。実際にはこのサンプルを消して、新しく自分のプログラムを書いていくわけですが、まず最初は正しくインストールされているかどうかを確かめるために、標準のサンプルをそのまま実行してみましょう。ツールバーの実行ボタンをおすと、プログラムが機械語に翻訳されて実行ファイルの作成・実行が行われます。
出来上がった実行ファイルは、''プロジェクトのあるフォルダ > buildフォルダ > Releaseフォルダ または Debugフォルダ'' の中にあります。この実行ファイルをコピーすることで頒布が可能ですが、メールなどに添付する際は必ずzipファイル等に圧縮してください。(ここで見えている実行プログラムは実際はフォルダです。Macのプログラムの仕様で、実際の実行プログラムは複数存在して、実行プログラムに見えるフォルダに格納されています。これを他のマシンに頒布するためにアップロードしたり、メールに添付したりするときには圧縮して実際に一つのファイルにまとめる必要があります。)
[img[ウインドウ説明|image/OSX/Psychlops_OSX_New7.png]]
インストールには管理者権限のあるユーザである必要があります。
Windows Vistaに関しては、インストールが自動化されていませんがお使いいただくことは可能です。
[[Tips: Vistaにおけるインストール]]をご覧ください。
!! OpenGLのドライバ設定の確認
Psychlopsは画面の垂直同期信号に合わせて画面を更新しますが、この機能を正常に動作させるには、ディスプレイドライバのOpenGL描画設定で垂直同期にあわせるオプションを常にオンにする必要があります。
一般的には''コントロールパネル > 画面''の設定の''モニタ''タブを選び、''詳細設定''ボタンから設定します。詳しくはご使用のビデオカードのマニュアルをご覧ください。
!! BCCのインストール
PsychlopsのWindowsでのインストールには、まずコンパイラをインストールする必要があります。ここではBorland社が無償で提供するBorland C++ Compiler 5.5をインストールしてみます。Cマガジンから提供されている設定ツールも一緒にダウンロードしましょう。
* [[BCCダウンロード|http://www.codegear.com/jp/downloads/free/cppbuilder]]
* [[設定ツールダウンロード|http://www.cmagazine.jp/setbcc.html]]
まずダウンロードページへ行き、''Borland C++Compiler / Turbo Debugger''をクリックします。この後個人情報を入力してダウンロードを開始します。
[img[BCCダウンロード|image/Win/BCC0.png]]
ダウンロードが終わるとインストーラの実行ファイルがありますので、''freecommandlinetools2.exe''をダブルクリックしてインストールを始めます。インストーラの指示に従ってインストールを完了します。
[img[BCCインストール|image/Win/BCC1.png]]
次に設定ツールである''setbcc15b.exe''をダウンロードし、実行します。
[img[BCCインストール|image/Win/BCC2.png]]
特に細かい設定をする必要はありません。''進む>>''をクリックし続けて実行すれば実行可能になります。
[img[BCCインストール|image/Win/BCC4.png]]
!! 開発環境Reloのインストール
次に開発環境をインストールします。ここではオープンソースのRelo2を利用します。
* [[Reloダウンロード|http://www.fifsoft.com/relo/download.php]]
まずダウンロードページへ行き、''RELO v2.0 INSTALLER''をクリックし、ダウンロードします。
[img[Reloダウンロード|image/Win/Psychlops_Relo1.png]]
ダウンロードが終わるとインストーラの実行ファイルがありますので、''relosetup20.exe''をダブルクリックしてインストールを始めます。インストーラの指示に従ってインストールを完了します。
[img[Reloインストール|image/Win/Psychlops_Relo2.png]]
インストール終了後、まず最初に環境設定を行うためにRelo2を起動します。起動したらメニューバーのTools から Compilers を選択します。
[img[Reloインストール|image/Win/Psychlops_Relo_New5.png]]
開いたウィンドウで、Newボタンを押し、そのまま進め、New CompilerをBCCにする設定をします。各フィールドを以下のように指定します。
* Name : BCC
* Compiler : bcc32
* Path : C:\borland\bcc55\Bin\
* Type : Borland
[img[Reloインストール|image/Win/Psychlops_Relo4.png]]
!! Psychlopsの本体をインストール
* [[Psychlops Download|http://psychlops.l.u-tokyo.ac.jp/?Download]]
ダウンロードページへ行き、BCC版の最新版をクリックします。
[img[Psychlopsダウンロード|image/Win/Psychlops_WinGL1.png]]
zipファイルを解凍するとインストーラフォルダが現れますので、この中の''PsychlopsLib''フォルダ内にある''PsychlopsLib_xxxx.exe''をダブルクリックしてインストールを開始します。
[img[Psychlopsインストール|image/Win/Psychlops_WinGL3.png]]
インストーラの指示に従ってインストールを完了します。
[img[Psychlopsインストール|image/Win/Psychlops_WinGL3.png]]
!! Relo用のテンプレートをインストール
zipファイルを解凍してできたインストーラフォルダの中の''ReloTemplate''フォルダ内にある''Psychlops_Relo2_Template_xxxxxxxx.exe''をダブルクリックしてインストールを開始します。インストーラの指示に従ってインストールを完了します。
[img[Psychlopsインストール|image/Win/Psychlops_Relo3.png]]
!! 新しいプロジェクト
Reloを起動し、''メニューパー → File → New''を選択します。
[img[新しいプロジェクト|image/Win/Psychlops_Relo_New1.png]]
新しいプロジェクトのウィザードが開いたら、''Psychlops Application''を選択します。
[img[新しいプロジェクト|image/Win/Psychlops_Relo_New2.png]]
新しいプロジェクトの名前を選びます。作成後に場所は変更可能ですが、名前は作り直す以外に変更できませんのでご注意ください。
[img[新しいプロジェクト|image/Win/Psychlops_Relo_New3.png]]
Reloはプロジェクトの作成直後は保存場所が決まっておりませんので、まずはプロジェクトを保存します。パスにスペースまたは日本語を含んでいると実行に失敗することがありますので、ASCII文字のみのフォルダの中に作成してください。
[img[新しいプロジェクト|image/Win/Psychlops_Relo_New4.png]]
プログラムはSourcesカテゴリの中にある(プロジェクト名).cppにあります。標準のサンプルプログラムが入っていますので、まずはダブルクリックしてエディタを開き、中身を見てみます(最初から開いていることもあります)。
下のようなウィンドウが開いたでしょうか?
[img[ウインドウ説明|image/Win/Psychlops_Relo_New6.png]]
プログラム編集エディタでブログラムを編集することができます。実際にはこのサンプルを消して、新しく自分のプログラムを書いていくわけですが、まず最初は正しくインストールされているかどうかを確かめるために、標準のサンプルをそのまま実行してみましょう。ツールバーの実行ボタンをおすと、プログラムが機械語に翻訳されて実行ファイルの作成・実行が行われます。
Reloは現在のバージョンでは、起動直後にコンパイラの設定ウィンドウを開いてOKボタンを押す必要があります。''メニューパー → Tools → Compilers''を選択して、開いたダイアログでOKを押します。
[img[新しいプロジェクト|image/Win/Psychlops_Relo_New5.png]]
出来上がった実行ファイルはプロジェクトと同じフォルダにあります。
[img[新しいプロジェクト|image/Win/Psychlops_Relo_New7.png]]
!! OpenGLのドライバ設定の確認
Psychlopsは画面の垂直同期信号に合わせて画面を更新しますが、この機能を正常に動作させるには、ディスプレイドライバのOpenGL描画設定で垂直同期にあわせるオプションを常にオンにする必要があります。
一般的には''コントロールパネル > 画面''の設定の''モニタ''タブを選び、''詳細設定''ボタンから設定します。詳しくはご使用のビデオカードのマニュアルをご覧ください。
!! CodeBlocks + GCCのインストール
PsychlopsのWindowsで利用するには、まずコンパイラと開発環境をインストールする必要があります。ここでは両者がセットになっているCodeBlocks(MinGW gccコンパイラつき)をダウンロードして利用する例を紹介します。
2010/01/31現在、CodeBlocks8.02 / Windows XP, Vista, 7に対応しておりますが、最新の対応状況につきましてはhttp://psychlops.l.u-tokyo.ac.jp/?Environmentからご確認ください。スクリーンショットはマニュアル作成時のものですので、バージョンが更新されていると異なる場合があります。ご了承ください。
* [[CodeBlocks + GCCダウンロード|http://www.codeblocks.org/downloads/5]]
まずダウンロードページへ行き、''codeblocks-8.02mingw-setup.exe''をSourceforgeまたはBerliOSのサーバからダウンロードします。どちらのサーバからダウンロードしても中身は同じです。
[img[CodeBlocksダウンロード|image/Win/CodeBlocks_Win01.png]]
ダウンロードが終わるとインストーラの実行ファイルがありますので、''codeblocks-8.02mingw-setup.exe''をダブルクリックしてインストールを始めます。インストーラの指示に従ってインストールを完了します。
[img[BCCインストール|image/Win/CodeBlocks_Win02.png]]
!! Psychlopsの本体をインストール
* [[Psychlops Download|http://psychlops.l.u-tokyo.ac.jp/?Download]]
ダウンロードページへ行き、Win32GL版の最新版をクリックします。
[img[Psychlopsダウンロード|image/Win/Psychlops_WinGL1.png]]
zipファイルを解凍するとインストーラフォルダが現れますので、この中の''PsychlopsLib''フォルダ内にある''PsychlopsFramework_Win32_x.x.x.exe''をダブルクリックしてインストールを開始します。
[img[Psychlopsインストール|image/Win/Psychlops_WinGL3.png]]
インストーラの指示に従ってインストールを完了します。
[img[Psychlopsインストール|image/Win/Psychlops_WinGL5.png]]
!! CodeBlocks用のテンプレートをインストール
zipファイルを解凍してできたインストーラフォルダの中の''CodeblocksTemplate''フォルダ内にある''Psychlops_Codeblocks_Template_xxxxxxxx.exe''をダブルクリックしてインストールを開始します。インストーラの指示に従ってインストールを完了します。
[img[Psychlopsインストール|image/Win/Psychlops_WinGL4.png]]
!! 新しいプロジェクト
CodeBlocksを起動し、''メニューパー → File → New → Project''を選択します。
[img[新しいプロジェクト|image/Win/CodeBlocks_Win10.png]]
新しいプロジェクトのウィザードが開いたら、''Psychlops GL Ploject''を選択します(見つからなければ選択ボックスをスクロールしてください)。
[img[新しいプロジェクト|image/Win/CodeBlocks_Win11.png]]
新しいプロジェクトの名前を決め、ファイルを保存する場所を選びます。作成後にフォルダごと移動することで場所は変更可能ですが、名前は作り直す以外に変更できませんのでご注意ください。
[img[新しいプロジェクト|image/Win/CodeBlocks_Win13.png]]
プログラムはSourcesカテゴリの中にあるmain.cppにあります。標準のサンプルプログラムが入っていますので、まずはダブルクリックしてエディタを開き、中身を見てみます(最初から開いていることもあります)。
下のようなウィンドウが開いたでしょうか?
[img[ウインドウ説明|image/Win/CodeBlocks_Win14.png]]
プログラム編集エディタでブログラムを編集することができます。実際にはこのサンプルを消して、新しく自分のプログラムを書いていくわけですが、まず最初は正しくインストールされているかどうかを確かめるために、標準のサンプルをそのまま実行してみましょう。ツールバーの実行ボタンをおすと、プログラムが機械語に翻訳されて実行ファイルの作成・実行が行われます。
出来上がった実行ファイルは、''プロジェクトのあるフォルダ > binフォルダ > Releaseフォルダ または Debugフォルダ'' の中にあります。この実行ファイルをコピーすることで頒布が可能ですが、メールなどに添付する際は必ずzipファイル等に圧縮してください。。
[[1.1|1.1 実験計画のプログラミング]], [[1.2|1.2 Gaborパッチの事前描画]]節で実際の試行に先立つ計算は終了したことになります。
ここでは実際の試行部分のプログラミングを行います。
各試行の実行時はここで作成している実験の場合大きく分けて2つに分かれるのでした。
a. 各試行における描画(刺激の提示):該当するGaborが描画されたImageインスタンスを用いた描画
b. 反応の取得と 配列への記録
まずa.の部分についてもう少し詳しく考えてみると、以下のような手順をループさせれば良いことがわかります。
# 各試行に対応した独立変数の取得
# 試行開始待ち(被験者のキー入力待ち)
# 刺激描画
# 反応待ち(被験者のキー入力待ち)
# 正誤判断とデータの格納
この流れに従って試行番号"trial"番の試行をプログラムしてみます。
ここで使う変数はあとでwhileループの外で宣言を行うので、とりあえず、宣言なしで変数を使っていきます。
!各試行に対応した独立変数の取得
まず、この試行におけるGaborパッチの傾きの方向"direction"をPsychlops::random()命令を使って0か1にランダムに決定します。
次に、試行番号trialから6.1で作成したクラスインスタンスinvarを用いて各独立変数を取得します。
各試行における傾き量の番号("ori_now")と提示時間("dura_now")を求めるには~IndependentVariables::get()を使います。
|!int ~IndependentVariables::get()|set(int vnum, int trial_now)|
|~|~|int vnum: 独立変数の識別番号|
|~|~|int trial_now: 試行番号|
単に設定したinvarから各試行に対応した変数を取得するのであれば、以下のコードの様になるでしょう。
{{{
ori_now=invar.get(0, trial);
dura_now=invar.get(1, trial);
}}}
しかしここでは実際に欲しい以下の2つの値を計算します
* [[1.2|1.2 Gaborパッチの事前描画]]節で事前描画したImageの配列gIMGの中でこの試行でCanvasに転送されるImageの番号
* 刺激提示のフレーム数
転送番号については、[[1.2|1.2 Gaborパッチの事前描画]]節で作った表をみると、呼び出す配列番号が(傾き量番号)+5 x (方向)であることがわかります。
|!gIMGの配列番号|gIMG[0]|gIMG[1]|gIMG[2]|gIMG[3]|gIMG[4]|gIMG[5]|gIMG[6]|gIMG[7]|gIMG[8]|gIMG[9]|
|!(傾き量, 方向) 番号|(0,0)|(1,0)|(2,0)|(3,0)|(4,0)|(0,1)|(1,1)|(2,1)|(3,1)|(4,1)|
次に、刺激提示のフレーム数ですが、これはCanvasクラスのリフレッシュレートを取得する関数getRefreshRate()を使えばCanvasの宣言に依存せずに提示時間を制御できます。^^*1^^
これらをまとめると変数の取得部は、以下の様なコードになります。
{{{
direction=Psychlops::random(2); //0:right 1:left
ori_now=invar.get(0,trial)+5*direction;
dura_now=sampleA.getRefreshRate()*(invar.get(1,trial)/1000);
}}}
^^*1 この実験では必ず計算結果が整数フレームとなるようにあらかじめ変数を設定してあることに注意してください。^^
!試行開始待ち(被験者のキー入力待ち)
次にユーザー(被験者)の準備が整って、キーを押すまでプログラムを"待ち"の状態にする部分をコーディングします。
ここでは、試行開始のキーにはスペースキーを用います。さらに、被験者がわかりやすいように、プログラム側の準備が整って「待ち」の状態になっている間は試行が何番目に当たるかを表示するようにしてみます。
キー入力待ちのコードは今まで何度も使ってきたとおりに、以下のように書きます。
{{{
while(!Input::get(Keyboard::spc));
}}}
試行番号を表示するにはCanvas::message()命令を使えばよいのですが、試行番号は試行毎に変化するので少し工夫が必要です。
ここでは、Cの標準関数であるsprintf()関数を使って、試行番号を文字列に埋め込みます。この部分はあまりプログラムの流れには関係ないので、良くわからない方はとりあえず無視して先に進んでください。以上をまとめると以下のようなコードになります。
{{{
sampleA.clear(0.5);
for(int i=0; i<64; i++) trial_header[i] = 0; // 文字列の初期化
sprintf(trial_header, "%s%d%s%d","Trial: ", trial, " /", trialNum ); //試行番号の埋め込み
sampleA.message(trial_header, sampleA.getHcenter(), sampleA.getVcenter()-100, Color::green);//キー待ちメッセージの描画
sampleA.flip();
while(!Input::get(Keyboard::spc));
}}}
!刺激描画
いよいよ実際の刺激の描画ですが、これは特に難しくありません。Image::draw()命令を使って裏画面にori_now番のイメージを転送し、Canvas::flip(int)命令を使って、dura_nowフレーム分の描画時間を予約して描画を反映させます。この後に、Canvas::clear()命令を使えば、一定時間たつと自動的に刺激が消えるプログラムになります。
{{{
sampleA.clear(0.5);
gIMG[ori_now].draw();
sampleA.flip(dura_now);
sampleA.clear(0.5);
sampleA.flip();
}}}
! 反応待ち(被験者のキー入力待ち)とデータの格納
刺激描画が終わったら被験者のキー入力(f: 左/j: 右)を待ちます。プログラムは"f"か"j"が押されるまでは待ち、押されたら押されたキーに従って、0(j), 1(f)のいずれかの値を変数ansに格納します。[[6.1|6.1 Gaborパッチの事前描画]]節においてgIMGを描画する際、傾きの方向番号にふられた番号が0が右、1が左であったことに注意してください。また、ここでescを押すと、プログラムを途中終了するコードも入れておきます。
{{{
while(1){
if(Input::get(Keyboard::j)){ans=0;break;} //"j"なら右(0)
if(Input::get(Keyboard::f)){ans=1;break;}//"f"なら左(1)
if(Input::get(Keyboard::esc)){exit(0);}
}
}}}
最後はデータの格納です。上の方でランダムに決定したdirectionとキー入力によって得られた値ansが同じならば正答(1),異なれば誤答(0)を配列answer のtrial番目に格納します。ついでに、後でデータを見やすくするためにここで使った2つ独立変数の値も配列orientationConditionと配列durationConditionに格納しておきます。
{{{
if(ans==direction) answer[trial]=1;
else answer[trial]=0;
orientationCondition[trial]=invar.get(0, trial);
durationCondition[trial]=invar.get(1, trial);
}}}
!まとめ
以上をまとめて、ループの中に入れると、各試行部分のプログラミングは完成です。
これまで保留していた変数の宣言がループの前にきちんと記述されていることやそれぞれの型がどのようになっているかにも注目してください。特に、answer, orientationCondition, durationConditionの配列を確保するためにはtrialNumをconst int型として宣言する必要があることには注意が必要です。
{{{
const int trialNum=2*5*10;
int answer[trialNum], orientationCondition[trialNum], durationCondition[trialNum], ans;
int ori_now, dura_now;
int direction;
char trial_header[64];
for(int trial=0; trial<trialNum; trial++){
//各試行に対する変数の取得
direction=Psychlops::random(2); //0:right 1:left
ori_now=invar.get(0,trial)+5*direction;
dura_now=sampleA.getRefreshRate()*(invar.get(1,trial)/1000);
//被験者のキー入力待ち
sampleA.clear(0.5);
for(int i=0; i<64; i++) trial_header[i] = 0; // 文字列の初期化
sprintf(trial_header, "%s%d%s%d","Trial: ", trial, " /", trialNum ); //試行番号の埋め込み
sampleA.message(trial_header, sampleA.getHcenter(), sampleA.getVcenter()-100, Color::green);//キー待ちメッセージの描画
sampleA.flip();
while(!Input::get(Keyboard::spc));
//刺激描画
sampleA.clear(0.5);
gIMG[ori_now].draw();
sampleA.flip(dura_now);
sampleA.clear(0.5);
sampleA.flip();
//反応の取得と格納
while(1){
if(Input::get(Keyboard::j)){ans=0;break;}
if(Input::get(Keyboard::f)){ans=1;break;}
if(Input::get(Keyboard::esc)){exit(0);}
}
if(ans==direction)answer[trial]=1;
else answer[trial]=0;
orientationCondition[trial]=invar.get(0, trial);
durationCondition[trial]=invar.get(1, trial);
}
}}}
2007/8/13以降のバージョンを使用していた場合は、Psychlopsの更新は通常のインストールとまったく同じです。ダウングレードする場合も同様です(つまり、単純に上書きしてしまってかまいません)。それ以前のバージョンが既にインストールされたシステムについては、一度アンインストールをしてからもう一度インストールを行ってください(下記参照)。
Psychlopsは特にレジストリ等の設定を行いませんので、アンインストール時はインストールされたフォルダごと削除してください。Psychlopsは単なるC++ライブラリで、常駐等はしませんので、削除しない場合でもシステムに影響を与えることはありません。
また、C++開発環境を自分で既に運用していて、デフォルトの場所以外の所にライブラリを置くことも可能です。この場合は、インストールを行った後に、自分でFrameworks以下にあるファイル・フォルダを目的の場所に移動して、ご自分でパスの設定をなさってください。
* ~MacOSX
** /Library/Frameworks/Psychlops.framework
* Windows
** (システムドライブ)\Library\Frameworks\Psychlops.framework
*** Windowsには通常Libraryフォルダがありませんので、このフォルダ内にPsychlopsしか入っていなければLibraryフォルダごと削除してかまいません。
** Relo、Code::Blocksのテンプレートについては、テンプレート一覧ファイルを上書きしています。元に戻す必要がある場合は、再インストールする必要があります。
最後にデータをファイルにセーブして、実験の終了メッセージを記録すれば、プログラミングは完了です。
被験者の反応を記録した配列answerと実験条件を記録した配列orientationCondition, durationConditionをData::savearray()命令を使用して"~ExptData.dat"という名前のテキストファイル(tab区切り)にセーブします。
{{{
sampleA.clear(0.5);
sampleA.message("Press space key to exit.",
sampleA.getHcenter(), sampleA.getVcenter()-100, Color::green);
Data::savearray("ExptData.dat",
"Duration /t Orientation /t Answer", trialNum,
durationCondition, orientationCondition, answer );
}}}
これで、すべてのパーツが完成しました。一つにまとめると以下の様になります。
[[Gabor方位判断の実験プログラム例]]
ぱっと見たときには長そうなプログラムですが、よく見ると実際にはクラスの宣言部分がほとんどを占めていて、実験に必要な部分のプログラム(つまりあなたが理解して、書かなければいけない部分!) は比較的短いコードであることがわかります。
コンパイルして1つ1つの部分が正しく実行されているかどうか確かめてみてください。
次節で詳しく述べるように、PsychlopsはC++のライブラリになっています。このライブラリは、いくつかの命令のグループから成り立っています。それぞれの命令のことをメソッド、グループのことをクラスと呼びます。Psychlopsを用いた描画は、ある変数がどのクラスに属する変数なのかを宣言し、この変数に対してメソッドを指定することで行います。より詳細な具体例は次節や、このマニュアルの中で随時見ていくと理解できるでしょう。今は、このようなグループがいくつかPsychlopsの中にあると言うことだけを理解しておけば十分です。以下に代表的なPsychlopsのクラスをまとめておきます。
|!Psychlops::Canvas|描画ウィンドウの確保と基本的な描画の命令セット|
|!Psychlops::Point|ドット描画と座標指定のための命令セット|
|!Psychlops::Rectangle|矩形描画と矩形範囲指定のための命令セット|
|!Psychlops::Color|描画色指定のための命令セット|
|!Psychlops::Image|オフスクリーン描画のための命令セット|
|!Psychlops::Matrix|行列演算のための命令セット|
|!Psychlops::Clock|時間計測のための命令セット|
|!Psychlops::Input|入出力取得のための命令セット|
それぞれのクラスの先頭についているPsychlops::はこれらのクラスがPsychlopsというライブラリの中のクラスであることを示しています。たとえば、Colorと言うクラスは、別のライブラリの中にも存在するかも知れません。その場合、プログラムを解釈する側は、このクラスがどのライブラリのクラスであるかを判断できなくなってしまいます。これを避けるために、スコープ演算子"::"を使って、あるクラスがどのライブラリに所属するかを示すのです。名字のようなものだと思うとわかりやすいかも知れません。
このほかにもPsychlopsにはData, File, Range等の特殊なクラスが存在しますが、これらについては具体的な使い方とともに解説します。
Psychlopsを使ったプログラミングはC++のプログラミングと同様ですが、必ずしもその全てを理解しておく必要はありません。(もちろん、理解しているほうがより柔軟なプログラミングができますが。)
ここでは、psychlopsを用いたプログラミングの際に、必要不可欠だと思われる基本要素についてごく簡単な説明を行います。より詳細なC++プログラミングを学びたい方は入門書・専門書を参考にされることをおすすめしますが、ここで説明するいくつかのことに対する大まかなイメージをもっておけばPsychlopsのプログラミングにとっては十分なはずです。より細かなことは実際のプログラミングを通して理解することをおすすめします。
[[1.6.1 プログラミングとは?]]
[[1.6.2 Psychlopsを使ったプログラムの形]]
[[1.6.3 配列]]
[[1.6.4 for命令]]
[[1.6.5 変数型]]
[[1.6.6 クラスと関数]]
まず最初に、プログラムとは何かについて説明します。
プログラムとは、画像や数値などのデータを、一定の手続きに従って操作する過程を、文字で表記したものです。たとえば、Excelなどの表計算でセルに書き込む式も、一種のプログラムということができます。
ここでは、Psychlopsのプログラムを例に考えてみます。
プログラムでまず最初に必要なことは、取り扱うデータや装置を準備することです。
コンピュータを使わない実験でも、画像や画像を配置する画面などをあらかじめ準備しなければなりませんが、
これと同じことをコンピューター上でも行います。
この準備を文字で表記すると、
''準備したい物 好きな名前''
という書き方になります。たとえば、
{{{
Image natural_image;
}}}
この1行は、画像(Iamge)にnatural_imageという名前をつけて取り扱う準備をしたことになります。
次に、データを操作する手続きを記述します。手続きは
''操作が行われる対象.操作方法(関係するデータ)''
の方法で記述します。英語のSVO文法に近い記述法ですね。
{{{
natural_image.load("Natural Image.png");
}}}
この一行は、natural_imageという画像として、「Natural Image.png」という名前の画像ファイルを読み込む手続きを表します。
{{{
natural_image.centering().draw();
}}}
この一行は、先ほど読み込んだ画像を、画面の中央に配置(センタリング)し、画面に描画する手続きを表します。
プログラムとは、「データを準備する」「データを操作する」ことについて、行う順番どおりに書いていくことなのです。
準備や操作に区切りをつけるには「;」(セミコロン)を記述します。
では、Psychlopsで動く簡単なプログラムを例にとって最も簡単なPsychlopsのプログラムの形を見てみます。
{{{
#include <psychlops.h>
using namespace Psychlops;
void psychlops_main() {
Canvas display( Canvas::fullscreen );
Image natural_image;
natural_image.load("Natural Image.png");
natural_image.centering().draw();
display.flip();
}
}}}
先頭の2行はPsychlopsを使うための命令です。
意味はわからなくてもかまいませんが、Psychlopsの命令をコンピュータに理解させるためには必ずこの2行をプログラムの先頭に書いておく必要があります。
{{{
#include <psychlops.h> // このプログラムにPsychlopsを含める
using namespace Psychlops; // このプログラムはPsychlopsを使う
}}}
次の行は、Psychlopsを用いたプログラム本体を書く場所(メインルーチン)を示す行です。
{{{
void psychlops_main() { // psychlopsが主に扱うブロック
}}}
Psychlopsは、ここから最後の行の閉じ括弧(})までのブロックをまず最初に実行します。
つまり、この括弧の中にある
{{{
Canvas display( Canvas::fullscreen );
Image natural_image;
natural_image.load("Natural Image.png");
natural_image.centering().draw();
display.flip();
}}}
この5行が実際にコンピュータに対して与える命令になります。
今はこの5行の具体的な内容については述べませんが、ユーザーは基本的にはこの部分を書き換えて描画を実行させることになります。
これまでの例では画像はひとつだけでしたが、何枚かの画像を選んで出したい場合があります。
いくつかのデータに似たような操作をしたい場合、「配列」が役に立ちます。配列とは、データがまとめて並んでいるもののことを言います。内部にいくつも区切りがある箱のようなものを想像するとよいかもしれません。
たとえば、画像を3枚まとめて扱いたい場合、以下のようにデータを準備します。
{{{
Image natural_images[3];
}}}
これで3枚の画像配列が用意されました。次に、データの並びの中のそれぞれの画像を操作してみましょう。
{{{
natural_images[0].load("Natural Image0.png");
natural_images[1].load("Natural Image1.png");
natural_images[2].load("Natural Image2.png");
}}}
'''配列[ 数字 ]'''
と書くことで、配列のうち数字で指定した順番の要素(この場合は画像)を操作することができます。
順番は0から始まることに注意してください。画像3枚を用意した場合、0,1,2になります。
この画像配列を順番に表示してみましょう。ここでは、繰り返し操作を記述するfor文を使うことにします。
{{{
for(int i=0; i<3; i++) {
natural_images[i].centering().draw();
display.flip(60);
}
}}}
for文は、{ } で囲まれたブロックを複数回実行することを示します。
回数は自分で指定することができます。
[img[image/formethod.png]]
この命令の後に続く{}で囲まれた部分のブロックは繰り返し、カウンタ(i)の値を0,1,2の順番で変更しながら実行されます。
{{{
{
natural_images[i].centering().draw();
display.flip(60);
}
}}}
つまり
{{{
for(int i=0; i<3; i++) {
natural_images[i].centering().draw();
display.flip(60);
}
}}}
と
{{{
natural_images[1].centering().draw();
display.flip(60);
natural_images[2].centering().draw();
display.flip(60);
natural_images[3].centering().draw();
display.flip(60);
}}}
は全く同じ結果になります。
image.centering().draw()は画像をセンタリングして描画する操作なので、以下のプログラムをNatural Image0.png, Natural Image1.png, Natural Image2.pngという3つのファイルがあるディレクトリで実行すると、3つの画像が順番に表示されます。
{{{
#include <psychlops.h>
using namespace Psychlops;
void psychlops_main() {
Canvas display( Canvas::fullscreen );
Image natural_image;
natural_images[0].load("Natural Image0.png");
natural_images[1].load("Natural Image1.png");
natural_images[2].load("Natural Image2.png");
for(int i=0; i<3; i++) {
natural_images[i].centering().draw();
display.flip(60);
}
}
}}}
ここまではPsychlopsで扱えるデータを題材に解説してきましたが、最後に、C++言語で取り扱うことのできる基本的なデータについて説明します。
これらのデータの中でもっとも基本的なものは数値です。数値は、主に整数型と小数点型に分かれます。
* int
** 整数(''int''eger)の略で、整数を取り扱います。小数点以下は切り捨てられますので、注意してください。
* double
** 浮動小数点を取り扱います(倍精度浮動小数点 ''double'' precision floating point number)
これらのデータも宣言方法は同じで、
{{{
int delay;
double contrast;
}}}
のように使います。データに特定の数値を代入する(格納する)場合は、
{{{
contast = 0.5;
}}}
のように = を使って指定します。
C++にはこのほかにも文字列型(char *やstd::string)、真偽値型(bool)などがありますが、ここでは詳しくは説明しません。これらの型の使い方については、これ以降のプログラミングの実例のなかで必要なところだけを説明していきます。詳細を知りたい型は、CやC++の入門書をご覧ください。
! クラス
クラスとは、関係する変数(データ)や関数(メソッド)をまとめたものです。
[[1.5節|1.5 Psychlopsの基本構造]]で概観したように、Psychlopsでは、画面(Canvas)や画像(Image)、図形(Ractangle)など、描画に関するさまざまなクラスを用意しています。各クラスには、その機能に関連した固有の関数があります。
たとえばImageは
* Image.draw()
* Image.pix(x, y, color)
* Image.rect(rect, color)
* Image.save(filename)
*Image. load(filename)
などさまざまな関数を持っています。
!! 関数
関数はには''返り値''と''引数''があります。たとえば累乗関数(power)を例に見てみましょう。
{{{
a = pow( 10, 3 );
}}}
この式を実行すると、変数aには10の3乗である1000が代入されます。累乗関数が必要とする値である10と3を引数といい、返される計算結果1000を返り値と呼びます。
pow関数の引数のうち、1番目は累乗される数、2番目は累乗する数です。順番や型は固定されていますのでご注意ください。順番を間違えると、計算結果も変わってきてしまいます。たとえば、
{{{
a = pow( 3, 10 );
}}}
を実行すると、3の10乗である59049がaに代入されます。
!! 関数の続け書き
Psychclopsでは、一部の関数を.でつなぎながら連続で書くことができます。
{{{
rect.centering().shift(x, y).draw(Color::red);
}}}
この書き方は他のC言語のプログラムではあまり見かけませんが、Psychlopsでは多用されています。プログラムの長さを短くし読みやすくすることができます。
この書き方ができるものは、関数の返り値として変数自身が返されるものに限ります。
!! 関数表の読み方
このマニュアルでは、各クラスの関数について表で説明が出てきます。この表の読み方を、Imageを表示する関数を題材に説明します。
|!Image::draw()|draw()|指定したオフスクリーンを描画します|
|~|draw(double left, double top)|指定したオフスクリーン領域を(left,top)を中心とした座標に設定します|
|~|~|double left: 描画するオフスクリーン領域の座標軸xを指定|
|~|~|double top: 描画するオフスクリーン領域の座標軸yを指定|
一番左の列は関数の名前を示します。ここでまたスコープ演算子が関数名の名前についていますが、これは1.5節と同様にその関数がどのクラスに所属しているかを示すためのものです。
これを見ると、実際のプログラムでも
{{{
Image::draw(10,10);
}}}
と書く様な気がしてしまいますが、いくつかのクラスをのぞいて^^*1^^このような書き方をすることはありません。なぜなら、実際のプログラムの中は、あるクラスに属する変数(インスタンス、と呼ばれます)に対しての命令しか行わないからです。Image型として宣言されたインスタンスに対してメソッドを実行するときには"."演算子を用います。たとえば、Image::draw()命令を実際に使うときには以下のような形になるでしょう。
{{{
Psychlops::Image img;
img.draw(10,10);
}}}
*1 Input, Display等のハードウェアと直結したクラスはインスタンスを宣言せずに使います。これは、これらのクラスがPsychlopsのイニシャライズ時にあらかじめ宣言されたデフォルトのインスタンスを持っていて、これに対するメソッドの実行を行うように設計されているためです。
上記の表は、Image::draw()というメソッドに
* Image::draw()
* Image::draw(double left, double top)
という二つの使い方があることを示しています。
二番目の列は、引数の違ういくつかのバージョンが列挙されています。
2つめの使い方Image::draw(double left, double top)では、括弧の中にコンマで区切られた2つの文字列があります。
これは、この使い方では関数の引数として2つの変数を代入することができることを示しています。
[img[image/FunctionNotation.png]]
一つめの引数はdouble leftと書かれています。これは、一つめの引数がdouble型の変数でなくてはいけないことを示しています。(ただし、後で書くようにleftと言う名前にする必要はありません。)このマニュアルの中ではそれぞれのメソッド型名(スペース)名前という形でそれぞれの使い方に必要な引数の形式を示しています。
実際に使用するときは、型名を書く必要はありません。left,topという2つの引数をこの関数に渡すときには以下のように書きます。
{{{
Psychlops::Image img;
double left=10,top=10;
img.draw(left,top);
}}}
一番右の列は、各バージョンでの引数の説明になっています。Image::draw(double left, double top)の右側の欄を見ると、各引数について、一番目が表示したいx座標、二番目がy座標であることがわかります。
各引数には名前がついていますが、関数を呼び出す際に引き渡す変数はこの名前である必要はありません。マニュアルでは
* image.draw(left, top)
となっていますが、プログラム中では
* image.draw(100, 200)
* image.draw( x, y )
など任意の値を渡すことが可能です。
Psychlopsには、プログラム内の変数をプログラムのユーザがインタラクティブに操作できるデモアプリケーションを簡単に作成するためのクラスIndependentが用意されています。
この節では、Independentクラスについて簡単に説明して、これを用いたプログラムの作成例について解説します。
[[2.1 独立変数Independent]]
[[2.2 デモ環境の表示]]
[[2.1 描画領域の宣言]]
[[2.1.1 Canvasの宣言]]
[[2.1.2 Canvasの基本構造と操作]]
[[2.1.3 Canvasのメンバ取得と変数の表示]]
[[2.2 図形描画のコマンド]]
[[2.2.1 画面上に点を描画する-Pointクラス1-]]
[[2.2.2 画面上に線を描画する]]
[[2.2.3 画面上に四角形を描画する-Rectangleクラス1-]]
[[2.2.4 画面上に円を描画する]]
[[2.2.5 描画する図形の色を指定する-Colorクラス-]]
[[2.2.6 画面上に縞を描画する-Rectangleクラス2-]]
[[2.2.7 画面上に縞を描画する-Pointクラス2-]]
[[2.2.8 運動する図形を描画する-Rectangleクラス3-]]
[[2.3 文字列の描画]]
この節ではPsychlopsのもっとも基本的なクラスであるCanvasクラスの宣言方法と初歩的な操作を説明します。
[[2.1.1 Canvasの宣言]]
[[2.1.2 Canvasの基本構造と操作]]
[[2.1.3 Canvasのメンバ取得と変数の表示]]
!Independentとは
Independentクラスは、プログラム中で独立変数に当たる変数を制御するためのオプション機能です。現在はデモ用のコンソールで使用することが出来ます。
通常、プログラムは画面を描画するなどの作業を行っています。プログラムの中にはたくさんの変数や関数が入っています。
[img[Independentなし|image/Independent1.png]]
プログラム中の変数をIndependentに登録すると、もともとのプログラムの動作を変えずにデモ用のコンソール機能などを追加することが出来ます。
[img[Independentあり|image/Independent2.png]]
!Independentクラスの設定
(Tiddly Wikiの機能上の都合、記号が全角で表示されていますが、実際は半角です)
|!Independent|<< 変数|Independentクラスに登録する変数を指定します。|
|~|| 文字列|変数につけるラベルを指定します|
|~|| Range|Independenクラスtに指定する変数の変動範囲を指定します。変動範囲チェックはデモコンソールなどからの書き換え時にしか行われないため、元のプログラムで変動範囲外に変化する場合は検出できません。|
|~|| 数値|デモコンソールでのキー押しあたり変化量を設定します。2つ続けて書いた場合、一方がShiftキーとの同時押し時の変化量になります。|
現在のところ(2007年11月現在)、Independentクラスに登録できる変数は数値型(int, double等)のみです。
!!Independentクラスへの登録
以下では、例として3つの変数をIndependentクラスに登録する書き方例を示します。
{{{
double rect_size = 100;
double rect_lum = 0.5;
double bg_lum = 0.2;
Range rng;
Independent << rect_size | "Rect Size" | 1< rng< 500 | 10.0 | 2.0 ;
Independent << rect_lum | "Rect Luminance" | 0.0<=rng<=1.0 | 0.1 | 0.05;
Independent << bg_lum | "BG Luminance" | 0.0<=rng<=1.0 | 0.1 | 0.05;
}}}
ここで注意することは、変数に適正な初期値を与えておくことです。もしも、Indpendentで設定される範囲の外に初期値が設定されてしまうと、実行時に変更することができなくなります。変数の宣言の時に忘れずに初期値の設定をしておくことをおすすめします。
!Canvasとは?
絵を書くときには描く筆や絵の具のほかに紙などのキャンバスが必要です。
この処理はコンピュータ上に絵を描くための"Canvas(描画のためのウィンドウ)"を用意します。
Canvasが確保されていないと描画処理を記述しても処理されません。
描画処理を行う前に必ずCanvasの宣言が必要となります。
!Canvasの宣言文
キャンバスを用意する命令には様々な書式があるのですが、その基本は以下の2つです。
一つは解像度、リフレッシュレートを変更せずに、画面いっぱいに領域を確保する
Canvas(Canvas::fullscreen)
もう一つは解像度、リフレッシュレートを変更して、画面いっぱいに領域を確保する
Canvas(int,int,int,double)
です。
後者で指定した画面モードが選べなかった場合は、例外が送出されプログラムは強制終了します。
そのほかに Canvas::windowスイッチを使ってウインドウを開いて画面を取得する方法があります。
この方法では描画の垂直同期信号への同期が保証されないので(くわしくは[[2.1.2 Canvasの基本構造と操作]]で説明します)、デモやプログラムのデバッグ時に使うとよいでしょう。
詳しくは[[関数一覧|Canvasの宣言]]をご覧ください。
* 参考[[Tips: Canvasとリフレッシュレートの詳細]]
|!Canvasの宣言|
|~|Canvas(Canvas::fullscreen, const Display disp)|>|!現在の画面モードで画面を確保する場合に使います|
|~|~|Canvasmode mode|[[表示モード|CanvasMode]](フルスクリーンorウィンドウ)を選択|
|~|~|const [[Display|Displayの指定値]] disp|[[表示するディスプレイ|Displayの指定値]]を選択(省略可能)|
|~|~|>|マルチディスプレイ環境では、最後の引数でどのディスプレイに表示するか指定できます。省略時はプライマリディスプレイになります。|
|~|Canvas(int width, int height, int colordepth, double refreshrate, const Display disp)|>|!画面モードを指定してフルスクリーン画面を確保します|
|~|~|int width|水平方向の解像度(画面の横幅)を指定|
|~|~|int height|垂直方向の解像度(画面の縦幅)を指定|
|~|~|int colordepth|カラーモード(ビット)を指定|
|~|~|double refreshrate|リフレッシュレート( Hz )を指定|
|~|~|const [[Display|Displayの指定値]] disp|[[表示するディスプレイ|Displayの指定値]]を選択(省略可能)|
|~|~|>|指定した画面モードが選べなかった場合は、例外が送出されプログラムは強制終了します。|
!!宣言文の書き方
{{{
<例1: Canvas::fullscreenオプションを使った宣言例>
#include <psychlops.h>
using namespace Psychlops;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
}
}}}
{{{
<例2: 各属性値を指定した宣言例>
#include <psychlops.h>
using namespace Psychlops;
void psychlops_main() {
Canvas sampleB(1024,768,32,60.0);
}
}}}
{{{
<例2: 各属性値を指定した宣言例>
#include <psychlops.h>
using namespace Psychlops;
void psychlops_main() {
Canvas sampleC(Canvas1024,768,32,60.0);
}
}}}
@@◎クラスとインスタンスについて@@
上記のソースでは「psychlops_main()」クラスの中で
sampleA,sampleBという名前のCanvasを宣言しています。
これらのプログラム内でCanvas型(クラス)として宣言された変数はインスタンスと呼ばれます。
マニュアル上では、ある命令hoge()の形式を説明するときにCanvas::hoge()と書きますが、実際にプログラム内で宣言されたCanvasに対して命令を実行したいときは、このインスタンス名を使って『インスタンス名.命令文()』という形式で記述します。たとえば、Sample.hoge(hoge)といった形です。より具体的な例はこれ以降に何回も現れます。
ここで注意しなくてはならない点として、ふつうCanvasを宣言する場所はpsychlops_main() 内であることです。つまり、この宣言名はpsychlops_main() 内でしか有効ではありません。
psychlops_main()で呼び出す他の関数内でCanvasを使用したいときは、その関数にCanvasのポインタを引き渡すかDisplayクラスを使って描画する必要があります。
* 参考[[Tips::CanvasクラスとDisplayクラス]]
Psychlopsでは2つの画面を交互にディスプレイに提示します。
この2つの画面が対となって、1つのCanvasを構成しています。たとえば、紙に裏表があるようなものだと思ってください。
この2枚の画面に描画をすることとflip()メソッドを使って表画面と裏画面を切り替えることがCanvasクラスに対する主な操作です。
[img[image/canvasstructure.png]]
!Canvas::clear() -画面を塗りつぶす-
はじめにCanvasを何かの色で塗りつぶしてみましょう。
そのための処理がCanvas::clear()命令です。
書式はCanvas::clear([[Psychlops::Color]] col,TargetSurface)です。いずれの引数も省略可能です。
|!Canvas::clear()|clear([[Psychlops::Color]] col,TargetSurface)|指定したCanvasを指定色で塗りつぶします|
|~|~|[[Psychlops::Color]] col :塗りつぶす色を指定|
|~|~|TargetSurface:塗りつぶす画面を指定|
|~|clear(TargetSurface)|指定したCanvasを塗りつぶします。色の指定には[[Canvas::setClearColor()]]命令を使用します|
|~|~|TargetSurface:塗りつぶす画面を指定|
2つめの引数は少しわかりにくいかもしれません。
clear()命令は2つめの引数を指定することで、今ディスプレイに表示されている画面(紙の表)と、表示されていない画面(紙の裏)のいずれを(あるいは両方を)塗りつぶすのかを指定することができます。
省略した場合は表示されていない画面を塗りつぶします。
!!Canvas::clear()の書き方
{{{
<例1>
#include <psychlops.h>
using namespace Psychlops;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
sampleA.clear(Color::white,Canvas::FRONT);
}
}}}
今ディスプレイに表示されている画面(紙の表)を白く塗りつぶせ、という命令文ですが
これでは描画が一瞬で結果がわかりにくいですね。
「while(!Input::get(Keyboard::spc));」を追加してスペースキーが押されるまで今ディスプレイに表示されている画面(紙の表)が白く表示されるかを確認できるようにしましょう。
この命令の詳細については、[[4.1節|4.1 外部装置(キーボード/マウス)の操作内容を取得する]] で詳しく説明します。今はこの文がスペースキーの入力待ちをする文であると思っていてください。
{{{
<例2>
#include <psychlops.h>
using namespace Psychlops;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
sampleA.clear(Color::white,Canvas::FRONT);
while(!Input::get(Keyboard::spc));
}
}}}
!Canvas::flip() -画面を入れ替える-
上の例では、オプションを使って現在表示されている画面に対して描画を実行しました。
しかし、これは例外的な描画で、Psychlopsでは描画処理は表示されていない画面(裏画面)に対して行われます。
従って、描画内容を画面に反映させるためには、表示画面と裏画面を入れ替える必要があります。
この表と裏の2つの画面を入れ替え処理を行う命令がCanvas::flip()命令です。
つまり、描画内容はCanvas::flip() 処理で画面を入れ替えることで初めて画面上に表示されます。
この命令は画面の垂直同期信号(=ビデオカードが1枚の画面を書ききったことを示す信号; Vsync)に同期して、画面の切り替えを行います。
* 参考 [[Tips: Canvas::flip()とコマ落ち]]
|!Canvas::flip()|flip(int)|int:入力値のリフレッシュ分描画内容を表示します|
|~|~|引数を入力しない場合(flip())、flip(1)が指定されます|
|~|flipAfter(int wait)|int wait回のリフレッシュ後まで待ってからflip()を実行して描画内容を画面に反映させます|
|~|flipAndWait(int wait)|もっとも近い垂直同期のタイミングでflip()を実行した後に、int wait回ののリフレッシュ分の表示を予約します。次にflip()命令がコールされても、int wait回ののリフレッシュ分が経過するまでは何も起こりません(flip()と全く同じ効果ですが、引数は省略不可)|
flip()系の命令は画面描画の時間制御をする上でもっとも重要な命令です。上の表だけではわかりにくいでしょうから、図と実例を用いて各命令の働きをもう少し詳しく見ていきましょう。
flipAfter(), flipAndWait()命令の動きを図解すると以下のようになります。以下の図ではflipAfter(3)とflip(3)(あるいはflipAndWait(3))の場合を例としてあげました。
[img[image/psychlops_flip.png]]
!!Canvas::flip()の書き方1
{{{
<例3>
#include <psychlops.h>
using namespace Psychlops;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
sampleA.clear(Color::blue,Canvas::BACK);
while(!Input::get(Keyboard::spc));
sampleA.flip();
}
}}}
例2では今ディスプレイに表示されている画面(紙の表)の色を塗りつぶしましたが
今回は表示されていない画面(紙の裏)を塗りつぶし、それをflip()命令で表画面に入れ替えました。
!!Canvas::flip()の書き方2
次は、flip()が実行されるタイミングを制御してみましょう。
flip()命令の引数を省略すると、flip()命令は、描画の計算が終了した時点からもっとも早い同期タイミングで画面の切り替えを行います。
{{{
<例4>
#include <psychlops.h>
using namespace Psychlops;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
sampleA.clear(Color::blue,Canvas::BACK);
sampleA.flip(60);
sampleA.clear(Color::cyan,Canvas::BACK);
sampleA.flip(60);
while(!Input::get(Keyboard::spc));
}
}}}
青→シアンに画面の色が塗りつぶされるのを確認できたでしょうか。
次はflipAfter()で同様のソースを実行します。
{{{
<例5>
#include <psychlops.h>
using namespace Psychlops;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
sampleA.clear(Color::blue,Canvas::BACK);
sampleA.flipAfter(60);
sampleA.clear(Color::cyan,Canvas::BACK);
sampleA.flipAfter(60);
while(!Input::get(Keyboard::spc));
}
}}}
<例4>と<例5>の結果の差が見えたでしょうか。
<例4>は60フレームのリフレッシュ分描画内容を表示したのに対し
<例5>は60フレームのリフレッシュ分後、描画内容を表示しました。
<例6>はflipAndWait()による画面入替えの確認をします。
こちらはflip();と同様の結果を返します。
<例4>と同じ結果が返るか確認してください。
{{{
<例6>
#include <psychlops.h>
using namespace Psychlops;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
sampleA.clear(Color::blue,Canvas::BACK);
sampleA.flipAndWait(60);
sampleA.clear(Color::cyan,Canvas::BACK);
sampleA.flipAndWait(60);
while(!Input::get(Keyboard::spc));
}
}}}
!Canvas::getXXXX()
Psychlopsではより複雑な描画に必要なCanvasの様々な画面情報を、Canvas::getXXXX()命令を使って取得します(XXXXにはそれぞれの情報に対応した名前が入ります)。
|!Canvas::getXXXX()|getCenter()|画面の中心座標(x,y)を取得します|
|~|getHcenter()|横方向の中心座標(x)を取得します|
|~|getVcenter()|縦方向の中心座標(y)を取得します|
|~|getHeight()|画面の高さ(ピクセル)を取得します|
|~|getWidth()|画面の幅(ピクセル)を取得します|
|~|getColorDepth()|カラーモード(ビット)を取得します|
|~|getRefreshRate()|リフレッシュレート(Hz)を取得します|
[img[image/canvasget.png]]
!!Canvas::getXXXX()の書き方
実際に取得してみます。
{{{
<例1>
#include <psychlops.h>
using namespace Psychlops;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
sampleA.getCenter();
sampleA.getHcenter();
sampleA.getVcenter();
sampleA.getHeight();
sampleA.getWidth();
sampleA.getColorDepth();
sampleA.getRefreshRate();
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
このコードを実行するだけでは、取得結果を確認できません。
次に取得結果を確認するため、画面に取得した変数の値を表示してみましょう。
!Canvas::var()とCanvas::msg()
取得した変数の値を画面に表示するにはCanvas::var()命令を使います。
|!Canvas::var()|var(string str, double x, double y,[[Psychlops::Color]] col, bool fillForward)|
|~|~|>|>|string str|画面表示をする変数を指定|
|~|~|>|>|double x|画面表示をする座標(x)を指定|
|~|~|>|>|double y|画面表示をする座標(y)を指定|
|~|~|>|>|[[Psychlops::Color]] col|描画する点の色を指定(設定しない場合、白が設定される)|
|~|~|~|bool fillForward|表示位置の指定|true |指定座標(x,y)から左部分に表示する|
|~|~|~|~|~|false |指定座標(x,y)から右部分に表示する|
|~|~|~|~|>|>|(指定しない場合、falseが設定される)|
さらに、わかりやすくするためにラベルをつけましょう。
定型の短い文章を画面上に表示する方法としてCanvas::msg()命令があります。
|!Canvas::msg()|msg(char* string,double x,double y,[[Psychlops::Color]] col)|char* stringを座標(x,y)に指定色で表示します|
|~|~|schar* string:画面表示をする文字列を指定|
|~|~|double x:画面表示をする座標(x)を指定|
|~|~|double y:画面表示をする座標(y)を指定|
|~|~|[[Psychlops::Color]] col:描画する点の色を指定(設定しない場合、白が設定される)|
|~|msg(string str,double x,double y,[[Psychlops::Color]] col)|string strを座標(x,y)に指定色で表示します|
|~|~|string str:画面表示をする文字列を指定|
|~|~|double x:画面表示をする座標(x)を指定|
|~|~|double y:画面表示をする座標(y)を指定|
|~|~|[[Psychlops::Color]] col:描画する点の色を指定(設定しない場合、白が設定される)|
|[img[image/canvasvar.png]]|[img[image/canvasmsg.png]]
!!Canvas::var()の書き方
{{{
<例2>
#include <psychlops.h>
using namespace Psychlops;
int a1,a2,a3,a4,a5;
double d1,d2,d3,x,y;
Psychlops::Point point1;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
point1=sampleA.getCenter();
d1=sampleA.getHcenter();
d2=sampleA.getVcenter();
a1=sampleA.getHeight();
a2=sampleA.getWidth();
a3=sampleA.getColorDepth();
d3=sampleA.getRefreshRate();
//get命令の内容を画面表示する
x=point1.getX();
y=point1.getY();
sampleA.msg("getcenter_x:",50,200,Color::yellow);
sampleA.var(x,200,200,Color::yellow,true);//getCenter:X
sampleA.msg("getcenter_y:",50,250,Color::yellow);
sampleA.var(y,200,250,Color::yellow,true);//getCenter:Y
sampleA.msg("getHcenter:",50,300,Color::green);
sampleA.var(d1,200,300,Color::green,true);//getHcenter
sampleA.msg("getVcenter:",50,350,Color::green);
sampleA.var(d2,200,350,Color::green,true);//getVcenter
sampleA.msg("gettHeight:",50,400,Color::red);
sampleA.var(a1,200,400,Color::red,false);//getHeight
sampleA.msg("getWidth:",50,450,Color::red);
sampleA.var(a2,200,450,Color::red,true);//getWidth
sampleA.msg("getColorDepth:",250,200);
sampleA.var(a3,500,200);//getColorDepth
sampleA.msg("getRefreshRate:",250,250);
sampleA.var(d3,500,250);//getRefreshRate
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
このコードを実行すると、宣言されたCanvasの各属性値が画面上に表示されます。
Independentクラスを使って、デモ環境を自動的に構成することが出来ます。
以下では具体例を挙げて簡単なデモ環境の構築を説明します。
1章で述べたやり方に従って、新規プロジェクトを作成すると、cppファイルの中に自動的にPsychlopsのデモプログラムが
書き込まれています。このデモプログラムはIndependentクラスを用いた簡単なデモンストレーションのソースファイルになっています。ここでは、このデモプログラムを題材にIndependentを用いたデモ環境の構築について説明します。
[[Psychlopsプログラムのデフォルトテンプレート]]
[[2.2.1 プログラムの基本構造]]
[[2.2.2 デモ環境に使う描画命令の設定]]
[[2.2.3 デモ環境の実行]]
[[2.1|2.1 描画領域の宣言]]節はCanvasそのものについて説明しました。
本節ではCanvas上に点や線、図形を描画します。
[[2.2.1 画面上に点を描画する-Pointクラス1-]]
[[2.2.2 画面上に線を描画する]]
[[2.2.3 画面上に四角形を描画する-Rectangleクラス1-]]
[[2.2.4 画面上に円を描画する]]
[[2.2.5 描画する図形の色を指定する-Colorクラス-]]
[[2.2.6 画面上に縞を描画する-Rectangleクラス2-]]
[[2.2.7 画面上に縞を描画する-Pointクラス2-]]
[[2.2.8 運動する図形を描画する-Rectangleクラス3-]]
このデモプログラムは大きく分けて2つの部分からなっています。
{{{
void RectLuminance() {
double rect_size = 100;
double rect_lum = 0.5;
double bg_lum = 0.2;
Psychlops::Rectangle rect(rect_size,rect_size);
rect.centering();
Range rng;
Independent << rect_size | "Rect Size" | 1< rng< 500 | 10.0 | 2.0 ;
Independent << rect_lum | "Rect Luminance" | 0.0<=rng<=1.0 | 0.1 | 0.05;
Independent << bg_lum | "BG Luminance" | 0.0<=rng<=1.0 | 0.1 | 0.05;
while(!Input::get(Keyboard::esc)) {
Display::clear(Color(bg_lum));
rect.resize(rect_size,rect_size);
rect.display(rect_lum);
Display::flip();
}
}
}}}
の部分と
{{{
void psychlops_main() {
Canvas display(Canvas::fullscreen);
Procedure p;
p.setDesign(Procedure::DEMO);
p.setProcedure(RectLuminance);
p.run();
}
}}}
の部分です。
ぱっと見てわかることは、Psychlops_mainの中に描画命令が存在しないことでしょう。実際の描画命令はPsychlops_main()の上にある~RectLuminance()という関数の中にかかれています。
代わりに個々まで一度も触れられていないようなProcedureと言うクラスのインスタンスが宣言されて、これに対してsetDesign(), setProcedure(), run()といったメソッドがかかれています。
実はデモを構築する時には、このProcedureクラスの詳細については理解する必要はありません。
setProcedure()の引数としてかかれている文字列が、実際の描画を行う関数としてプログラム中にかかれていれば、後の部分は変更しなくてもデモ環境を構築することができます。
つまり、もし実際の描画命令を書く関数の名前が~DemoDraw()であったとすると、この描画内容を使ったデモンストレーションを構築するためには、
{{{
void DemoDraw(){
......
}
void psychlops_main() {
Canvas display(Canvas::fullscreen);
Procedure p;
p.setDesign(Procedure::DEMO);
p.setProcedure(DemoDraw);
p.run();
}
}}}
と書けば良いと言うことです。デフォルトのテンプレートと比較して、p.setProcedure()の行だけが異なっていることに注意してください。
!Canvas::pix()
Canvas上に点を描画するための命令です。
Canvasに点や線等を描画するときに必要なのは「どこに」「何色」のものを描画するか、です。
ここでの「どこが」が点を描画する「座標」、「何色」が「色の指定」となります。
座標はCanvasの左上端が座標の基点(x=0,y=0)となっています。
色の指定についての詳細は[[2.2.5 描画する図形の色を指定する-Colorクラス-]]を参照してください。ここでは、必要最小限の命令のみを使用・説明します。
|!Canvas::pix()|pix(double x,double y,[[Psychlops::Color]] col) |>|!座標(x,y)に指定色の点を描画します<例1>|
|~|~|double x|描画する点の座標軸xを指定|
|~|~|double y|描画する点の座標軸yを指定|
|~|~|[[Psychlops::Color]] col|描画する点の色を指定|
|~|pix([[Psychlops::Point]] point,[[Psychlops::Color]] col) |>|!Point座標(x,y)に指定色の点を描画します^^*1^^<例4>|
|~|~|[[Psychlops::Point]] point |座標(x,y)を指定|
|~|~|[[Psychlops::Color]] col|描画する点の色を指定|
|~|pix(int dotsCnt,double* xArray,double* yArray),[[Psychlops::Color]]* colArray)|>|!配列の座標(x[],y[])に指定色(配列を用いて複数色指定)の点を描画します<例2>|
|~|~|int dotsCnt|描画する点の数を指定(後ろの引数の配列の個数を超えてはいけない)|
|~|~|double* xArray|描画する点の座標軸xが格納された配列名(ポインタ)を指定|
|~|~|double* yArray|描画する点の座標軸yが格納された配列名(ポインタ)を指定|
|~|~|[[Psychlops::Color]]* colArray|描画する点の色が格納された配列名(ポインタ)を指定|
|~|pix(int dotsCnt,double* xArray,double* yArray,[[Psychlops::Color]] col)|>|!配列の座標(x[],y[])に指定色(一色のみ)の点を描画します<例3>|
|~|~|int dotsCnt|描画する点の数(配列数)を指定|
|~|~|double* xArray|描画する点の座標軸xが格納された配列名(ポインタ)を指定|
|~|~|double* yArray|描画する点の座標軸yが格納された配列名(ポインタ)を指定|
|~|~|[[Psychlops::Color]] col|描画する点の色を指定|
|~|pix(int dotsCnt,[[Psychlops::Point]]* pointArray,[[Psychlops::Color]]* colArray)|>|!配列Point座標(x[],y[])に指定色の点を描画します^^*1^^<例5>|
|~|~|int dotsCnt|描画する点の数(配列数)を指定|
|~|~|[[Psychlops::Point]]* pointArray |配列の座標(x[],y[])が格納された配列名(ポインタ)を指定|
|~|~|[[Psychlops::Color]]* colArray|描画する点の色が格納された配列名(ポインタ)を指定|
^^*1 次のPoint::set()で説明します。^^
[img[image/canvaspix.png]]
!!Canvas::pix()の書き方
座標(50,50)に青の点を描画します。
{{{
<例1>
#include <psychlops.h>
using namespace Psychlops;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
while(!Input::get(Keyboard::spc)){
sampleA.pix(50,50,Color::blue);
sampleA.flip();
}
}
}}}
次は一命令で複数の異なる輝度の点を描画します。一つの命令を繰り返し実行する[[for文|1.6.4 for命令]]を使用しなくても複数個の点が描画される点に注目してください。
{{{
<例2>
#include <psychlops.h>
using namespace Psychlops;
//色の配列を宣言. 輝度値は0.0-1.0の範囲で指定
Psychlops::Color colArray[3]={1.0,0.5,0.25};
//X座標の配列を宣言
double xArray[3] = {50,100,150};
//Y座標の配列を宣言
double yArray[3] = {50,50,50};
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
sampleA.pix(3,xArray,yArray,colArray);
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
次は<例2>と同様に一命令で複数の点を描画しますが、全点の色は同一です。
{{{
<例3>
#include <psychlops.h>
using namespace Psychlops;
//X座標の配列を宣言
double xArray[3] = {50,100,150};
//Y座標の配列を宣言
double yArray[3] = {50,50,50};
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
sampleA.pix(3,xArray,yArray,Psychlops::Color(1.0,0.0,0.0));//3つめの引数は描画色に赤を指定
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
@@◎カラーについて@@
<例1>~<例3>で使用している[[Psychlops::Color]]型について簡単に説明します。
PsychlopsではRGB,GRAY(輝度)の色を描画することが可能です。
<例1>で青の点を描画するため「Color::blue」という命令を使用しています。
Psychlopsではいくつかのよく使用する色をあらかじめ予約語として設定してあります。
これらのプリセットされた色は「Color::色名」と記述することで、使用することができます。
あらかじめプリセットされた色については[[Color::XXXX]]を参照してください。
<例2>では輝度値を使用しています。
輝度値は0.0(黒)~1.0(白)の範囲で指定されています。
Psychlops::Color colArray[3]={1.0,0.5,0.25};
の命令でそれぞれの点に輝度値が設定されました。
<例3>では赤の点を描画しています。
Psychlops::Color型の変数に3つの引数を指定したときは、それぞれの値が順に赤、緑、青の各画素の輝度の指定として解釈されます。
red,green,blueはそれぞれ0.0~1.0の範囲で指定されます。
この例では[[Psychlops::Color]](red=1.0,green=0.0,blue=0.0)と指定することで赤が設定されました。
!Point::set()
今まで座標はそれぞれx,y座標の変数を作成して値を設定していました。
今回はPointクラスを使用して座標(x,y),ないし座標X,Yを指定し点を描画します。
Point::set()を使用し座標を取得することでソースの簡略化がなされ
また、修正がしやすくなります。
|!Point::set()|set(double x, double y)|座標(x,y)の値を設定します|
|~|~|double x:座標xを指定|
|~|~|double y:座標yを指定|
|~|setX(double val)|X座標の値を設定します|
|~|~|double val :座標xを指定|
|~|setY(double val)|Y座標の値を設定します|
|~|~|double val :座標yを指定|
!!Point::set()の書き方
座標(50,50)に青の点を描画します。
<例1>との差分は、直接座標を記述するかPoint::set()で設定するか、だけです。
{{{
<例4>
#include <psychlops.h>
using namespace Psychlops;
//Point型を宣言
Psychlops::Point point1;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
//座標(X,Y)を設定
point1.set(50,50);
sampleA.pix(point1,Psychlops::Color(0.0,0.0,1.0));
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
次はX,Y座標それぞれ座標を取得し、一命令で複数の点を描画します。
<例2>との差分は、直接座標を記述するかPoint::set()で設定するか、だけです。
{{{
<例5>
#include <psychlops.h>
using namespace Psychlops;
//色の配列を宣言
Psychlops::Color colArray[3]={1.0,0.5,0.25};
//座標(X,Y)の配列を宣言
Psychlops::Point pointArray[3];
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
//座標(X[],Y[])にそれぞれ値((50,50),(100,50),(150,50)を設定
for(int i=0; i<3; i++){
pointArray[i].setX((i+1)*50);
pointArray[i].setY(50);
}
sampleA.pix(3,pointArray,colArray);
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
!Point::centering()
今度は点を画面中心に描画してみましょう。
画面の大きさは環境により変化します。
今までの命令では画面中心に点を描画することは難しいです。
そこでPoint::centering()を使用します。
この命令を使用することで、点を自動的に画面中心に移動します。
|!Point::centering()|centering()|座標(x,y)の点を画面の中心に移動します|
|~|centering(double h, double v)|任意の座標(h,v)に点を移動します|
|~|~|double h:移動するx軸の値を指定|
|~|~|double v:移動するy軸の値を指定|
!Point::centering()の書き方
{{{
<例6>
#include <psychlops.h>
using namespace Psychlops;
//座標(X,Y)を宣言
Psychlops::Point point1;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
//座標(X,Y)を画面中心に移動
point1.centering();
//点を描画する
sampleA.pix(point1,Color::red);
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
!Point::shift()
この命令もPoint::centering()と同様に点に対する移動命令です。
元の座標(x,y)を基点に入力値(h,v)だけ移動します。
|!Point::shift()|shift(double h, double v)|座標(X,Y)を(h,v)だけ移動します|
|~|~|double h:右水平方向に移動する値を指定|
|~|~|double v:下垂直方向に移動する値を指定|
[img[image/pointshift.png]]
!!Point::shift()の書き方
<例6>を元に画面中央から(100,100)移動した青の点を描画します。
{{{
<例7>
#include <psychlops.h>
using namespace Psychlops;
//座標(X,Y)を宣言
Psychlops::Point point1,point2;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
//座標(X,Y)を画面中心に移動
point1.centering();
//座標(X,Y)を画面中心に移動し、更に(100,100)移動する
point2.centering().shift(100,100);
//点を描画する
sampleA.pix(point1,Color::red);
sampleA.pix(point2,Color::blue);
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
画面中央の赤色の点(point1)と画面中央の右下に描画された青色の点(point2)が確認できたでしょうか。
さて、次に実際に描画を行っている関数~RectLuminance()について見てみましょう。[[2.2.1節|2.2.1 プログラムの基本構造]]で触れたとおり、デフォルトのデモプログラムでは、デモをする本体がpsychlops_mainとは別の関数になっています。
{{{
void RectLuminance() {
double rect_size = 100;
double rect_lum = 0.5;
double bg_lum = 0.2;
Psychlops::Rectangle rect(rect_size,rect_size);
rect.centering();
Range rng;
Independent << rect_size | "Rect Size" | 1< rng< 500 | 10.0 | 2.0 ;
Independent << rect_lum | "Rect Luminance" | 0.0<=rng<=1.0 | 0.1 | 0.05;
Independent << bg_lum | "BG Luminance" | 0.0<=rng<=1.0 | 0.1 | 0.05;
while(!Input::get(Keyboard::esc)) {
Display::clear(Color(bg_lum));
rect.resize(rect_size,rect_size);
rect.display(rect_lum);
Display::flip();
}
}
}}}
現段階ではこのデモ環境に使う描画関数は引数をとることができません。同様に値を返すこともできないので、かならずvoid XXXXXXX()という形で宣言が行われることになります。[[2.2.1節|2.2.1 プログラムの基本構造]]でも触れたとおり、名前は何でもかまいません。
次に中身を見てみると、はじめに変数の宣言とIndependentクラスへの変数の登録が行われています。
この部分は、[[前節2.1|2.1 独立変数Independent]]で上げた例と全く同じです。良くわからなくなったら[[2.1節|2.1 独立変数Independent]]も参照してみてください。
{{{
double rect_size = 100;
double rect_lum = 0.5;
double bg_lum = 0.2;
Range rng;
Independent << rect_size | "Rect Size" | 1< rng< 500 | 10.0 | 2.0 ;
Independent << rect_lum | "Rect Luminance" | 0.0<=rng<=1.0 | 0.1 | 0.05;
Independent << bg_lum | "BG Luminance" | 0.0<=rng<=1.0 | 0.1 | 0.05;
}}}
次の部分では、これまでにも見てきたようにwhile文を使ってループが設定され、その中に描画命令がかかれています。デモ実行は永久ループさせておきます。ここではESCキーを押すと終了するように設定されています。
{{{
while(!Input::get(Keyboard::esc)) {
Display::clear(Color(bg_lum));
rect.resize(rect_size,rect_size);
rect.display(rect_lum);
Display::flip();
}
}}}
ここでは、描画時に"Display::"命令が使われていることに注意してください。
Canvasクラスの宣言はPsychlops_main()の中で行われるので、ここで宣言されたCanvas名displayを使うことはできません。たとえば、1行目はdisplay.clear(Color(bg_lum));と書いてしまうとコンパイル時にエラーとなってしまいます。そこでデフォルトのCanvasへの描画をするためのクラス"Display::"を用いて宣言されたCanvasへの描画を行っています。
!Canvas::line()
Canvas上に線を描画するためにはCanvas::line()命令を使います。
|!Canvas::line()|line(double x1, double y1, double x2, double y2, [[Psychlops::Color]] col)|>|!開始座標(X1,Y1)から終端座標(X2,Y2)まで指定色の線を描画します|
|~|~|double x1|描画する線の開始座標x1を指定|
|~|~|double y1|描画する線の開始座標y1を指定|
|~|~|double x2|描画する線の終端座標x2を指定|
|~|~|double y2|描画する線の終端座標y2を指定|
|~|~|[[Psychlops::Color]] col|描画する点の色を指定|
|~|line([[Psychlops::Point]] point1, [[Psychlops::Point]] point2,[[Psychlops::Color]] col)|>|!開始Point座標(x1,x1)から終端Point座標(X2,Y2)まで指定色の線を描画します|
|~|~|[[Psychlops::Point]] point1|開始座標(x1,y1)を指定|
|~|~|[[Psychlops::Point]] point2|終端座標(x2,y2)を指定|
|~|~|[[Psychlops::Color]] col|描画する点の色を指定|
画面上に線を描画するためには、「どこから」「どこに」「何色」の線を引くのかの指定が必要です。
「どこから」は開始座標(x1,y1)、「どこに」は終端座標(x2,y2)、「何色」は[[Psychlops::Color]]型で指定します。
座標はCanvasの左上端が座標の基点(x=0,y=0)となっています。
これらの座標はPoint型の変数を使用して指定することもできます。
[img[image/canvasline.png]]
!!Canvas::line()の書き方
座標(100,100)から(300,300)まで赤い線を描画します。
{{{
<例1>
#include <psychlops.h>
using namespace Psychlops;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
sampleA.line(100, 100,300,300,Color::red);
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
次に[[Psychlops::Point]]型を使用して黄色の線を描画します。
{{{
<例2>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Point point1,point2;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
//座標(x,y)を設定
point1.set(100,100);
point2.set(300,300);
//線を描画
sampleA.line(point1,point2,Color::yellow);;
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
[[2.2.1節|2.2.1 プログラムの基本構造]]、[[2.2.2節|2.2.2 デモ環境に使う描画命令の設定]]で作成したプログラムを実行すると、デモ環境を使用したデモを使うことができます。
画面左上にデモ環境コンソールが表示されています。コンソールには、Independentで指定した変数が列挙されています。
[img[部分行列|image/console_1.png]]
コンソールで明るく表示されている変数が変更対象となる(アクティブな)変数です。アクティブな変数は上下(↓↑)キーで切り替えることができます。
[img[部分行列|image/console_2.png]]
コンソールでは、左右キーで変数の値を変更することができます(減←、→増)。shiftキーを押しながら←→キーを押すと、変量が変わります。
[img[部分行列|image/console_3.png]]
[img[部分行列|image/console_4.png]]
PsychlopsでCanvasと並んで重要なクラスにRectangleクラスがあります。
RectangleはCanvas上に四角形の領域を設定する命令です。
この領域の設定はコピーやペーストのために使用することもできますし、領域自体を塗りつぶすことで四角形を表示することもできます。
PsychlopsにおけるCanvas上の描画はほとんどこのRectangleクラスを使用して行うことになります。
!Rectangle::set()
Rectangle::set()は四角形の領域を設定します。
領域の設定は、四角形の左上座標(x1,y1)と四角形の右下座標(x2,y2)を指定することで行います。
また色指定は[[Psychlops::Color]]型で指定します。
|!Rectangle::set()|set(double width, double height)|>|!width×heightの四角形を設定します|
|~|~|double width|描画する四角形の横幅|
|~|~|double height|描画する四角形の縦幅|
|~|set(double l, double t, double r, double b)|>|!左上座標(l,t)から右下座標(r,b)までの四角形を設定します|
|~|~|double l|描画する四角形の左上座標x1を指定|
|~|~|double t|描画する四角形の左上座標y1を指定|
|~|~|double r|描画する四角形の右下座標x2を指定|
|~|~|double b|描画する四角形の右下座標y2を指定|
|~|set([[Psychlops::Point]] point1, [[Psychlops::Point]] point2)|>|!左上Point座標(x1,x1)から右下Point座標(X2,Y2)までの四角形を設定します|
|~|~|[[Psychlops::Point]] point1|左上座標(x1,y1)を指定|
|~|~|[[Psychlops::Point]] point2|右下座標(x2,y2)を指定|
[img[image/rectset.png]]
!Canvas::rect()
画面上に四角形を描画するにはCanvas::rect()命令を使用します。
Rectangle::set命令で四角形の領域設定が終わったらCanvas::rect命令で描画処理を行います。
この二つの命令は四角形の描画において対になっているので併せて覚えてください。
|!Canvas::rect()|rect([[Psychlops::Rectangle]] rect, [[Psychlops::Color]] col)|>|!四角形を指定色で描画します|
|~|~|([[Psychlops::Rectangle]] rect|描画する四角形を指定|
|~|~|[[Psychlops::Color]] col|描画する四角形の色を指定|
[img[image/canvasrect.png]]
!!Rectangle::set()とCanvas::rect()の書き方
Rectangleを宣言して、画面上に四角形を描画して見ましょう。
Rectangleを縦横のサイズのみを用いて宣言したときには、左上の座標は(0,0)に設定されています。
下の例では、200×100ピクセルの白色の四角形を画面左上隅に描画します。
{{{
<例1>
#include <psychlops.h>
using namespace Psychlops;
//Rectangleの宣言
Psychlops::Rectangle rect1;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
rect1.set(200,100);
sampleA.rect(rect1,Color::white);
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
次に(50,100)と(100,150)を頂点とする大きさ50×50ピクセルの四角形を描画します。
描画命令にはCanvas::rect()を使用し、色に黄色を指定しています。
{{{
<例2>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Rectangle rect1;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
rect1.set(50,100,100,150);
sampleA.rect(rect1,Color::yellow);
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
次に[[Psychlops::Point]]型を使用して頂点の座標を設定し、四角形を描画します。
描画命令にはCanvas::rect()を使用し、色に緑を指定しています。
{{{
<例3>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Rectangle rect1;
Psychlops::Point point1,point2;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
//座標(x,y)を設定
point1.set(100,100);
point2.set(300,300);
rect1.set(point1,point2);
sampleA.rect(rect1,Color::green);
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
ここまでは、基本的な四角形の設定について実行してきました。次に複数の同一四角形を描画します。
複数の同一四角形を描画するのにそれぞれに対応したRectangleを宣言することもできますが、よりよい方法があります。
一つだけRectangleを宣言して、これをシフトしながら描画する方法です。一つのはんこを紙の上に何度も押すようなものです。
Psychlopsでは、Canvas::flip()が起こるまでは、画面上に描画内容が表示されませんから、このような方法を用いても複数の四角形が同時に画面に表示されます。
<例1>の四角形を座標(0,0),画面中心,画面中心から(200,200)移動したところに四角形を描画してみましょう。
{{{
<例4>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Rectangle rect1,rect2,rect3;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
rect1.set(200,100);
rect2.set(rect1);
rect3.set(rect1);
rect2.centering();
rect3.centering().shift(200,200);
sampleA.rect(rect1,Color::white);
sampleA.rect(rect2,Color::white);
sampleA.rect(rect3,Color::white);
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
Canvas上に円を描画します。
座標はCanvasの左上端が座標の基点(x=0,y=0)となっています。
!Canvas::oval()
Rectangleを使用してこれに内接するような円を描画することもできます。
[[Rectangle.set()|2.2.3 画面上に四角形を描画する-Rectangleクラス1-]]命令で横軸と縦軸の直径を設定し、Canvas::oval()命令で円を描画します。
色指定は[[Psychlops::Color]]型で指定します。
|!Canvas::oval()|oval([[Psychlops::Rectangle]] rect, [[Psychlops::Color]] col)|[[Psychlops::Rectangle]] rectで指定された直径の円を指定色で塗りつぶし描画します|
|~|~|[[Psychlops::Rectangle]] rect:描画する円の直径|
|~|~|[[Psychlops::Color]] col:描画する点の円を指定|
[img[image/canvasoval.png]]
!!Canvas::oval()の書き方
Rectangleを宣言し、直径100ピクセルの白い円を描画します。
{{{
<例1>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Rectangle rect1;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
rect1.set(100,100);
sampleA.oval(rect1,Color::white);
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
今までCanvas、描画図形に色を塗りつぶしてきましたが、色の設定については詳しくは触れませんでした。
ここでは色設定について詳しく説明します。
!Color::set()
描画対象に対して色を設定します。
描画対象はCanvas,点・線・四角形・多角形・円等多岐にわたります。
色の設定はおおまかに2パターンあります。
RGBと~GRAYLevel(輝度)です。
RGBは色の三原色に基づいた赤・緑・青で構成されています。
引数が3つ(4つ)あるColor::set()命令はRGB(A)指定になります。
それぞれ値を0.0~1.0の範囲で持ち、3色のバランスで色を構成します。
赤・緑・青の全ての値が0.0の場合は黒、全ての値が1.0の場合は白になります。
4つめの引数(A)は色の透明度を決定します。0.0の場合は完全透過、1.0の場合は完全に不透明になります。省略したときはA=1.0(完全不透過)と解釈されます
~GRAYLevel(輝度)はグレースケールの輝度値を決定します。
引数は1つで0.0~1.0の範囲を持ち、0.0の場合は黒、1.0の場合は白になります。
カラーで描画したい場合はRGB(A)
白~黒を描画したい場合は~GRAYLevel
と覚えておくとよいでしょう。
|!Color::set()|set(double r, double g, double b, double a)|r,g,bと透明度aで構成されるカラー情報を設定します。|
|~|~|double r:赤の輝度を指定。範囲は0.0~1.0|
|~|~|double g:緑の輝度を指定。範囲は0.0~1.0|
|~|~|double b:青の輝度を指定。範囲は0.0~1.0|
|~|~|double a:透明度を指定。範囲は0.0~1.0 (0.0に近いほど透明度が高い)|
|~|set(double r, double g, double b)|r,g,bで構成されるカラー情報を設定します。|
|~|~|double r:赤の輝度を指定。範囲は0.0~1.0|
|~|~|double g:緑の輝度を指定。範囲は0.0~1.0|
|~|~|double b:青の輝度を指定。範囲は0.0~1.0|
|~|set(double gray)|グレースケールの輝度を設定します。|
|~|~|double gray: 輝度を指定。範囲は0.0~1.0|
!!Color::set()の書き方
RBGで様々な色を描画してみましょう。
{{{
<例1>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Rectangle rect1,rect2,rect3;
Psychlops::Color col1,col2,col3;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
sampleA.clear(Color(0.25,0.25,0.25));
rect1.set(50,50);
rect2.set(rect1);
rect3.set(rect1);
rect1.centering().shift(-100,0);
rect2.centering();
rect3.centering().shift(100,0);
//色の設定
col1.set(0.7,0.2,0.4);
col2.set(0.5,0.8,1.0);
col3.set(0.2,0.2,0.5);
sampleA.rect(rect1,col1);
sampleA.rect(rect2,col2);
sampleA.rect(rect3,col3);
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
では次に透明度の値を変えたときにどう描画されるか確認しましょう。
色は赤(固定)で透明度だけ0.0,0.5,1.0と変化させてみます。
{{{
<例2>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Rectangle rect1,rect2,rect3;
Psychlops::Color col1,col2,col3;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
sampleA.clear(Color(0.25,0.25,0.25));
rect1.set(50,50);
rect2.set(rect1);
rect3.set(rect1);
rect1.centering().shift(-100,0);
rect2.centering();
rect3.centering().shift(100,0);
//色の設定
col1.set(1.0,0.0,0.0,0.25);
col2.set(1.0,0.0,0.0,0.5);
col3.set(1.0,0.0,0.0,1.0);
sampleA.rect(rect1,col1);
sampleA.rect(rect2,col2);
sampleA.rect(rect3,col3);
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
今度はグレースケールで描画してみましょう。
{{{
<例3>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Rectangle rect1,rect2,rect3;
Psychlops::Color col1,col2,col3;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
rect1.set(50,50);
rect2.set(rect1);
rect3.set(rect1);
rect1.centering().shift(-100,0);
rect2.centering();
rect3.centering().shift(100,0);
//色の設定
col1.set(0.1);
col2.set(0.5);
col3.set(1.0);
sampleA.rect(rect1,col1);
sampleA.rect(rect2,col2);
sampleA.rect(rect3,col3);
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
!Canvas::setGammaValue()
他の多くの描画ライブラリ同様、CRTの各画素における輝度レベルとPsychlopsで指定する輝度レベルは線形の関係を維持しません。
これを簡易的に補正するために、R,G,Bの各色チャンネルについてGamma関数を用いた補正を行う方法が広く用いられています。
Psychlopsでは、このGamma関数の係数を指定することで、Gamma補正された色を表示することが可能です。
このための命令として、setGammaValue()命令があります。
|!Canvas::setGammaValue()|setGammaValue(double Cr, double Cg, double Cb)|この命令以降実行されるPsychlops::Colorに対してGamma補正を自動的に行います|
|~|~|Cr: R(赤)チャンネルのガンマ係数|
|~|~|Cg: G(緑)チャンネルのガンマ係数|
|~|~|Cb: B(青)チャンネルのガンマ係数|
コード例は[[ガンマ補正|Tips: ガンマ補正]]を参照ください。
2.2.1節から2.2.5節まで基本的な描画記法を説明してきました。
この節ではここまでのまとめとして、[[Psychlops::Rectangle]]型を使って「縞」を描画します。
^^ここは、ステップが多いので、かなりかみ砕いた解説をしています。縞の描画になれた方は使用する関数の説明とプログラム例のみをざっとご覧になられることをおすすめいたします。^^
!縞を描いてみようStep1~グラデーションの四角形の描画~
これから[[Psychlops::Rectangle]]型を用いて黒(0.0)と白(1.0)の縞を作ります。
どういう風に縞を作成するか想像してみてください。
白と黒の四角形を並べて描画するのでしょうか。
[img[image/縞1.png]]
しかし、これでは、単純な白と黒の繰り返しの縞しか描くことができません。
もう少し複雑な縞を描くためにできそうなことを順に整理してみましょう。
#四角形(w × h)を描画する
#四角形内の色を変化させる
#四角形の位置を移動させる
前の2つはこれまでに説明した[[Canvas::rect()]]命令を使えばできそうです。実際にはこの節では[[Canvas::rect()]]命令の代わりに''Rectangle::draw()''命令を用います。この命令は[[Canvas::rect()]]命令と全く同じ効果を持っているので、あなたの好みでどちらを使用してもかまいません。
四角形の位置を移動させるには、''Rectangle::centering()'',''Rectangle::shift()''命令を使います。
以下でまずこれらの命令の書式と使い方を簡単な例とともに説明します。
!!Rectangle::centering()
[[Psychlops::Rectangle]]型の図形を移動します。
[img[image/rect_centering().png]]
座標を指定する場合、その座標に図形の中心が設定されます。
|!Rectangle::centering()|centering()|[[Psychlops::Rectangle]] rectを画面中心に移動します|
|~|centering(double x, double y)|[[Psychlops::Rectangle]] rectを指定した座標(x,y)に移動します|
|~|~|double x:移動する図形のx座標を指定|
|~|~|double y:移動する図形のy座標を指定|
|~|centering([[Psychlops::Point]] po)|[[Psychlops::Rectangle]] rectを指定したpoint座標(x,y)に移動します|
|~|~|[[Psychlops::Point]] po:移動する図形の(x,y)座標を指定|
|~|centering(Rectangle rect)|[[Psychlops::Rectangle]] rectを指定した[[Psychlops::Rectangle]] rectの中心座標(x,y)に移動します|
|~|~|[[Psychlops::Rectangle]] rect :移動する図形の中心座標となる[[Psychlops::Rectangle]] rectを指定|
!!Rectangle::shift()
[[Psychlops::Rectangle]]型の図形を移動します。
元の座標(x,y)を基点に入力値(h,v)だけ移動します。
|!Rectangle::shift()|shift(double h, double v)|座標(X,Y)を(h,v)だけ移動します|
|~|~|double h:入力値分、右水平方向に移動|
|~|~|double v:入力値分、下垂直方向に移動|
[img[image/rect_shift().png]]
!!!Rectangle::centering()とRectangle::shift()の書き方
200×100の四角形を中心座標から(-100,-200)だけ移動します。
{{{
<例1>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Rectangle rect1;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
rect1.set(200,100);
rect1.centering().shift(-100,-200);
sampleA.rect(rect1,Color::white);
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
!!Rectangle::draw()
今まで図を描画する時、[[Psychlops::Canvas]]型の命令を使用しました。
今回は描画命令にRectangle::draw()命令を使用します。
|!Rectangle::draw()|draw()|対象の[[Psychlops::Rectangle]]型を描画します|
|~|draw([[Psychlops::Color]] col)|対象の[[Psychlops::Rectangle]]型を[[Psychlops::Color]] colで塗りつぶし描画します|
|~|~|[[Psychlops::Color]] col:描画する四角形の色を指定|
!!Rectangle::draw()の書き方
<例1>の四角形の色をRectangle::draw()命令を使用して「cyan」に塗りつぶします。
{{{
<例2>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Rectangle rect1;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
rect1.set(200,100);
rect1.centering().shift(-100,-200);
rect1.draw(Color::cyan);
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
!!実際に縞を描いてみましょう
とりあえず四角形(w × h)を(200 × 100)の大きさに決めます。
まず黒(0.0)から白(1.0)へ左から右にグラデーションする縞を描画してみましょう。
{{{
<例3>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Rectangle rect1;
Psychlops::Color col1;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
rect1.set(1,100);
for(int i=0;i<200;i++){
col1.set((double)i/200);
rect1.centering().shift((double)-1/2*200+i,0);
rect1.draw(col1);
}
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
このプログラムでは、200個の1 x 100ピクセルのRectangleを描画しています。
左から数えてi個目の四角形の色をi / 200とすると、一番左i = 0では色は0.0(黒)となって、右に行くに従ってRectangleの輝度が大きくなるように設定できます。
描画する場所は最終的な縞の中心が画面中央(Xc,Yc)に来るように計算します。
[img[image/縞2.png]]
描画する縞は200 × 100ピクセルなので、一番左のRectangleの中心位置は(Xc - 100,Yc)、一番右の位置は(Xc + 100,Yc)です。
一般に幅wの縞を描くときには、一番左のRectangleの位置は(Xc - 1 / 2 * w ,Yc)、一番右の位置は(Xc + 1/2 * w ,Yc)になります。
次にこの一番左のRectangleの位置を使って、i番目のRectangleの座標位置を考えてみます。
i番目のRectangleは一番左のRectangleから見て+ i だけx座標を移動した位置にあるはずです。
従って、i番目の座標は(Xc -1 / 2 * w + i ,Yc)となります。
!!縞を描いてみようStep2~正弦波で縞を作成する~
Step1で黒から白のグラデーション(色変化)の縞を作成しました。
次にもう少し複雑な正弦縞を作成してみましょう。
Step1から使えそうな要素を抜き出しましょう。
*描画する四角形(w × h):(1 × hピクセルの四角形をw回描画する)
*座標:Xc - 1 / 2 * w + i ,Yc
[img[image/正弦波1.png]]
{{{
* 正弦波の一般式
}}}
[img[image/正弦波の一般式.png]]
A:振幅,λ:波長,θ:位相,x:i
但しy>0であるので(扱うのが色のため)
[img[image/正弦波2.png]]
[img[image/色の式.png]]
となる。
上記式に値をあてはめていきます。
A:振動をC:コントラストに置き換えます。
Michelsonコントラストの定義式を用います。
[img[image/contrastEq.png]]
C=2A / 2Lmean = A / Lmean
A=C * Lmean
今回は位相0の縞を描画するのでθ = 0
コントラスト:1.0
波長λ:60
平均輝度(Lmean): 0.5の縞を描画します。
y=Asin{ ( 2π * x / λ ) + θ } + Lmean
y=( C * Lmean ) sin ( 2π * i / λ ) + Lmean
y=Lmean { C sin ( 2π * i / λ ) + 1 }
i番目の色=Lmean { C sin ( 2π * i / λ ) + 1 }
{{{
i番目の色=1.0 { 0.5 sin ( ( 2π * i / 60 ) + 1 }
}}}
これで色が求まりました。
実際にコードを書いてみましょう。
{{{
<例4>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Rectangle rect1;
Psychlops::Color col1;
double width=200;
double height=100;
double lambda=60;
double lmean=0.5, contrast=1.0, theta=0;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
rect1.set(1,height);
for(int i=0;i<width;i++){
col1.set(lmean*((contrast*sin((2*PI*i/lambda)+theta))+1));
rect1.centering().shift((double)-1/2*width+i,0);
rect1.draw(col1);
}
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
!!縞を描いてみようStep3~位相が異なる縞~
Step2では位相0の縞を描画しました。
位相に値を入れて位相90の縞を描画しましょう。
わかりやすいように位相0の下に並べて描画します。
{{{
<例5>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Rectangle rect1,rect2;
Psychlops::Color col1,col2;
double width=200;
double height=100;
double lambda=60;
double lmean=0.5, contrast=1.0, theta1=0, theta2=90;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
rect1.set(1,height);
rect2.set(rect1);
for(int i=0;i<width;i++){
//位相=0の縞
col1.set(lmean*((contrast*sin((2*PI*i/lambda)+theta1))+1));
rect1.centering().shift((double)-1/2*width+i,0);
rect1.draw(col1);
//位相=90の縞
col2.set(lmean*((contrast*sin((2*PI*i/lambda)+theta2))+1));
rect2.centering().shift((double)-1/2*width+i,150);
rect2.draw(col2);
}
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
!縞を描いてみようStep4~矩形波の縞を作成する~
正弦波の縞の場合、白と黒の境目が曖昧です。
境目をはっきり描画したい場合、矩形波の縞を描画します。
ですが実際には正弦波の式を使います。
{{{
* 正弦波の一般式
}}}
[img[image/正弦波の一般式.png]]
y = 0を軸に正弦波が展開します。
なのでこれを利用し、y > 0の場合はLmax , y < 0の場合はLminを描画するという判定分を追加することで矩形波の縞が描画できます。
コントラスト、Lmeanを使ってLmax,Lmeanの値を求めておくと、
Lmax = Lmean + Lmean * コントラストが求まります。
Lmin = Lmean - Lmean * コントラストが求まります。
これらを使用してコードを書いてみましょう。
{{{
<例6>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Rectangle rect1;
double col1;
double width=200;
double height=100;
double lambda=60;
double lmean=0.5, contrast=1.0, theta=0;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
rect1.set(1,height);
for(int i=0;i<width;i++){
col1=(lmean*contrast*sin((2*PI*i/lambda)+theta));
if(col1>0) col1=lmean+contrast*lmean;
else col1=lmean-contrast*lmean;
rect1.centering().shift((double)-1/2*width+i,0);
rect1.draw(col1);
}
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
!!縞を描いてみようStep5~縞の角度を変えてみる~
今まで縦(90°)の縞を描画してきました。
次は横(0°)の縞を描画してみましょう。
w × hの縦の縞を描画するために1 × hの四角形をw個並べました。
横の縞を描画するにはどうしたらいいか。
w × hの横の縞を描画するためにw × 1の四角形をh個並べたら作れないでしょうか。
まずわかっていることを書いてみます。
*描画する四角形( w × h ) : ( w × 1の四角形をh回描画する)
*座標X:Xc - 1 / 2 * w + i
*色:y = A sin { ( 2π * x / λ ) + θ } + Lmean
今回はX座標ではなくY座標を指定する必要があります。
よって以下の通りになります。
*座標Y:Yc - 1 / 2 * h + j (考え方はX座標と同じなので[[Step1|2.2.6 画面上に縞を描画する-Rectangleクラス2-]]参照のこと)
これらから実際にコードを書いて試してみましょう。
{{{
<例7>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Rectangle rect1;
Psychlops::Color col1;
double width=200;
double height=100;
double lambda=60;
double lmean=0.5, contrast=1.0, theta=0;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
rect1.set(width,1);
for(int j=0;j<height;j++){
col1.set(lmean*((contrast*sin((2*PI*j/lambda)+theta))+1));
rect1.centering().shift(0,(double)-1/2*height+j);
rect1.draw(col1);
}
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
確認できたでしょうか。
矩形波の横の縞はStep4を参考に自分で試してみてください。
[[2.2.6節|2.2.6 画面上に縞を描画する-Rectangleクラス2-]]では[[Psychlops::Rectangle]]型を用いて縞を描画しました。
この節では[[Psychlops::Point]]型を用いて縞を描画します。
四角形(w × h)を描画する際、[[Psychlops::Rectangle]]型では1 × hの四角形をw個
もしくはw × 1の四角形を h個並べて描画しました。
[[Psychlops::Point]]型ではw × hの点を並べて描画します。
!縞を描いてみようStep1~正弦縞を描画する~
はじめに一番簡単な垂直方位の縞を書いてみましょう。
まずわかっていることを書いてみます。
*描画する縞のサイズ:w × h (1ピクセルの点をw×h回描画する)
*縞の中心座標X:Xc - 1 / 2 * w + i
*縞の中心座標Y:Yc - 1 / 2 * h + j
*縞の式:L(x,y) = Lmean*(1+contrast*sin { ( 2π * x / λ ) + θ } )
式のパラメータは以下の通りとします。
|位相θ:0|
|コントラスト:0.5|
|波長λ:60|
|平均輝度(Lmean): 0.25|
では以上をふまえてコードを書いてみましょう。
{{{
<例1>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Point p1;
Psychlops::Color col1;
double width=200;
double height=100;
double lambda=60;
double lmean=0.25, contrast=0.5, theta=0;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
for(int i=0;i<width;i++){
for(int j=0;j<height;j++){
col1.set(lmean*((contrast*sin((2*PI*i/lambda)+theta))+1));
p1.centering().shift((double)-1/2*width+i,(double)-1/2*height+j);
sampleA.pix(p1,col1);
}
}
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
[img[image/grating01.png]]
!縞を描いてみようStep2~斜めの縞を描画する~
[[Psychlops::Rectangle]]型を使った書き方では方位90°の縞(縦)と0°の縞(横)のみを描画しました。
Pointクラスを使った縞の描画では、これ以外の方位の縞も描画することができます。
ここでは回転を使って方位45°の縞(斜め)を描画します。
!!図形の回転
点(i, j)をθラジアン回転させて(xx,yy)に変換する式は以下の通りです。:
{{{
xx = cosθ * i - sinθ * j
yy = sinθ * i + cosθ * j
}}}
この式を用いて、斜めの方位を持つ縞を描画してみましょう。
θはラジアン単位です。度数法d(°)で値を指定したいときは下の式で変換した値を用います。
{{{
θ ( rad ) = d° * π / 180°
}}}
以上をから<例1>を修正して方位が45°の縞を描画してみましょう。
{{{
<例2>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Point p1;
Psychlops::Color col1;
double width=200;
double height=100;
double lambda=60;
double lmean=0.5, contrast=0.5, theta=0;
double xx,yy;
double orientation;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
orientation=45*PI/180;
for(int i=0;i<width;i++){
for(int j=0;j<height;j++){
//回転
xx=cos(orientation)*i-sin(orientation)*j;
yy=sin(orientation)*i+cos(orientation)*j;
col1.set(lmean*((contrast*sin((2*PI*yy/lambda)+theta))+1));
p1.centering().shift((double)-1/2*width+i,(double)-1/2*height+j);
sampleA.pix(p1,col1);
}
}
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
[img[image/grating45.png]]
!縞を描いてみようStep3~交差した縞(Plaid)を描画する~
では次にStep2で作成した45°の縞に135°の縞を交差させた縞を描画してみましょう。
|位相θ:0|
|コントラスト:0.5|
|波長λ:60|
|平均輝度(Lmean): 0.25|
|方位:45°|
交差する縞の情報を設定します。
|位相θ:0|
|コントラスト:0.25|
|波長λ:45|
|平均輝度(Lmean): 0.25|
|方位:135°|
{{{
<例3>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Point p1;
Psychlops::Color col1;
double width=200;
double height=100;
double lambda=60;
double lmean=0.25, contrast=0.5, theta=0;
double xx,yy;
double orientation=45*PI/180;
//交差する縞の情報
double lambda2=45;
double lmean2=0.25, contrast2=0.25, theta2=0;
double xx2,yy2;
double orientation2=135*PI/180;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
for(int i=0;i<width;i++){
for(int j=0;j<height;j++){
//回転
xx=cos(orientation)*i-sin(orientation)*j;
yy=sin(orientation)*i+cos(orientation)*j;
xx2=cos(orientation2)*i-sin(orientation2)*j;
yy2=sin(orientation2)*i+cos(orientation2)*j;
col1.set( lmean*(1+contrast*sin(theta+(2*PI*yy/lambda)))+
lmean2*(1+contrast2*sin(theta2+(2*PI*yy2/lambda2)))
);
p1.centering().shift((double)-1/2*width+i,(double)-1/2*height+j);
sampleA.pix(p1,col1);
}
}
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
[img[image/plaid45_135.png]]
難しいと思われた方は、orientationやcontrast等のパラメタを変えて実行してみるとわかりやすいかもしれません。
この節では[[2.2.6節|2.2.6 画面上に縞を描画する-Rectangleクラス2-]]で作成した縞を動かします。
[[2.2.6節|2.2.6 画面上に縞を描画する-Rectangleクラス2-]]では、位相を変えてみることで縞の位置を移動させました。
「縞を動かす」ことは一見難しそうですが位相を変化させて描画することで
縞が運動しているように見えます。
ちょっと復習しましょう。
下のコードを実行すると位相 = 0と位相 = 90の四角形がそれぞれ描画されます。
{{{
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Rectangle rect1,rect2;
Psychlops::Color col1,col2;
double width=200;
double height=100;
double lambda=60;
double lmean=0.5, contrast=1.0, theta1=0, theta2=90;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
rect1.set(1,height);
rect2.set(rect1);
for(int i=0;i<width;i++){
//位相=0の縞
col1.set(lmean*((contrast*sin((2*PI*i/lambda)+theta1))+1));
rect1.centering().shift((double)-1/2*width+i,0);
rect1.draw(col1);
//位相=90の縞
col2.set(lmean*((contrast*sin((2*PI*i/lambda)+theta2))+1));
rect2.centering().shift((double)-1/2*width+i,150);
rect2.draw(col2);
}
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
ずれているのを確認できたでしょうか。
ここで、描画する毎にθの値を変更して入力すれば四角形が常に移動しているように見えます。
実際に試してみましょう。
{{{
<例1>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Rectangle rect1;
Psychlops::Color col1;
double width=200;
double height=100;
double lambda=60;
double lmean=0.5, contrast=1.0, theta=0;
double speed=PI/90;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
while(!Input::get(Keyboard::spc)){
rect1.set(1,height);
for(int i=0;i<width;i++){
col1.set(lmean*((contrast*sin((2*PI*i/lambda)+theta))+1));
rect1.centering().shift((double)-1/2*width+i,0);
rect1.draw(col1);
}
sampleA.flip();
theta+=speed;
}
}
}}}
<例2>は<例1>の1箇所だけを変えたコードです。
どこが変わっているのか見つけてみてください。
{{{
<例2>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Rectangle rect1;
Psychlops::Color col1;
double width=200;
double height=100;
double lambda=60;
double lmean=0.5, contrast=1.0, theta=0;
double speed=PI/90;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
while(!Input::get(Keyboard::spc)){
rect1.set(1,height);
for(int i=0;i<width;i++){
col1.set(lmean*((contrast*sin((2*PI*i/lambda)+theta))+1));
rect1.centering().shift((double)-1/2*width+i,0);
rect1.draw(col1);
}
sampleA.flip(6);
theta+=speed;
}
}
}}}
答えはflip()です。<例1>ではflip(),<例2>ではflip(6)になっています。
実行すると運動の速度が随分変化していることに気づくでしょう。
flip(6)では6回リフレッシュが行われるまで描画したままの状態で待ち、次の垂直同期で画面が入れ替わります。
60Hzの画面でflip(6)を実行する場合、100ミリsec描画したままの状態を維持できます。
このようにして、flip(int waitframe)命令を活用すると運動刺激のリフレッシュレート(各フレームの長さ)や細かい描画の待ち時間を設定できます。
flip(int waitframe)について詳しい説明は[[2.1.2節|2.1.2 Canvasの基本構造と操作]]を参照してください。
[[2.1.3節|2.1.3 Canvasのメンバ取得と変数の表示]]では簡単な文字列の表示について説明しました。
この節ではより本格的な2バイト文字を用いた文字列の描画について取り扱います。
Psychlopsで2バイト文字を描画するには2つの方法があります。
いずれの方法もCanvas::msg()命令を使用します。
|!Canvas::msg()|msg(char* string,double x,double y,[[Psychlops::Color]] col)|>|char* stringを座標(x,y)に指定色で表示します|
|~|~|schar* string|画面表示をする文字列を指定|
|~|~|double x|画面表示をする座標(x)を指定|
|~|~|double y|画面表示をする座標(y)を指定|
|~|~|[[Psychlops::Color]] col|描画する点の色を指定(設定しない場合、白が設定される)|
|~|msg(string str,double x,double y,[[Psychlops::Color]] col)|>|string strを座標(x,y)に指定色で表示します|
|~|~|string str|画面表示をする文字列を指定|
|~|~|double x|画面表示をする座標(x)を指定|
|~|~|double y|画面表示をする座標(y)を指定|
|~|~|[[Psychlops::Color]] col|描画する点の色を指定(設定しない場合、白が設定される)|
2つの方法とはこの命令を単体で使う方法とLettersと呼ばれるクラスを用いた方法です。
前者の方法は簡便ですが、フォント指定などはできず、それほど美しく表示することはできません。
後者の方法はフォント指定など文字列表示の方法を細かく指定できますが、文字列を表示する前にメモリ領域に文字列を描画しておく必要があります。
以下の節でそれぞれの方法を説明します。
[[2.3.1 Canvas::msg()命令単体での文字列描画]]
[[2.3.2 Lettersクラスを用いた文字列描画]]
また、これらのクラスの詳細については[[3.5節|3.5 文字列のレンダリングの仕様と関連情報]]をご覧ください。
!!Canvas::msg()命令単体での文字列描画
まず、[[2.1.3節|2.1.3 Canvasのメンバ取得と変数の表示]]で説明したCanvas::msg()命令をそのまま用いるやり方について説明します。
基本的には[[2.1.3節|2.1.3 Canvasのメンバ取得と変数の表示]]とほとんど同じですが、これまではここにアルファベットや記号、いわゆる1バイト文字でできた文字列を代入してきました。
{{{
<例0>
#include <psychlops.h>
using namespace Psychlops;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
sampleA.msg("A test of Letter Drawing", SampleA.getCenter());
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
ここで、表示したい文字列がひらがな・カタカナなどの2バイト文字とか全角文字と呼ばれる文字でできている場合は「”」(ダブルクォーテーション)の前に大文字のLを入れます。以下の例を見てください。
{{{
<例1>
#include <psychlops.h>
using namespace Psychlops;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
sampleA.msg(L"日本語の表示", SampleA.getCenter());
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
{{{
sampleA.msg(L"日本語の表示", SampleA.getCenter());
}}}
の命令を見てわかるとおり、"日本語の表示" という文字列のまえに大文字のLが入っています。
!!文字変数を使った代入
さらにこの部分に文字変数を入れたい場合はどうすればよいでしょうか。
普通のプログラムで文字列によく使うstring, char型を使ってもコンパイルできません。
このような場合はstring型を拡張したwstring型の変数を宣言してCanvas::msg()命令に代入します。
|!Canvas::msg()|msg(std::wstring string,double x,double y,[[Psychlops::Color]] col)|char* stringを座標(x,y)に指定色で表示します|
|~|~|string str:画面表示をする文字列を指定|
|~|~|double x:画面表示をする座標(x)を指定|
|~|~|double y:画面表示をする座標(y)を指定|
|~|~|[[Psychlops::Color]] col:描画する点の色を指定(設定しない場合、白が設定される)|
{{{
<例2>
#include <psychlops.h>
using namespace Psychlops;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
std::wstring wstr;
wstr=L"日本語の表示";
sampleA.msg(wstr, SampleA.getCenter());
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
ワイド文字列についてはいくつか知っておいた方が便利なことがあります。これらの詳細な情報については[[3.5節|3.5 文字列のレンダリングの仕様と関連情報]]をご覧ください。
Canvas命令単体での文字列描画は簡単ですが、フォントがぎざぎざしていたりしてあまり美しくは表示されません。
また、もう少し大きな文字を表示したい場合やイタリック体で文字を表示したい場合にはこの方法では対応できません。
この様な複雑な指定をして文字列を描画したい場合には事前にメモリに文字列を描画して、これを画面にコピーするという方法を行います。本や大量の部数の文書を作るときに、活字を組んで版をつくり、インクで紙に刷るのと同じ事です。
この事前のメモリ領域への文字列描画のためのクラスとして、[[Letters]], [[Font]]クラスが用意されています。
この方法による文字列描画の流れを簡単に説明すると以下のようになります
# [[Font]]クラスを用いてフォントを指定する
# [[Letters]]クラスを用いてメモリに文字列を描画
# [[Canvas::msg()命令を用いてメモリ内の文字列を画面に描画]]
以下でそれぞれの段階の具体的な方法について説明します。
![[Font]]クラスを用いたフォントの指定
Psychlopsにおけるフォントの指定は、[[Font]]クラスの変数を宣言し、これを引数として代入することによって行います。
Fontの宣言は以下の書式に従って行います。
|!Fontの宣言|font(double size, int weight, Style style, std::wstring family);|!フォントを指定します。|>|
|~|~|double size|フォントサイズ(pixel)を指定|
|~|~|int weight|線幅を指定 <br> (normal_style (400) / bold (700) : 省略可能)|
|~|~|Style style|斜体の有無を指定 <br> (normal_style / italic : 省略可能)|
|~|~|std::wstring family|フォント名を指定 <br> (省略可能)|
実際の例をいくつかあげてみましょう。
{{{
Font font1(24, Font::bold, Font::italic, L"ArialMT");
}}}
{{{
Font font2(12, Font::bold, Font::normal_style, L"メイリオ");
}}}
{{{
Font font3(18, Font::normal_weight, Font::normal_style, L"ヒラギノ角ゴ Pro");
}}}
第2引数は文字の太さ、第3引数で斜体の有無を選択します。
第4引数はフォントの名前を2重引用符(")でくくって指定します。
これらは省略可能です。
Canvas.msg関数などで使われるデフォルトフォントを変更するには、Font::default_font変数に新しいフォント構造体を指定します。
{{{
Font::default_font = Font(L"Arial", 20);
}}}
フォントはインストールされていなければその環境標準のものになります。一般的にWindowsとMacOSXでは同じ名前のフォントはありませんので、注意してください。
![[Letters]]クラスによる文字列のプリレンダリングと描画
次のステップでは、上で用意したフォントを使って、文字列の元版(レンダリング済みの文字列、といいます)をメモリ内に作成します。
これにはLettersクラスを使用します。
|!Lettersの宣言|Letters(std::wstring str, Font font );|!文字列を指定されたフォントでレンダリングします。|>|
|~|~|std::wstring str|描画する文字列を指定|
|~|~|Font font|フォントを指定 <br> (省略可能)|
ここでも実際の例をいくつかあげてみましょう。
{{{
Letters let1(L"Space Barを押すと文字が変わります", Font(20, Font::bold));
}}}
{{{
Letters let2(L"矢印キーを押してください。配置が変わります。");
}}}
{{{
Letters let3(L"Font \"Arial\" have only Latain characters. 日本語フォントが必要です.", font1);
}}}
文字列の内容をプログラム内で変更したいときなど、宣言とは違うところで文字列をレンダリングしたいときには、Letters::string()命令を使います。
|!Letters::string()|string(std::wstring str);|!文字列を指定されたフォントでレンダリングします。|>|
|~|~|std::wstring str|描画する文字列を指定|
このとき、font指定はLetters::font()命令を使用して別に行う事になりますが、何も指定せずデフォルトのフォントでレンダリングする事もできます。
|!Letters::font()|font(Font font);|!文字列を指定されたフォントでレンダリングします。|>|
|~|~|std::wstring str|描画する文字列を指定|
|~|~|Font font|フォントを指定 |
例えば上にあげた一つ目の例
{{{
Letters let1(L"Space Barを押すと文字が変わります", Font(20, Font::bold));
}}}
をこれらの命令を使って書き直してみるとこの様になるでしょう。
{{{
Letters let1;
let1.string(L"Space Barを押すと文字が変わります");
let1.font(Font(20, Font::bold));
}}}
! Canvas::msg()命令を用いてメモリ内の文字列を画面に描画
ここまでの段階で、指定されたフォントを使って表示したい文字列の元版ができあがりました。実際に画面にこれを表示するにはCanvas::msg()命令を使います。
|!Canvas::msg()|msg(Letters let, double x, double y, Color col, align, algn );|!文字列を指定されたフォントでレンダリングします。|>|
|~|~|Letters let|描画するレンダリング済み文字列を指定|
|~|~|double x|文字列描画の水平位置を指定|
|~|~|double y|文字列描画の垂直位置を指定|
|~|~|Color col|文字列の描画色を指定 <br> (省略可能)|
|~|~|align, algn|文字詰めの方法を指定 <br> (省略可能)|
{{{
Letters let1(L"Space Barを押すと文字が変わります", Font(20, Font::bold));
Display::msg(let1, 100, 100);
}}}
! Letters::draw()命令を用いてメモリ内の文字列を画面に描画
もう一つ同じ目的の命令としてLetters::draw()命令があります。
LettersクラスにはRectangle型に対して使用できるcentering(), shift()などの命令が使えます。
これらの命令を使って同じ文字列を異なる位置に簡単に描画することができます。
|!Letters::centering()|centering(double x, double y)|!レンダリング済みの文字列の描画位置をその中央の座標で指定する。|>|
|~|~|double x|センタリングの水平位置を指定<br> (省略可能)|
|~|~|double y|センタリングの垂直位置を指定<br> (省略可能)|
|~|~|>|>|これらの値が省略されるときは、画面中央に描画位置が設定される|
|!Letters::shift()|shift(double x, double y)|!レンダリング済みの文字列の描画位置を現在の指定位置から移動する。|>|
|~|~|double x|水平方向の移動量を指定|
|~|~|double y|垂直方向の移動量を指定|
|!Letters::draw()|draw(Color col)|!レンダリング済みの文字列を描画する|>|
|~|~|double x|水平方向の移動量を指定|
|~|~|double y|垂直方向の移動量を指定|
|~|~|Color col|文字列の描画色を指定 <br> (省略可能)|
以下のように使います。
{{{
Letters let1(L"Space Barを押すと文字が変わります", Font(20, Font::bold));
let1.centering(Mouse::x, Mouse::y+30).draw(Color::green);
}}}
!まとめ
ここまでをまとめて、文字列描画の簡単なプログラム例を作ってみましょう。
{{{
#include <psychlops.h>
using namespace Psychlops;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen); //描画用windowの取得
Letters let1(L"Space Barを押すと文字が変わります", Font(20, Font::bold));
Letters let2;
let2.string(L"文字が変わりました");
let2.font(Font(20, Font::bold));
sampleA.clear(Color(0.0));
sampleA.msg(let1, 100, 100);
sampleA.flip();
while(!Input::get(Keyboard::spc));
sampleA.clear(Color(0.5));
let2.centering().draw(Color::red);
while(!Input::get(Keyboard::spc));
}
}}}
[[3.1 オフスクリーン描画の基本]]
[[3.1.1 オフスクリーン領域の宣言]]
[[3.1.2 オフスクリーン上に点・四角形を描画する]]
[[3.1.3 オフスクリーン上の任意の位置に描画する]]
[[3.1.4 発展的な内容]]
[[3.2 オフスクリーンを用いた描画例-ランダムドットパターン-]]
[[3.3 画像ファイルの取り扱い]]
[[3.3.1 画像ファイルを保存する]]
[[3.3.2 画像ファイルを読み込み表示する]]
[[3.3.3 複数の画像ファイルからアニメーションを作成する]]
[[3.4 Canvasのオフスクリーンへの取り込み]]
[[3.5 文字列のレンダリングの仕様と関連情報]]
[[3.1.1 オフスクリーン領域の宣言]]
[[3.1.2 オフスクリーン上に点・四角形を描画する]]
[[3.1.3 オフスクリーン上の任意の位置に描画する]]
[[3.1.4 発展的な内容]]
!Imageクラスの概要
!!オフスクリーン領域とは?
第2章では直にCanvasに描画してきました。第3章ではオフスクリーンへ領域の描画ないし取扱いをします。
Canvasは裏表2枚しかないので、複雑な動画を描画するには不便です。
裏画面に描画が終わるまでflip()を実行できないので(もし強引にflip()を実行してしまったら、描画途中の中途半端なイメージが表示されてしまいます)、複雑な動画ではアニメーションの質が下がってしまいます。
このような動画の描画や、とても時間のかかる内容の描画を行うときにはオフスクリーン領域(描画バッファともいいます)と呼ばれる特殊な描画領域を用います。
このオフスクリーン領域はメモリ上に確保された画像ファイルのようなものだと考えるとよいかもしれません。
この画像ファイルに複雑な描画内容を保存しておいて、後で紙芝居のように画面に表示することができれば、複雑な内容の描画も素早く行えるわけです。^^*1^^
Psychlopsではこのオフスクリーン領域をImageというクラスを用いて操作します。
^^ *1もう少し正しく表現するなら、オフスクリーン領域とはコンピュータのメインメモリ(RAM 1GBなどという”あれ”のことです)内に確保された描画内容の保存場所(描画用バッファ)のことです。^^
!!ImageとCanvasの違い
ここではImageとCanvasの違いについて詳しく説明することはしませんが、これら2つのクラスは物理的に保管場所が違うということは頭に置いておいてください。
Canvasの内容は画面上に素早く提示することのできる位置に保管されています。flip()命令を実行すると正確なタイミングで描画が起こるわけです。
これに対して、ImageはCanvasよりも遙かに大量のデータを保管することができますが、画面上に描画内容を提示するには、ある程度の時間が必要です。
これは、Canvas(の保管場所)にImageの内容を転送する必要があるからです。
より詳しい内容については[[Tips: Image使用上の注意]]を参考にしてください。
[img[image/オフスクリーン領域.png]]
!オフスクリーン領域の宣言文
オフスクリーン領域は画像を取り扱うので
宣言時には取り扱いたい画像の大きさやカラーモード、透明度の指定が必要となってきます。
Image()のように宣言のみの場合は''Image::set()''命令で指定します。
|!Imageの宣言|Image()|オフスクリーン領域を宣言します|
|~|Image([[Psychlops::Rectangle]] rec,PixelComponentsCnt pixcompcntval)|指定された[[Psychlops::Rectangle]] 型と同じ大きさのオフスクリーン領域を宣言します|
|~|~|[[Psychlops::Rectangle]] rect :[[Psychlops::Rectangle]] 型を指定|
|~|~|PixelComponentsCnt pixcompcntval :[[カラーモード]]を指定。省略した場合はRGBが指定される|
|~|Image(long x, long y, PixelComponentsCnt pixcompcntval)|指定されたx×yと同じ大きさのオフスクリーン領域を宣言します|
|~|~|long x :オフスクリーン領域の横幅を指定|
|~|~|long y :オフスクリーン領域の縦幅を指定|
|~|~|PixelComponentsCnt pixcompcntval :[[カラーモード]]を指定。省略した場合はRGBが指定される|
!Image::set()
オフスクリーン領域の宣言時に何の指定もしなかった場合、Image::set()命令を使用します。
|!Image::set()|set([[Psychlops::Rectangle]] rect, PixelComponentsCnt pixcompcntval)|指定された[[Psychlops::Rectangle]] 型と同じ大きさのオフスクリーン領域を設定します|
|~|~|[[Psychlops::Rectangle]] rect :[[Psychlops::Rectangle]] 型を指定|
|~|~|PixelComponentsCnt pixcompcntval :[[カラーモード]]を指定。省略した場合はRGBが指定される|
|~|set(long x, long y, PixelComponentsCnt pixcompcntval)|指定されたx×yと同じ大きさのオフスクリーン領域を設定します|
|~|~|long x :オフスクリーン領域の横幅を指定|
|~|~|long y :オフスクリーン領域の縦幅を指定|
|~|~|PixelComponentsCnt pixcompcntval :[[カラーモード]]を指定。省略した場合はRGBが指定される|
!!宣言文の書き方
{{{
<例1>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Rectangle rect1(200,100);
Psychlops::Image Noise1(rect1,Image::GRAY);
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
次はImageの宣言と[[Image::set()]]命令を組み合わせたコードです。
{{{
<例2>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Image Noise1;
long Width=200, Height=100;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
Noise1.set(Width,Height,Image::RGBA);
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
これらのコードは実行しても黒い画面が表示されるだけでした。
次の3.1.2節で実際にImage上に描画を行います。
[[3.1.1|3.1.1 オフスクリーン領域の宣言]]節ではImageの宣言(領域確保)のみを行っていたので、コードを実行しても黒い画面が表示されるだけでした。
本節では実際にImage上に点・四角形を描画して、これを画面に表示してみます。
Imageクラスの命令(メソッド)はCanvasに対する命令とほとんど変わりません。
!Image::pix()
オフスクリーン領域に点を描画するための命令です。
[[Canvas::pix()|2.2.1 画面上に点を描画する-Pointクラス1-]]命令同様に点を描画するために「座標」と「色の指定」を行います。
色の指定についての詳細は[[2.2.5 描画する図形の色を指定する-Colorクラス-]]を参照してください。
|!Image::pix()|pix(int x, int y, [[Psychlops::Color]] col)|座標(x,y)に[[Psychlops::Color]] col色の点を描画します|
|~|~|int x: 描画する点の座標軸xを指定|
|~|~|int y: 描画する点の座標軸yを指定|
|~|~|[[Psychlops::Color]] col:描画する点の色を指定|
|~|pix(double x, double y, [[Psychlops::Color]] col)|座標(x,y)に[[Psychlops::Color]] col色の点を描画します|
|~|~|double x: 描画する点の座標軸xを指定|
|~|~|double y: 描画する点の座標軸yを指定|
|~|~|[[Psychlops::Color]] col:描画する点の色を指定|
|~|pix([[Psychlops::Point]] po, [[Psychlops::Color]] col)|Point座標(x,y)に[[Psychlops::Color]] col色の点を描画します|
|~|~|[[Psychlops::Point]] po :描画する点の座標(x,y)を指定|
|~|~|[[Psychlops::Color]] col:描画する点の色を指定|
!Image::draw()
実際にオフスクリーン領域に描画する際の命令です。
この命令を記述しないといくら''Image::pix()''命令やこの後出てくる''Image::rect()''命令を記述しても描画されませんので注意してください。
|!Image::draw()|draw()|指定したオフスクリーンを描画します|
|~|draw(double left, double top)|指定したオフスクリーン領域を(left,top)を中心とした座標に設定します|
|~|~|double left: 描画するオフスクリーン領域の座標軸xを指定|
|~|~|double top: 描画するオフスクリーン領域の座標軸yを指定|
!!Image::pix(),Image::draw()の書き方
100×100のImageに点を描画します。
{{{
<例1>
#include <psychlops.h>
using namespace Psychlops;
double width=100,height=100;
Psychlops::Rectangle rect1(width,height);
Psychlops::Color col;
Psychlops::Image Noise1(rect1,Image::GRAY);
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
//水平方向に2,垂直方向に5間隔の点を描画する。
for(int x=0; x<width; x+=2){
for(int y=0; y<height; y+=5){
col.set(0.5);
Noise1.pix(x,y,col);
}
}
Noise1.draw();
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
!Image::rect()
オフスクリーン領域に四角形を描画するための命令です。
実際の描画処理は''Image::draw()''命令で行われます。
色の指定についての詳細は[[2.2.5 描画する図形の色を指定する-Colorクラス-]]を参照してください。
|!Image::rect()|rect([[Psychlops::Rectangle]] rect, [[Psychlops::Color]] col)|[[Psychlops::Rectangle]]型の四角形を[[Psychlops::Color]] col色で描画します|
|~|~|[[Psychlops::Rectangle]] rect : 描画する四角形を指定|
|~|~|[[Psychlops::Color]] col:描画する四角形の色を指定|
!!Image::rect()の書き方
100×100のImageに青の四角形を描画します。
{{{
<例2>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Rectangle rect1(100,100);
Psychlops::Image Noise1(rect1);
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
Noise1.rect(rect1,Color::blue);
Noise1.draw();
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
前節でオフスクリーン上に図形の描画を行いました。
描画位置の指定がない場合、オフスクリーン領域は常に画面の左上に設定されます。
この節ではオフスクリーン領域を任意の場所に移動します。
2.2節ではそれぞれ[[Psychlops::Point型の移動|2.2.1 画面上に点を描画する-Pointクラス1-]]と[[Psychlops::Rectangle型の移動|2.2.6 画面上に縞を描画する-Rectangleクラス2-]]の移動について説明しました。
オフスクリーン領域の移動も上記の型と同様の命令文を使用します。
!Image::centering()
[[Psychlops::Image]]型の領域を移動します。
座標を指定する場合、その座標に領域の中心が設定されます。
|!Image::centering()|centering()|[[Psychlops::Image]]型の領域を画面中心に移動します|
|~|centering(double x, double y)|[[Psychlops::Image]]型の領域の中心を指定した座標(x,y)に移動します|
|~|~|double x:移動する領域のx座標を指定|
|~|~|double y:移動する領域のy座標を指定|
!!Image::centering()の書き方
前節の<例1>で作成したImageを画面中心に移動します。
{{{
<例1>
#include <psychlops.h>
using namespace Psychlops;
double width=100,height=100;
Psychlops::Rectangle rect1(width,height);
Psychlops::Color col;
Psychlops::Image Noise1(rect1,Image::GRAY);
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
Noise1.centering();
//水平方向に2,垂直方向に5間隔の点を描画する。
for(int x=0; x<width; x+=2){
for(int y=0; y<height; y+=5){
col.set(0.5);
Noise1.pix(x,y,col);
}
}
Noise1.draw();
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
!Image::shift()
[[Psychlops::Image]]型の領域を移動します。
元の座標(x1,y1)を基点に入力値(x,y)だけ移動します。
|!Image::shift()|shift(double h, double v)|座標(X,Y)を(h,v)だけ移動します|
|~|~|double x:入力値分、右水平方向に移動|
|~|~|double y:入力値分、下垂直方向に移動|
!!Image::shift()の書き方
前節の<例2>で作成したImageを座標(100,50)分移動します。
{{{
<例2>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Rectangle rect1(100,100);
Psychlops::Image Noise1(rect1);
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
Noise1.shift(100,50);
Noise1.rect(rect1,Color::blue);
Noise1.draw();
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
この節ではは少し発展的で込み入った内容を取り扱います。ここまでの命令を使ってとりあえず自分の目的が達成できている場合は無視しても構いません。
複雑で大きなサイズのイメージを高速でどうしても表示したい時にだけ、読んでみてください。
!Image::quicken()
3.1.1節でサイズが大きなImageをCanvasに転送するには時間がかかるという説明をしました。
この問題を解決する方法が一つあります。
転送するのにかかる時間の総和を短縮することはできませんが、PsychlopsはImageをCanvasへより早く転送することのできるメモリ領域へ転送することができます。
Canvasに描画する場合はCanvasの裏に描画して[[Canvas::flip()]]命令で画面に表示しています。
Imageはコードの記述はCanvasと大差ありませんが、Canvasの裏ではなくメインメモリ(主記憶装置)に描画しています。
[[Image::draw()]]命令を実行することで初めてメインメモリ(主記憶装置)にある図形がVRAMに転送され、flip()とともに画面に表示されます。
<[[Image::draw()]]命令実行時のイメージ図>
[img[image/draw()命令.png]]
つまり普通であればImageからCanvasへ直接転送が起こります。
ここで、この2つの中間地点にあらかじめ転送しておくことで、Canvasへの転送時間を見かけ上小さくすることができるのです。
この中間地点はVRAMと呼ばれるビデオボード上のメモリチップの中に作られます。
実は、VRAM内でのメモリの転送速度に比べてメインメモリからVRAMへの転送はとても時間がかかるのです。
この時間がかかる部分を事前に行っておくことで、見かけ上の転送時間を大きく縮めることができます。
この転送を事前に一部行っておく命令がImage::quicken()命令です。
<<図>>
[img[image/quicken()命令.png]]
<quickenモード>
通常、[[Image::draw()]]命令を使用すると上図のようにメインメモリ(主記憶装置)から画面まで一気に転送し描画します。
quicken()を実行すると、Imageの図形をVRAMに転送し、quickenモードに切り替えます。
そして[[Image::draw()]]命令でVRAMにある図形を画面に転送し表示します。
|!Image::quicken()|quicken()|メインメモリ(主記憶装置)にあるImageの内容をあらかじめVRAMに送ります|
|~|~|解除する場合はquicken(false)と記述|
!!Image::quicken()の書き方
[[Image::draw()]]命令の直前に記述します。
{{{
<例1>
#include <psychlops.h>
using namespace Psychlops;
double width=100,height=100;
Psychlops::Rectangle rect1(width,height);
Psychlops::Color col;
Psychlops::Image Noise1(rect1);
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
Noise1.centering();
for(int x=0; x<width; x+=3){
for(int y=0; y<height; y+=3){
col.set(0.5,0.2,0.8);
Noise1.pix(x,y,col);
}
}
Noise1.quicken();
Noise1.draw();
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
!!Image::quicken()の使用上の注意
ただし、この転送を行うためには
# ImageがRGB/RGBAモードで宣言されていること
# Imageへの描画がすでに完了していること
が必要です。
特に2番目については注意が必要です。
Imageに対する描画命令は全てメインメモリ上にあるImageに対して行われます。
したがって、すでにquicken()してしまったimageに対して描画を行っても、変更が反映されません。
{{{
<例2>
#include <psychlops.h>
using namespace Psychlops;
double width=100,height=100;
Psychlops::Rectangle rect1(width,height);
Psychlops::Color col;
Psychlops::Image Noise1(rect1);
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
Noise1.quicken();
Noise1.centering();
while(!Input::get(Keyboard::spc)){
for(int x=0; x<width; x+=3){
for(int y=0; y<height; y+=3){
col.set(0.5,0.2,0.8);
Noise1.pix(x,y,col);
}
}
Noise1.draw();
sampleA.flip();
}
}
}}}
この新しい描画内容を画面に反映させるには、quicken(false)命令を使ってquicken()モードを解除するか、再度Image::quicken()命令を実行してもう一度転送を行う必要があります。<プログラム例>
(転送を行ってもメインメモリ上にコピーは残っているので、単にこれをもう一度転送し直すことで描画を反映させることができます)
{{{
<例3>
#include <psychlops.h>
using namespace Psychlops;
double width=100,height=100;
Psychlops::Rectangle rect1(width,height);
Psychlops::Color col;
Psychlops::Image Noise1(rect1);
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
Noise1.quicken();
Noise1.centering();
while(!Input::get(Keyboard::spc)){
for(int x=0; x<width; x+=3){
for(int y=0; y<height; y+=3){
col.set(0.5,0.2,0.8);
Noise1.pix(x,y,col);
}
}
Noise1.draw();
sampleA.flip();
Noise1.quicken(false);
}
}
}}}
※2007年9月現在、quicken()命令はRGB/RGBAでのみ稼働
この節ではImageクラスを用いたオフスクリーンへの描画の利用例として、下図に示した砂嵐のような図、ランダムドットパターンを描画してみます。
[img[image/randomdot.png]]
はじめにドットサイズが1ピクセル四方の基本的なパターンを描画する例を説明した後、ドットサイズがもっと大きいものについて説明します。
[[3.2.1 基本的なランダムドットを描画する]]
[[3.2.2 ドットサイズの異なるランダムドットパターンの描画]]
[[3.2.3 ランダムドットキネマトグラムの描画]]
この節では、複数の細いImageをshift()命令を使って並べることによって、ランダムドットを描画しています。この方法は簡単ですが、ドット密度やドットの運動方向を精密に操作したい場合は、Rectangleの配列を用いた方が描画がスムーズな場合があります。これについては、[[Tips: Rectangleの配列を用いたドットパターンの描画例]]を参照してください。
PsychlopsではImageを用いてランダムドットパターンが比較的簡単に描画できます。
ランダムドットを描画するためには''Psychlops::random()''命令を使用します。
色の返値を''Psychlops::random()''命令を使用しランダムに値を返すことでランダムドットパターンが描画できます。
!!Psychlops::random()
0~1,0~任意の数等好きに設定した値の中でランダムに数を返します。
|!Psychlops::random()|random()|0~1の値をランダムに返します。|
|~|random(double N)|0~Nの値をランダムに返します。|
|~|~|double N: ランダムで返す値の最大値を指定|
|~|random(int N)|0~Nの整数値をランダムに返します。|
|~|~|int N: ランダムで返す整数値の最大値を指定|
|~|random(double min, double max)|min~maxの値をランダムに返します。|
|~|~|double min: ランダムで返す値の最小値を指定|
|~|~|double max: ランダムで返す値の最大値を指定|
引数が実数(double)か整数(int)かで動作が異なりますので、実数で返したい場合は最大値が整数でも必ず1.0などのように小数点をつけて指定してください。整数変数を指定する場合は(double)でキャストしてください。
{{{
random(5) ← [0,1,2,3,4] のいずれかの数がint型で返る
random(5.0) ← [0, 5)の間の浮動小数点値が返る
int i = 5;
random(i) ← [0,1,2,3,4] のいずれかの数がint型で返る
random((double)i) ← [0, 5)の間の浮動小数点値が返る
}}}
!!!Psychlops::random()の書き方
3.1.3節の<例1>では画面中央のImage上に点を描画しました。
このコードを応用してランダムドットを描画します。
3.1.3節の<例1>では水平方向に2 ピクセル,垂直方向に5 ピクセル間隔に色:0.5の点を描画しました。
ここではグレースケールのランダムドットを描画するために、色(輝度)が0 ~ 1の間でランダムに設定された、100 x 100 ピクセル四方の隙間のないドット列を描画します。
{{{
<例1>
#include <psychlops.h>
using namespace Psychlops;
double width=100,height=100;
double temp1;
Psychlops::Rectangle rect1(width,height);
Psychlops::Color col;
Psychlops::Image Noise1(rect1,Image::GRAY);
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
Noise1.centering();
for(int x=0; x<width; x++){
for(int y=0; y<height; y++){
//0~1の値をランダムに返す
temp1=Psychlops::random();
col.set(temp1);
Noise1.pix(x,y,col);
}
}
Noise1.draw();
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
<例1>では[[カラーモード]]をGRAYで描画しましたがRGB,RBGAでも描画可能です。
<例2>ではRGBAでランダムドットを描画します。
{{{
<例2>
#include <psychlops.h>
using namespace Psychlops;
double width=100,height=100;
double temp1,temp2,temp3,temp4;
Psychlops::Rectangle rect1(width,height);
Psychlops::Color col;
Psychlops::Image Noise1(rect1,Image::RGBA);
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
Noise1.centering();
for(int x=0; x<width; x++){
for(int y=0; y<height; y++){
//R,G,B,Aそれぞれに0~1の値をランダムに返します
temp1=Psychlops::random();
temp2=Psychlops::random();
temp3=Psychlops::random();
temp4=Psychlops::random();
col.set(temp1,temp2,temp3,temp4);
Noise1.pix(x,y,col);
}
}
Noise1.draw();
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
[img[image/colornoise.png]]
今まで描画したランダムドットパターンのドットサイズは1 ピクセル四方でした。
次はピクセルサイズがn( n > 1 )のランダムドットパターンを描画します。
1 ピクセルのドットを描画する時[[Image::pix()]]命令を使用していましたが
n ピクセルのドットを描画するためには[[Image::rect()]]命令を使用します。
横幅×高さの領域にn ピクセルのランダムドットパターンを描画する場合、以下の要領で描画します。
{{{
1.ドットサイズ × ( 高さ × ドットサイズ )のImageをN個(横幅/ドットサイズ)用意する。
2.ドットサイズ × ドットサイズのrectをN個( 横幅 / ドットサイズ )用意する。
3.1で作成したImage(x)に2で作成したrect(x)をセットする。
4.2で作成したrect(x)にランダムドットパターンを描画する。
5.rectをドットサイズ縦方向に移動し、Image(x)全てに描画されるまで3~4を行う。
6.Imageを横幅の長さまで並べる。
}}}
[img[image/dotsize.png]]
下の例では、横幅がwドット分、縦幅がhドット分のランダムドットパターンを描画しています。ここで指定した単位がドット単位でピクセル単位でないことに注意して、例を見てみてください。
{{{
<例3>
#include <psychlops.h>
using namespace Psychlops;
const int dotsize=4;
const int width=100;
double height=50;
double temp1;
Psychlops::Rectangle rect1[width];
Psychlops::Color col;
Psychlops::Image Noise1[width];
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
for(int x=0; x<width; x++){
Noise1[x].set(dotsize,height*dotsize);
rect1[x].set(dotsize, dotsize);
for(int y=0; y<height; y++){
temp1=Psychlops::random();
col.set(temp1);
Noise1[x].rect(rect1[x],col);
//ドットサイズ分下に移動する
rect1[x].shift(0, dotsize);
}
Noise1[x].centering().shift((double)-1/2*(width*dotsize)+x*dotsize,0);
Noise1[x].draw();
}
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
ドットサイズを他の値に変えて実行したい場合は『const int dotsize=4;』の値を変更してください。
この例では、画面中央にランダムドットパターンを描画しました。画面中央に描画するための式は[[2.2.6節|2.2.6 画面上に縞を描画する-Rectangleクラス2-]]を参照してください。
ここでは( Xc - 1 / 2 * w + i , Yc )のwとiにドットサイズ dotsize を乗算しています。
ドットサイズを乗算することでImageが重ならないように移動して描画しています。
試しにドットサイズの乗算を外すと、1ピクセル分のみ移動されて最後の列のみ正常に、他の列は細長いドットが表示されます。これはImageが重なって描画されてしまっているためです。
[img[image/dotsize2.png]]
!ランダムドットキネマトグラムを描画しよう
次は上記のランダムドットを水平方向に動かしてみましょう。
このような運動するランダムドットパターンをランダムドットキネマトグラムといいます。
今回は水平方向に移動するのでX座標( Xc - 1 / 2 * w + i )について考慮します。
( Xc - 1 / 2 * w + i ) + α と記述すると α 分移動します。
しかし既存の横幅内での運動を行いたいので
下図のように右に飛び出たα を前に移動します。
移動要素である i + α に対しwで除算し余りを算出することで求まります。
{{{
( Xc - 1 / 2 * w ) + ( i + α ) % w
}}}
[img[image/rdk.png]]
<例3>ではドットサイズを変更しているので、それぞれの値をドットサイズ倍する必要があります。
{{{
( Xc - 1 / 2 * w*dotsize ) + ( i *dotsize+ α*dotsize ) % w*dotsize
}}}
次に α の内容について考えてみましょう。
1フレームあたりの移動量をnドット分とすると、mフレーム目での0フレーム目の位置からのピクセル単位での移動量αは、 n* dotsize * m となります。
つまりi番目のimageの水平方向の移動量は、以下のようになります。
{{{
( Xc - 1 / 2 * w * dotsize ) + ( i *dotsize+ frame*speed*dotsize ) % w*dotsize
}}}
第1項(+の前)はImageをXcを中心として並べるための移動量、第2項が運動するパターンを実現するための移動量です。
早速上記式を用いて描画します。
{{{
<例4>
#include <psychlops.h>
using namespace Psychlops;
const int dotsize=4;
const int width=100;
double height=50;
double temp1;
double speed=1;
double phase=0;
int frame=0;
Psychlops::Rectangle rect1[width];
Psychlops::Color col;
Psychlops::Image Noise1[width];
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
for(int x=0; x<width; x++){
Noise1[x].set(dotsize,height*dotsize);
rect1[x].set(dotsize, dotsize);
for(int y=0; y<height; y++){
temp1=Psychlops::random();
col.set(temp1);
Noise1[x].rect(rect1[x],col);
rect1[x].shift(0, dotsize);
}
}
while(!Input::get(Keyboard::spc)){
for(int x=0; x<width; x++){
phase=(double)-1/2*(width*dotsize)+(int)(x*dotsize+frame*speed*dotsize)%(int)(width*dotsize);
Noise1[x].centering().shift(phase,0);
Noise1[x].draw();
}
sampleA.flip();
frame++;
}
}
}}}
水平方向へ移動するドットパターンが描画されたでしょうか?
これと同様にしてランダムドットステレオグラムを書くこともできます。[[Tips: ランダムドットステレオグラムの描画コード]]を参照してみてください。
この節では、画像ファイルの取扱いについて説明します。
現バージョンのPsychlopsではpng形式のファイルの読込と保存が可能です。
この読込と保存はImage型を通して行われます。
読み込まれた画像ファイルはPsychlops内部ではImage型の変数(インスタンス)として扱われます。
同様に保存はImage型の描画内容をPng形式でファイルに書き出すことによって行われます。
以下では、この2つについて説明します。
[[3.3.1 画像ファイルを保存する]]
[[3.3.2 画像ファイルを読み込み表示する]]
ディスプレイに提示した画像をプログラムが終了した後でも使えるように保存したい時がよくあります。
この節では、画面上に描画した図形を外部ファイルとして保存する方法について説明します。
PsychlopsではImageの描画内容を、PNG形式の画像ファイルとして保存することができます。
これにはImage::save()命令を使用します。はじめにこのImage::save()命令の使い方と例を見てみましょう。
!Image::save()
画像ファイルをpng形式で保存します。
|!Image::save()|save(filename)|!指定したImageを保存します|>|>|
|~|~|filename| 保存するImageの画像ファイル名称(拡張子まで)を指定|>|>|
|~|~|保存先を指定しない場合のパス|Mac:Macintosh HD/ユーザ/ユーザ名(任意)/書類/Psychlops/ ^^*1^^|>|
|~|~|~|Win:実行ファイルと同じパス|>|
|~|~|保存をする場合のパス指定|%APP%|Mac:実行バンドル(.app)と同じパス|
|~|~|~|~|Win:実行ファイルと同じパス|
|~|~|~|%RESOURCES%|Mac:実行バンドルの内部のResourcesフォルダ|
|~|~|~|~|Win:実行ファイルのパスの直下のrscという名前のフォルダ|
|~|~|~|%~USER_HOME%|Mac:"~/"と同じ(/Users/(user)/)|
|~|~|~|~|Win:ユーザープロファイルフォルダ(C:\Documents and Settings\(user)\)|
|~|~|~|%~USER_SETTING%|Mac:"~/Library/Psychlops/"|
|~|~|~|~|Win:"%~AppData%Psychlops"|
|~|~|~|%~USER_DOCUMENTS%|Mac:"~/Documents/Psychlops"|
|~|~|~|~|Win:ユーザープロファイルフォルダ\My Documents\Psychlops|
|~|~|~|%~USER_DOCUMENTS_ROOT%|Mac:"~/Documents"|
|~|~|~|~|Win:ユーザープロファイルフォルダMy Documents\|
*1 Finderでの表示は上記のとおりですが、Psychlopsで指定する場合は下記のUnix形式でパスを指定してください
** "~/Documents/Psychlops/"
*2 MacOSX版では、日本語パス名は使用できません。強制終了してしまいます。
*3 VISTA環境の場合、パス指定が正常に動作しません。VISTA環境の方はお手数ですが保存先を指定しないでお使いください。
!Canvas::to()
この命令は[[Psychlops::Rectangle]]型の情報を[[Psychlops::Image]]型に移動する命令です。
この命令と''Image::save()''命令を組み合わせることによってImage上以外で描画した画像が保存できます。
|!Canvas::to()|to([[Psychlops::Image]] img, [[Psychlops::Rectangle]] rect) |>|![[Psychlops::Rectangle]]型の情報を[[Psychlops::Image]]型に移動する|
|~|~|[[Psychlops::Image]] img |移動先のImageを指定|
|~|~|[[Psychlops::Rectangle]] rect |移動元のRectangleを指定|
ここで注意する点が一つあります。
Canvas::to()命令は[[Psychlops::Rectangle]]型の引数で指定された大きさを取得して移動先のImageのサイズに指定します。
このときに移動先のImageのサイズがあらかじめセットされていると、プログラムが予期しない動作をすることがあります。
変数の宣言だけが行われていて、まだセットをされていないImage型を使用するか、
Image::release()命令を使って一度Imageの中身をクリアしてからCanvas::to()命令を使うようにして下さい。
|!Image::release()|release(void) |メモリ内のイメージの内容を解放する|
!!Image::save()の書き方
[[Canvas::pix()|2.2.1 画面上に点を描画する-Pointクラス1-]]命令でCanvas上に描画したランダムドットをImageに移動し保存します。
''Canvas::to()''命令は[[Psychlops::Rectangle]] 型が移動元になるので
描画した点と同じ場所に空の[[Psychlops::Rectangle]] 型を設定する必要があります。
<例1>では座標(400,500)に100 × 100のランダムドットを描画し保存します。
centerX,centerYの値を変更することで描画する座標位置が変わります。
0,0を代入もしくは削除すると左上に描画されます。
{{{
<例1>
#include <psychlops.h>
using namespace Psychlops;
double width=100,height=100;
double temp1;
double centerX=700,centerY=500;
Psychlops::Rectangle rect1(width,height);
Psychlops::Color col;
Psychlops::Image Noise1;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
rect1.shift(centerX,centerY);
for(int x=0; x<width; x++){
for(int y=0; y<height; y++){
temp1=Psychlops::random();
col.set(temp1);
sampleA.pix(x+centerX,y+centerY,col);
}
}
sampleA.to(Noise1,rect1);
sampleA.flip();
while(!Input::get(Keyboard::spc));
Noise1.save("test.png");
}
}}}
次は保存するパスを指定して保存します。
{{{
<例2>
#include <psychlops.h>
using namespace Psychlops;
double width=100,height=100;
double temp1;
double centerX=700,centerY=500;
Psychlops::Rectangle rect1(width,height);
Psychlops::Color col;
Psychlops::Image Noise1;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
rect1.shift(centerX,centerY);
for(int x=0; x<width; x++){
for(int y=0; y<height; y++){
temp1=Psychlops::random();
col.set(temp1);
sampleA.pix(x+centerX,y+centerY,col);
}
}
sampleA.to(Noise1,rect1);
sampleA.flip();
while(!Input::get(Keyboard::spc));
Noise1.save("%APP%test.png");
}
}}}
指定のパスに保存されたか確認してください。
今までの節では自分でオフスクリーン上に図形を描画しました。
この節では既存の画像ファイルを読み込みます。
!Image::load()
既存の任意の画像ファイルを読み込みます。
|!Image::load()|load(filename)|指定した画像ファイルを読み込みます|>|>|
|~|~|filename: 読み込む画像ファイルの名前を指定|>|>|
|~|~|保存先を指定しない場合のパス|Mac:Macintosh HD/ユーザ/ユーザ名(任意)/書類/Psychlops/ ^^*1^^|>|
|~|~|~|Win:実行ファイルと同じパス|>|
|~|~|読込みをする場合のパス指定|%APP%|Mac:実行バンドル(.app)と同じパス|
|~|~|~|~|Win:実行ファイルと同じパス|
|~|~|~|%RESOURCES%|Mac:実行バンドルの内部のResourcesフォルダ|
|~|~|~|~|Win:実行ファイルのパスの直下のrscという名前のフォルダ|
|~|~|~|%~USER_HOME%|Mac:"~/"と同じ(/Users/(user)/)|
|~|~|~|~|Win:ユーザープロファイルフォルダ(C:\Documents and Settings\(user)\)|
|~|~|~|%~USER_SETTING%|Mac:"~/Library/Psychlops/"|
|~|~|~|~|Win:"%~AppData%Psychlops"|
|~|~|~|%~USER_DOCUMENTS%|Mac:"~/Documents/Psychlops"|
|~|~|~|~|Win:ユーザープロファイルフォルダ\My Documents\Psychlops|
|~|~|~|%~USER_DOCUMENTS_ROOT%|Mac:"~/Documents"|
|~|~|~|~|Win:ユーザープロファイルフォルダMy Documents\|
*1 Finderでの表示は上記のとおりですが、Psychlopsで指定する場合は下記のUnix形式でパスを指定してください
** "~/Documents/Psychlops/"
*2 MacOSX版では、日本語パス名は使用できません。強制終了してしまいます。
*3 VISTA環境の場合、パス指定が正常に動作しません。VISTA環境の方はお手数ですが保存先を指定しないでお使いください。
!Image::load()の書き方
[[3.3.1節|3.3.1 画像ファイルを保存する]]の<例1>で保存したランダムドットを読み込みます。
表示は[[Image::draw()]]で行います。
{{{
<例1>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Image Noise1;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
Noise1.load("test.png");
Noise1.centering();
Noise1.draw();
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
次にパス指定を行い、画像ファイルを読み込みます。
{{{
<例2>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Image Noise1;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
Noise1.load("%APP%test.png");
Noise1.centering();
Noise1.draw();
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
スクリーンキャプチャのように、画面上に描画したイメージをオフスクリーンに取り込むことができます。
|!Canvas::to()|to(Image target, Rectangle source)|VRAM上にある画面イメージをメインメモリ(主記憶装置)にあるImageに転送します|
|~|~|Image target: 転送先のImageを指定します|
|~|~|Rectangle source: 転送する画面上の範囲を指定します|
この際、転送先のImageがsetされていなければ自動的にsetします。setされていた場合、破棄されて上書きされます。
{{{
#include <psychlops.h>
using namespace Psychlops;
void psychlops_main() {
Canvas d(Canvas::fullscreen);
Psychlops::Rectangle rect(100,100);
rect.centering();
// check rect external bordar and Rectangle.resize validity
rect.resize(102,102).draw(Color::red);
// check rect internal bordar and Rectangle.resize validity
rect.resize(100,100).draw(Color::green);
// rect reference
rect.resize(98,98).draw(Color::gray);
rect.resize(100,100);
// check Canvas.to(Image, Rectangle) validity
Image img;
d.to(img, rect);
img.centering().shift(0,-200).draw();
d.msg("Canvas.to(Image)", d.getHcenter(), d.getVcenter()-200);
// check Canvas.copy(Rectangle, Rectangle) validity
d.copy(rect, rect.dup().shift(200,0));
d.msg("Canvas.copy", d.getHcenter()+200, d.getVcenter());
d.flip();
// Wating roop
while(!Input::get(Keyboard::esc)) {
}
}
}}}
[[2.3節|2.3 文字列の描画]]ではLettersクラスを使った2バイト文字列の描画について最低限の事だけを説明しました。実はLettersクラスはImageクラスを応用して作成されています。
ここでは、やや応用的ですが、本節で説明したImageクラスの仕様を踏まえてより詳細なLettersクラスの仕様と関連情報について説明します。
* [[レンダリングのタイミング]]
* [[ワイド文字列の入出力]]
この章ではここまで用いてきたいくつかの描画のためのクラスをまとめて一つのグループとして取り扱う方法を紹介します。
たとえば、いくつかのRectangleやImageをまとめて動かしたい時に、それぞれに対してshiftメソッドを使うというやり方は少し面倒です。
こんな時に、これらを一つのグループにまとめてしまって、そのグループ全体に対して移動の操作をすることができれば、プログラムはかなり見通しがよくなるでしょう。
また、描画したものをある点を中心に回転させたいというような時に、回転のための数学操作を考えるのはかなり面倒です。
PsychlopsはOpenGLを用いて描画を行っているのですが、OpenGLは描画内容の回転や拡大・縮小をグラフィックチップに計算させることが簡単にできます。Psychlopsではグループ化したオブジェクトに対してこれらの変換を行うことが可能です。
[[4.1 複数の描画オブジェクトをグループ化するーGroupクラスー]]
[[4.2 描画内容の回転および拡大縮小]]
!Groupクラスの構造
1. Groupクラスの変数を宣言する
2. GroupクラスにShapeを追加する
3. Groupクラスに対して操作を行う
4.(必要に応じて)ShapeをGroupから外す
! 1. Groupクラスの宣言
グループクラスの変数宣言は特に引数などは必要ではありません。
Canvasの取得後である必要がある?
{{{
code sample
}}}
! 2. GroupクラスにShapeを追加する
ここまで取り扱ってきたRectangleやImageといったクラスはすべてShapeという大きなクラスを元に構成されています(クラスの継承)。Shapeクラスに属するクラスは以下の通りです。
|Line|線領域|
|Rectangle|四角形の領域|
|Polygon|多角形の領域|
|Ellipse|円を含む楕円形の領域|
|Letters|文字列を格納するための領域(テキストボックス)|
Shapeクラスの詳細については[[]]
これらのクラスはここまででも扱ってきたようなcentering(), shift(), draw()などの共通する命令セットを持っています。これらの命令が実際にどのように振る舞うかはクラスの内容に合わせて異なっていますが、引数などの命令の形はどれも共通になっています。これらの共通の命令セットの一つとしてadd()命令があります。
|!Shape::add()|add(Group gp)|>|Shapeをグループクラスに追加します。|
|~|~|Group gp|追加先のグループを指定|
コードの説明
{{{
code sample
}}}
またGroupに対してappend()命令を使用しても同じことができます。
使いやすい方を使うとよいでしょう。
|!Group::append()|append(Shape shp)|>|Shapeをグループクラスに追加します。|
|~|~|Shape shp|追加するShapeを指定|
コードの説明
{{{
code sample
}}}
[[5.1 外部装置(キーボード/マウス)の操作内容を取得する]]
[[5.2 ファイルの入出力]]
!Inputクラスとは
コンピュータにあるキーボードやマウスから反応を取得するためのクラスとしてInputクラスがあります。
現バージョンでPsychlopsはキーボードとマウスの反応を取得することが可能です。Psychlopsでは、キーボードのそれぞれのキーやマウスのボタン、カーソル位置をそれぞれKeyboard, Mouseクラスのメンバとして取り扱っています。具体的な使い方を以下で説明します。
!Input::get() -反応を取得する-
これまでのソースの中でwhile(!Input::get(Keyboard::spc))という命令をスペースキー待ちの命令として使ってきました。これは、"スペースキーが押されるまでループを繰り返しなさい"という命令です。この文でも使っているようにInput::get()命令は引数として指定された入力機器の反応があれば1(TRUE)を返します。入力が無ければは0(FALSE)を返します。Input::get()命令の第2引数は省略可能です。この場合には、Input::get()命令は~KeyStateにはpushedが指定されたとみなします。
|!Input::get()|Input::get([[Keyboard::KeyCode|キーコード表]] code, [[Keyboard::KeyState]] state)|キーボードの反応を取得します|
|~|~|code 反応を取得するキーの名前 [[キーコード表]] 参照|
|~|~|state 反応状態のオプション (省略時はpushed) [[Keyboard::KeyState]]参照|
|~|Input::get([[Mouse::ButtonCode|キーコード表]] code, [[Mouse::ButtonState]] state)|マウスの反応を取得します|
|~|~|code 反応を取得するマウスボタンの名前 [[キーコード表]] 参照|
|~|~|state 反応状態のオプション (省略時はpushed) [[Mouse::ButtonState]]参照|
!! ~KeyState(~MouseState)の使い方
第2引数のの"~KeyState"・"~MouseState"の使い方は少し難しいかもしれません。以下でこれらの"~KeyState"の使い方を見てみましょう。
~KeyStateには"pushed", "pressed", "released"の3つのモードが存在します。これら3つのモードの意味を実例とともに見ていきましょう。
はじめに"pushed"を使った例を見てみましょう。ここでは、Input::get()命令の第2引数として"pushed"を明示的に指示しましたが、省略しても、挙動は変わりません。
[[例1: pushedを使ったソース]]
このソースを実行すると中央に表示された四角形がカーソルの左右キーで左右に動きます。キーを押し続けても一度しかシフトが起こらないことに注目してください。"pushed"を指定したときにInput::get()命令がTRUEを返すのは、キーが押し込まれたフレームより後に実行されたうちのはじめの一回だけです。キーを離すと元の状態に戻って、またキーが押し込まれたフレーム以降に一度だけTRUEが返されます。
次に、"pressed"を指定した例です。例1とソースはほとんど変わりませんが、Input::get()命令の第2引数だけが"pressed"に変わっています。
[[例2: pressedを使ったソース]]
このソースを実行すると、やはりカーソルキーにしたがって四角形が左右に動きますが、例1と違って押している間、常に動き続けています。"pressed"を指定すると、キーが押し込まれている間ずっとInput::get()命令はTRUEを返します。
次に"released"を使った例について見てみましょう
[[例3: releasedを使ったソース]]
この例では、例2に"released"を指定したInput::get()命令の文が付け加わっています。実行してみると、四角形はやはりキーを押している間、常に動き続けますが、キーを離すと中央に戻ってしまいます。キーが離されたことが"released"を指定したInput::get()命令によって検出されると、四角形がcentering()命令を使って中央に戻るようにコードが組まれています。このように"released"は指定したキーが離されたことを検出するためのオプションです。
ここで、例の中にInput::refresh()という命令が使われています。これは、キーボードやマウスのボタン検出を初期化する命令です。複雑な入出力動作をするプログラムを書くときにはこの命令でキーボード・マウスの初期化をしておくことをおすすめします。
|!Input::refresh()|refresh(void)|キーボード・マウスの入出力ルーチンを初期化します|
参考[[Tips: Psychlopsにおけるキーボードマウスの入出力検出ルーチン]]
最後に、これらをまとめて比較するためのデモプログラム例を載せておきます。a/sでpushedのデモ, z/xでpressed, q/wでpressed & released, cで四角形の位置初期化です。
[[例4: pushed,pressed,releasedを使ったソース]]
以上では、キーボードの入力取得オプションである"~KeyState"を例として扱いましたが、"~MouseState"の使い方もまったく同じです。以下に"~MouseState"を使った簡単なプログラム例を挙げます。上の例2とほとんど変わりません。右クリックで右方向、左クリックで左方向へ移動します。
[[例5: Mouse::pressedを使ったソース]]
! カーソル座標の取得 とマウスカーソルの表示切替
マウスの座標はMouseクラスのメンバMouse::x(水平座標), Mouse::y(垂直座標)に記録されています。このメンバに対して代入を行うとマウスの位置も移動します。
|!Mouseのメンバ|Mouse::x|マウスの水平座標(代入可能)|
|~|Mouse::y|マウスの水平座標(垂直座標)|
マウスカーソルの表示切替はMouse::show(), Mouse::hide()命令を使用してください。
|!Mouse::show()|show()|マウスカーソルを表示する|
|!Mouse::hide()|hide()|マウスカーソルを非表示にする|
次の例では、マウスカーソルを最初に画面中央に移動した後に、ユーザーのマウスの動きに合わせてマウスカーソルを動かします。左クリックすると、マウスカーソルの下にあるオブジェクトを選択して、ドラッグできます。また、右クリックでマウスカーソルを描画しないようにします。
[[例6: マウスのデモ]]
この節では簡易的なファイルの入出力命令について説明します。
PsychlopsはC, C++の標準命令と混在が可能なので、そちらの方法になれている方は、標準の入出力関数を用いてデータの保存、読み込みを行っていただいても問題ありません。
しかし、単にプログラム中で用いた配列に格納されたデータを保存するだけの時に標準の入出力関数を使うのが面倒な時があります。
Psychlopsではファイルの入出力を簡易的に行う命令Data::savearray(), Data::loadarray()が準備されています。ただし、これらの命令を使用してデータを保存するには以下の条件が満たされている必要があります。
# 保存する複数の配列のサイズが同じ
# 保存する配列の数が20個以下
Data::loadarray()も同様にサイズが同じ20個以下の配列を読み込むことができます。
[[5.2.1 ファイルへの出力]]
[[5.2.2 ファイルからの入力]]
!ファイルへの出力
はじめに配列を保存するための命令Data::savearray()から説明を行います。
|!Data::savearray()|savearray(filename, header, length, ...)|指定した配列を保存します|>|>|
|~|~|filename: 保存する配列のファイル名称(拡張子まで)を指定|>|>|
|~|~|保存先を指定しない場合のパス|Mac:Macintosh HD/ユーザ/ユーザ名(任意)/書類/Psychlops/ ^^*1^^|>|
|~|~|~|Win:実行ファイルと同じパス|>|
|~|~|保存をする場合のパス指定|%APP%|Mac:実行バンドル(.app)と同じパス|
|~|~|~|~|Win:実行ファイルと同じパス|
|~|~|~|%RESOURCES%|Mac:実行バンドルの内部のResourcesフォルダ|
|~|~|~|~|Win:実行ファイルのパスの直下のrscという名前のフォルダ|
|~|~|~|%~USER_HOME%|Mac:"~/"と同じ(/Users/(user)/)|
|~|~|~|~|Win:ユーザープロファイルフォルダ(C:\Documents and Settings\(user)\)|
|~|~|~|%~USER_SETTING%|Mac:"~/Library/Psychlops/"|
|~|~|~|~|Win:"%~AppData%Psychlops"|
|~|~|~|%~USER_DOCUMENTS%|Mac:"~/Documents/Psychlops"|
|~|~|~|~|Win:ユーザープロファイルフォルダ\My Documents\Psychlops|
|~|~|~|%~USER_DOCUMENTS_ROOT%|Mac:"~/Documents"|
|~|~|~|~|Win:ユーザープロファイルフォルダMy Documents\|
|~|~|ファイル名に%TIME_という文字列があると、日時分秒に自動変換されます|>|
|~|~|header: 配列の冒頭に加える説明などを記述します|>|>|
|~|~|length: 保存する配列の大きさを指定します|>|>|
|~|~|...: 保存する配列を指定します。最大20個まで指定できます|>|>|
*1 Finderでの表示は上記のとおりですが、Psychlopsで指定する場合は下記のUnix形式でパスを指定してください
** "~/Documents/Psychlops/"
*2 MacOSX版では、日本語パス名は使用できません。強制終了してしまいます。
*3 VISTA環境の場合、パス指定が正常に動作しません。VISTA環境の方はお手数ですが保存先を指定しないでお使いください。
以下でこの命令の使い方の例を見てみましょう。
{{{
<例1>
#include <psychlops.h>
using namespace Psychlops;
const int MaxTrials = 100;
int condition[MaxTrials], result[MaxTrials]
void psychlops_main() {
for(int CurrentTrial=0; CurrentTrial<MaxTrials; CurrentTrial++) {
condition[CurrentTrial] = Psychlops::random(2);
// ...実験の描画処理
if(Input::get(Keyboard::left))
result[CurrentTrial] = 0;
if(Input::get(Keyboard::right))
result[CurrentTrial] = 1;
}
}
}}}
上のプログラム例では、変数配列resultには、被験者のキー押しによって0か1かの数値が入ります。この配列を配列conditionに保存されている実験条件とともにsavearray()命令を用いて、”sample_保存時間.txt"という名前のファイルに保存するには以下のように命令を書きます。
{{{
Data::savearray("sample_%TIME_.txt", "conditions\tresults", MaxTrials, condition, result);
}}}
第1引数はファイル名の指定、第2引数はファイルのヘッダ(1行目の内容)です。conditionおよび、resultの配列の個数はMaxTrials(100)個なので、第3引数はMaxTrials, それ以下に実際の配列の名前であるcondition, resultが続きます。
実際のプログラムは以下の様になります。
{{{
<例2>
#include <psychlops.h>
using namespace Psychlops;
const int MaxTrials = 100;
int condition[MaxTrials], result[MaxTrials]
void psychlops_main() {
for(int CurrentTrial=0; CurrentTrial<MaxTrials; CurrentTrial++) {
condition[CurrentTrial] = Psychlops::random(2);
// ...実験の描画処理
if(Input::get(Keyboard::left))
result[CurrentTrial] = 0;
if(Input::get(Keyboard::right))
result[CurrentTrial] = 1;
}
Data::savearray("sample_%TIME_.txt", "conditions\tresults", MaxTrials, condition, result);
}
}}}
標準の出力パスに「sample_(日時).txt」というファイルが出力されたでしょうか?
ファイルは、配列を縦に並べたタブ区切り形式となります。上記の例を適用すると
| ヘッダ| |
| Condition[0] | Result[0] |
| Condition[1] | Result[1] |
| Condition[2] | Result[2] |
| ... | ... |
の順番で記録されています。このファイルを実際に開くと、以下の例のようになっています。
{{{
<例3>
conditions results
0 1
1 1
0 0
0 1
1 0
...
}}}
この結果ファイルはそのままExcelなどに読み込むことが出来ます。
Data::savearray()とは逆に、ファイルからデータを読み込むことも出来ます。
データは配列に読み込まれるため、savearrayで書き込んだものをそのまま読み込むことが出来ます。
!Data::savearray()
テキストファイルから配列に読み込みます。
|!Data::savearray()|savearray(filename, skipped_lines, length, ...)|指定した配列を読み込みます|>|>|
|~|~|filename: 読み込むファイル名称(拡張子まで)を指定|>|>|
|~|~|保存先を指定しない場合のパス|Mac:Macintosh HD/ユーザ/ユーザ名(任意)/書類/Psychlops/ ^^*1^^|>|
|~|~|~|Win:実行ファイルと同じパス|>|
|~|~|読み込む場合のパス指定|%APP%|Mac:実行バンドル(.app)と同じパス|
|~|~|~|~|Win:実行ファイルと同じパス|
|~|~|~|%RESOURCES%|Mac:実行バンドルの内部のResourcesフォルダ|
|~|~|~|~|Win:実行ファイルのパスの直下のrscという名前のフォルダ|
|~|~|~|%~USER_HOME%|Mac:"~/"と同じ(/Users/(user)/)|
|~|~|~|~|Win:ユーザープロファイルフォルダ(C:\Documents and Settings\(user)\)|
|~|~|~|%~USER_SETTING%|Mac:"~/Library/Psychlops/"|
|~|~|~|~|Win:"%~AppData%Psychlops"|
|~|~|~|%~USER_DOCUMENTS%|Mac:"~/Documents/Psychlops"|
|~|~|~|~|Win:ユーザープロファイルフォルダ\My Documents\Psychlops|
|~|~|~|%~USER_DOCUMENTS_ROOT%|Mac:"~/Documents"|
|~|~|~|~|Win:ユーザープロファイルフォルダMy Documents\|
|~|~|skipped_lines: 冒頭から読み込みを飛ばす行数を指定します|>|>|
|~|~|length: 読み込む配列の大きさを指定します|>|>|
|~|~|...: 読み込む配列を指定します。最大20個まで指定できます|>|>|
*1 Finderでの表示は上記のとおりですが、Psychlopsで指定する場合は下記のUnix形式でパスを指定してください
** "~/Documents/Psychlops/"
*2 MacOSX版では、日本語パス名は使用できません。強制終了してしまいます。
*3 VISTA環境の場合、パス指定が正常に動作しません。VISTA環境の方はお手数ですが保存先を指定しないでお使いください。
savearrayで保存したファイルを読み込んでみましょう。savearrayでは通常冒頭2行にファイルの説明を加えるため、skipped_linesには2を指定します。ファイル名中で%TIME_を指定すると現在時刻に変換されてしまうため、読み込むファイルはあらかじめ別の名前にしておきます。ここではsample.txtとしました。
{{{
<例1>
#include <psychlops.h>
using namespace Psychlops;
const int MaxTrials = 100;
int condition[MaxTrials], result[MaxTrials]
void psychlops_main() {
Data::loadarray("sample.txt", 2, MaxTrials, condition, result)
}
}}}
これでsample.txtの中身が配列に読み込まれました
[[6.1 時間を計測する]]
[[6.2 各種ツールを使ってみる]]
[[6.2.1 描画の時間精度を確認する-FPSチェッカー]]
[[6.2.2 画面に反映されない描画の進行度を表示する-プログレスバー]]
[[6.3 行列演算を行う]]
[[6.3.1 行列の宣言]]
[[6.3.2 行列の要素の操作]]
[[6.3.3 行列全体の操作]]
[[6.3.4 行列の加減乗除]]
[[6.3.5 行列の中身をオフスクリーンに描画する]]
[[6.3.6 メッシュグリッドの作成]]
!Clockクラスの概要
Psychlopsで時間を計測する方法は2種類あります。
画面のリフレッシュを基準として、リフレッシュされたフレーム数をカウントすることです。この方法は比較的簡単ですが、1フレームの時間以上の精度で時間を制御することができません。
そこで、より高い精度で時間を計測するためのクラスとしてPsychlopsにはClockクラスが用意されています。このClockクラスはCPUタイマーを利用することによって高い精度の時間間隔の計測を実現しています。
!Clockクラスの使い方
Clockの宣言は特に難しくありません。[[Psychlops::Clock]]型のインスタンスを以下のような形式で単に宣言するだけです。
{{{
Psychlops::clock Timer;
}}}
このTimerに現在のCPUタイマーの値を入力するにはClock::update()命令を使用します。
|!Clock::update()| Clock::update(void)| 現在のCPUタイマーの値を入力する|
このままでは、保持されている時刻はCPUタイマーのカウントなので、実際にプログラム内でこのクラスを使うときにはClock::at_msec()命令を使用してタイマーのカウントをmsec単位に変換して使用します。
|!Clock::at_msec()| double Clock::at_msec(void)| カウントをmsec単位に変換(double)して返す|
下にClockクラスを用いた簡単な時間計測例を挙げて見ました。スペースキーを押している時間が画面に表示されます。
{{{
* Clockクラスの使用例
#include <psychlops.h>
using namespace Psychlops;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
double elapsed=0.0, dcx, dcy;
Clock Start, End;
dcx=sampleA.getHcenter();
dcy=sampleA.getVcenter();
sampleA.clear(Color(0.5));
sampleA.flip();
while(!Input::get(Keyboard::esc)) {
if(Input::get(Keyboard::spc, Keyboard::released)){
End.update();
elapsed=(End-Start).at_msec();
sampleA.clear(Color(0.5));
sampleA.flip();
sampleA.clear(Color(0.5));
sampleA.msg("Elapsed Time(msec)", dcx-100,dcy-30, Color::white);
sampleA.var((int)elapsed,dcx ,dcy, Color::white);
sampleA.flip();
}
if(Input::get(Keyboard::spc, Keyboard::pushed)){
Start.update();
Input::refresh();
}
}
}
}}}
^^ごく稀にWindows環境下でこのプログラムを実行すると、キー押し時間がマイナスになることがあるようです。このような状態が起こったら、並行して動いているプログラムを終了させて、再起動をしてからこのプログラムを実行してみてください。参考[[Tips: 時間精度が必要なプログラムを実行するとき]]^^
Psychlopsにはここまで扱ったような簡単な描画命令だけではなく、描画内容をチェックするためのツールが実装されています。ここでは、これらのツールについて説明します。
[[6.2.1 描画の時間精度を確認する-FPSチェッカー]]
[[6.2.2 画面に反映されない描画の進行度を表示する-プログレスバー]]
!!FPSチェッカの概要
Psychlopsには、現在描画している画面がプログラムで指定した時間通りに実行されているかどうかをチェックするツールがあります。
たとえば、以下のようなコードを考えて見ます。
{{{
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
while(true){
非常に時間のかかる描画コード
sampleA.flip();
}
}}}
このプログラムを書いたときの意図は、描画を毎フレームごとに更新するというものでしょう。(でなければ、なんらかの「待ち」処理がsampleA.flip()の前後に入っているはずです。)
しかし、非常に複雑な描画内容をCanvasの裏画面に書いたり、大きなImageを転送するときは、描画に1フレーム分以上の時間かかることがあります。この場合、psychlopsは描画が完了するまで待ってから[[Canvas::flip()]]命令を実行するので実際に描画が更新されるのは2フレーム以上たってからということになります。
問題は、このような"こま落ち"はただ見ているだけではわからないことが多いことです(複雑な刺激呈示の途中に16.7 msと33.3msの区別がつくでしょうか?)。
psychlopsにはこのような"こま落ち"を検出するためのFPSチェッカと呼ばれるツールが準備されています。
このFPSチェッカを使うには、まず時間計測を行いたい部分の前にFPSチェッカを起動する命令を書きます。
これには、Canvas::watchFPS()命令を使用します。
|!Canvas::watchFPS()|void watchFPS(void)|FPSチェッカをonにします|
このままでは、FPSチェッカが起動しているだけで、その内容を見ることはできません。
FPSチェッカの内容を表示するためには、[[Canvas::flip()]]命令の直前にCanvas::showFPS()命令を書いてFPSチェッカのウィンドウを表示させます。
|!Canvas::showFPS()|void showFPS(void)|FPSチェッカのレポートを画面左上に表示します|
Canvas::showFPS()命令を実行すると、半透明のレポートウィンドウが画面右上に表示されます。このレポートウィンドウは、すべての描画内容の上に重ねて表示されます(以下を参照してください)
!!FPSチェッカの使い方
FPSチェッカを実際に使ってみましょう
以下のコードは5フレームに一度描画内容を更新して、コマ落ちが発生しているかどうかを表示します。
{{{
#include <psychlops.h>
using namespace Psychlops;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
sampleA.watchFPS();
while(!Input::get(Keyboard::spc)){
sampleA.showFPS();
sampleA.clear(0.5);
sampleA.flip(5);
}
}
}}}
このコードを60Hzのディスプレイで実行すると、以下のような画面が表示されます。
[img[image/FPSchecker.png]]
showFPS()命令を実行した後に、Canvas::clear()命令を実行しているにもかかわらずレポートウィンドウが表示されていることに注意してください。このように、実際にはレポートウィンドウは全ての描画が終了した後に描画されます。
このレポートウィンドウを拡大したものが下図です。
[img[image/FPSwindow.png]]
レポートウィンドウは3行から成り立っています。一番上の赤い数字は、これまでにコマ落ちしたフレーム数、真ん中の白い数字は表示された総フレーム数、一番下の青い数字は前回のflip()から今回のflip()までにかかった時間(msec単位)です。
この例では、5フレームに一度描画が更新されているので、60Hzだと画面更新間の時間は1/12 sec = 83.3 msecで、一番下の青い数字と一致していることに注目してください。
!プログレスバーの概要
大量の計算をコンピュータに行わせているとき、画面には何の変化も起きずにプログラムが暴走しているのか、単に計算に時間がかかっているだけなのかの区別がつかないときが良くあります(本当は、暴走をさせないようなプログラム構造にするべきですが・・・)。進行度を文字で画面に表示する方法もありますが、文字表示のために[[Canvas::flip()]]命令を挟んでしまうと計算速度が落ちてしまいます。
このような状況で、画面に計算の総量に対して今どのくらいの割合が終了したかを表示するためのツールにプログレスバーがあります。このプログレスバーは強制的にフロントバッファ(現在表示中の画面)に表示されるため、[[Canvas::flip()]]命令は必要ありません。
このプログレスバーを表示するにはCanvas::progressbar()命令を使用します。
|!Canvas::progressbar()|void progressbar(X now, X max)|現在のnowの値のmaxに対する比率をプログレスバーとして表示します。now, maxは数値型であればどの型でもかまいません|>|
|~|~|X now|現在の進行度を示す変数値(型は数値型)|
|~|~|X max|nowの最終的な到達値(型は数値型)|
|~|void progressbar(double ratio)|ratioで示される比率をプログレスバーとして表示します。|>|
|~|~|double ratio|比率の値(0.0~1.0)|
この命令を使用すると、画面左上に灰色の四角のなかに時間とともに伸びる青い四角形が表示されます。この青い四角形が灰色の四角いっぱいまで伸びると、nowの値がmaxまで到達した(ratioが1.0になる)ことを示しています。
!!プログレスバーの使い方
実際の使い方の例を見てみましょう。
下の例では100 msecと50 msecの"待ち"を100回繰り返します。
時間経過とともに左上に提示されるプログレスバーが伸びていく様子に注目してください。
{{{
#include <psychlops.h>
using namespace Psychlops;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
Clock timer1, timer2;
int temp;
sampleA.watchFPS();
sampleA.clear(0.5);
sampleA.message("calculating...", sampleA.getHcenter(), sampleA.getVcenter());
sampleA.flip();
temp=0;
for(int i=0; i<100; i++){
timer1.update();
sampleA.progressbar(i,100);
while((timer2-timer1).at_msec()<100){timer2.update();}
if(Input::get(Keyboard::esc))break;
}
sampleA.clear(0.5);
sampleA.message("10 sec elapsed", sampleA.getHcenter(), sampleA.getVcenter());
sampleA.flip();
while(!Input::get(Keyboard::spc));
sampleA.clear(0.5);
sampleA.message("calculating...", sampleA.getHcenter(), sampleA.getVcenter());
sampleA.flip();
temp=0;
for(int i=0; i<100; i++){
timer1.update();
sampleA.progressbar((double)i/100.0);
while((timer2-timer1).at_msec()<50){timer2.update();}
if(Input::get(Keyboard::esc))break;
}
sampleA.clear(0.5);
sampleA.message("5 sec elapsed", sampleA.getHcenter(), sampleA.getVcenter());
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
!Matrixクラスの概要
!!行列とは?
Matrixクラスは行列を取り扱います。線形代数としての取り扱いもできますが、Matlabの行列のように数値を2次元的に配置した表や画像(Image)のようなものとしても利用できます。Imageは精度が低いため、Imageに保存した色データをもとに再計算するのはお勧めできませんが、Matrixではそのような計算をしても十分な精度があります。
!!MatrixとImageの違い
MatrixとImageは基本的に2次元のよく似たデータ構造を持っており、相互に変換が可能です。(実際にはImageは下で述べるようなより複雑なデータ構造を持っています。)ただし、両者にはいくつかの相違点があります。
[Img[Image/Image_Matrix.png]]
* Matrixの要素アクセスは行(縦)列(横)の順で指定し、起点は1です。ImageはX(横)Y(縦)の順で指定し、起点は0です。
[Img[Image/ImagetoMatrix.png]]
* Imageは一つのx,y値に対してRGB 3値、RGBA 4値を取り扱うことが出来ます(数学的にはテンソルの構造を持っています)。Matrixは行列の要素として一つの値しか取り扱えないため、3値または4値のImageに変換する場合、それぞれの座標におけるR,G,B,(A)の値を示す同じ次元を持った3つまたは4つの行列を用意する必要があります。
[Img[Image/ColImagetoMatrix.png]]
* Matrixは精度を重視して64bit浮動小数点(double型)を取り扱います。Imageは速度を重視して8bit整数(unsigned char型)を基本として取り扱います(32bit浮動小数点 float型にすることもできます)
[[6.3.1 行列の宣言]]
[[6.3.2 行列の要素の操作]]
[[6.3.3 行列全体の操作]]
[[6.3.4 行列の加減乗除]]
[[6.3.5 行列の中身をオフスクリーンに描画する]]
[[6.3.6 メッシュグリッドの作成]]
!Matrixクラスの概要
!!行列とは?
Matrixクラスは行列を取り扱います。線形代数としての取り扱いもできますが、Matlabの行列のように数値を2次元的に配置した表や画像(Image)のようなものとしても利用できます。Imageは精度が低いため、Imageに保存した色データをもとに再計算するのはお勧めできませんが、Matrixではそのような計算をしても十分な精度があります。
!!MatrixとImageの違い
MatrixとImageはよく似ており、相互に変換が可能です。
* Matrixは精度を重視して64bit浮動小数点(double型)を取り扱います。Imageは速度を重視して8bit整数(unsigned char型)を基本として取り扱います(32bit浮動小数点 float型にすることもできます)
* ImageはRGB 3値、RGBA 4値を取り扱うことが出来ます。Matrixは1値しか取り扱えないため、3値または4値のImageに変換する場合、3つまたは4つの行列を用意する必要があります。
* Matrixの要素アクセスは行(縦)列(横)の順で指定し、起点は1です。ImageはX(横)Y(縦)の順で指定し、起点は0です。
!行列の宣言文
行列を確保するには、行数と列数を指定します。
Matrix()のように宣言のみの場合は''Matrix::set()''命令で指定します。
|!Matrixの宣言|Matrix()|行列の使用を宣言します|
|~|Matrix(rows, cols)|指定されたrows行cols列の行列を宣言します|
|~|~|long rows :行数を指定|
|~|~|long cols :列数を指定|
!Matrix::set()
行列の宣言時に何の指定もしなかった場合、Matrix::set()命令を使用します。
|!Matrix::set()|Matrix(rows, cols)|指定されたrows行cols列の行列を設定します|
|~|~|long rows :行数を指定|
|~|~|long cols :列数を指定|
!!宣言文の書き方
{{{
<例1>
#include <fstream>
#include <psychlops.h>
using namespace Psychlops;
Matrix LuminanceMap1(200, 100);
void psychlops_main() {
std::ofstream result("dump.txt");
result << LuminanceMap1;
}
}}}
次はMatrixの宣言と[[Matrix::set()]]命令を組み合わせたコードです。
{{{
<例2>
#include <fstream>
#include <psychlops.h>
using namespace Psychlops;
Matrix LuminanceMap1;
long Width=10, Height=10;
void psychlops_main() {
std::ofstream result("dump.txt");
LuminanceMap1.set(Height, Width);
result << LuminanceMap1;
}
}}}
これらのコードを実行すると、プログラムと同じ場所に作成されるファイルdump.txtにMatrixの中身が保存されます。
6.3.1節ではMatrixの宣言(領域確保)のみを行っていたので、Matrixの要素に0が代入されるだけでした。
この節では実際にMatrix中に数値を代入していきます。
!Matrix全体への代入
Matrix全体をひとつの値で埋め尽くすには、Matrixに対して数値の代入を行います。
{{{
<例1>
#include <fstream>
#include <psychlops.h>
using namespace Psychlops;
void psychlops_main() {
std::ofstream result("dump.txt");
Matrix LuminanceMap1(5, 5);
LuminanceMap1 = 7.0;
result << LuminanceMap1;
}
}}}
これで全体に7が代入されました。
!Matrix(row, col) 単独要素
Matrix中の要素にアクセスするための命令です。値を取り出すことも代入することも出来ます。二次元の配列に似た扱い方になりますが、要素の指定が行(縦)列(横)の順番であること、起点が0ではなく1であることに注意してください。
|!Matrix()|Matrix(row, col) |要素を取り出します。ちょうど配列のように、値を取り出すことも代入することもできます。|
|~|~|int row: 要素の行を指定します。|
|~|~|int col: 要素の列を指定します。|
行列の範囲外の値が指定された場合はエラーになります。エラーメッセージを表示して強制終了します。
!!Matrixの要素へのアクセスの仕方
2行2列目の要素に数値を代入し、さらにその値を取り出します。
{{{
<例2>
#include <fstream>
#include <psychlops.h>
using namespace Psychlops;
const int Width = 10, Height = 10;
Matrix LuminanceMap1(Height, Width);
void psychlops_main() {
std::ofstream result("dump.txt");
LuminanceMap1(2,2) = 1.0;
std::cout << LuminanceMap1;
double a = LuminanceMap1(2,2);
result << a;
}
}}}
このプログラムを実行すると、行列の中身が10行10列で出力されます。次の行に、行列の(2,2)の値である1.0が出力されます。
!!Matrixの全要素へのアクセスの仕方
全要素にアクセスするには、forループを使って順次アクセスしていきます。
{{{
<例3>
#include <fstream>
#include <psychlops.h>
using namespace Psychlops;
const int Width = 10, Height = 10;
Matrix LuminanceMap1(Height, Width);
void psychlops_main() {
std::ofstream result("dump.txt");
for(int row; row<Width; row++) {
for(int col; col<Width; col++) {
LuminanceMap1(row, col) = row+col;
}
}
result << LuminanceMap1;
}
}}}
!Matrix(row, col) 部分行列
要素アクセスで整数を指定すると単一の要素にアクセスできました。行列のある範囲をまとめて取り扱いたい場合は、Range型を使って範囲を指定し、部分行列にアクセスします。
[img[部分行列|image/MatrixPartial.png]]
部分行列は、行列の一部分を切り取ってサイズの異なる行列であるかのように振る舞わせます。部分行列は元の行列の窓のように振る舞うため、部分行列に代入すると元の行列の一部分に代入するのと同じように作用します。
|!Matrix()|Matrix(Range row, Range col)|行列の一部分から部分行列を作ります。|
|~|~|Range row: 要素の行を指定します。|
|~|~|Range col: 要素の列を指定します。|
!!部分行列の書き方
10×10のMatrixの一部に数値を代入します。部分行列のさらに一部の要素にもアクセスできます。
{{{
<例4>
#include <fstream>
#include <psychlops.h>
using namespace Psychlops;
const int Width = 10, Height = 10;
Matrix LuminanceMap1(Width, Height);
void psychlops_main() {
std::ofstream result("dump.txt");
Range row, col;
LuminanceMap1(2<=row<=5, 3<=col<=6) = 1.0;
LuminanceMap1(2<=row<=5, 3<=col<=6)(2,2) = 2.0;
result << LuminanceMap1;
}
}}}
!!部分行列のコピー
部分行列は元の行列の窓として機能するので、部分行列への代入をすると元の行列にも代入されます。元の行列を壊したくない場合は、明示的にコピーをとっておく必要があります。
{{{
<例4>
#include <fstream>
#include <psychlops.h>
using namespace Psychlops;
const int Width = 10, Height = 10;
Matrix LuminanceMap1(Width, Height), Copy;
void psychlops_main() {
std::ofstream result("dump.txt");
Range row, col;
LuminanceMap1(2<=row<=5, 3<=col<=6) = 1.0;
Copy = LuminanceMap1(2<=row<=5, 3<=col<=6);
Copy = 2.0;
result << LuminanceMap1 << Copy;
}
}}}
!要素の再配置
ここでは、行列全体に対して回転、列のスライドなどの操作を行う命令について解説します。
Psychlopsが行列全体に対して行える操作は大まかに以下の表に示した5つです。ここではこれらの命令について解説します。
|slide|行列のスライド|
|transpose|行列の転置|
|rotation|行列全体の回転|
|cat|行列の結合|
|reshape|行列要素の再配置|
上記命令はすべて自己破壊的に作用します。
つまり、命令を実行した段階で、元の行列が命令が適用された行列に置き換わってしまいます。
これには、行列の次元の変換も含まれるので、上記命令を式の中に書いた場合には注意が必要です。
いかに注意した方がよい命令例を挙げてみます。
{{{
Matrix a(5, 3), b(3, 5);
b = a.transpose() + a;
b = b.transpose();
}}}
一行目では、 左側のa.transpose()でaが自己破壊的に転置されてしまうため、右側のaも転置されたものとして扱われてしまいます。つまり、このような命令を実行すると、
{{{
b=2*a.transpose();
}}}
と同じ結果がえられます。
2行目では、左辺のbは3行5列、右辺のbは5行3列で不正な代入にはならず、右辺bが自己破壊的に転置されて代入されます。
!slide(行・列のスライド)
|!Matrix::slide|slide(row, col)|要素を行方向、列方向にずらします。あふれた分は、反対側に移動します。|
|~|~|int row 行方向にずらす量を指定します。|
|~|~|int col 列方向にずらす量を指定します。|
!!slide操作の内容
{{{
1 2 3
4 5 6
をslide(1,0)すると
4 5 6
1 2 3
1 2 3
4 5 6
をslide(0,1)すると
3 1 2
6 4 5
1 2 3
4 5 6
をslide(1,1)すると
6 4 5
3 1 2
}}}
!!slideの書き方
{{{
#include <fstream>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Matrix LuminanceMap1;
long Width=10, Height=10;
void psychlops_main() {
std::ofstream result("dump.txt");
LuminanceMap1.set(Height, Width);
for(int row; row<Width; row++) {
for(int col; col<Width; col++) {
LuminanceMap1(row, col) = row*Height + col;
}
}
result << LuminanceMap1.slide(1,0);
}
}}}
!転置
|!Matrix::transpose|transpose()|行列を転置(行と列の読み替え)します。自分自身を書き換えるので注意してください。|
!!転置操作の内容
{{{
1 2 3
4 5 6
をtransposeすると
1 4
2 5
3 6
}}}
!!transposeの書き方
{{{
#include <fstream>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Matrix LuminanceMap1;
long Width=10, Height=10;
void psychlops_main() {
std::ofstream result("dump.txt");
LuminanceMap1.set(Height, Width);
for(int row; row<Width; row++) {
for(int col; col<Width; col++) {
LuminanceMap1(row, col) = row*Height + col;
}
}
result << LuminanceMap1.transpose();
}
}}}
!行列の回転
|!Matrix::rot90|rot90(count)|行列を90度単位で反時計回りに回転します。|
|~|~|int count 回転角度を90度単位で指定します。-3,1,5のとき90度、-2,2,6のとき180度、-1,3,7のとき270度回転します。|
!!回転操作の内容
{{{
1 2 3
4 5 6
をrot90(1)すると
3 6
2 5
1 4
}}}
!!rot90()の書き方
{{{
#include <fstream>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Matrix LuminanceMap1;
long Width=16, Height=1;
void psychlops_main() {
std::ofstream result("dump.txt");
LuminanceMap1.set(Height, Width);
for(int col; col<Width; col++) {
LuminanceMap1(1, col) = col;
}
result << LuminanceMap1.rot90(1);
}
}}}
!行列の結合
|!Matrix::catRow|catRow(other)|この行列の次の行から別の行列を継ぎ足します。|
|~|~|Matrix other: 付け足す行列を指定します。付け足してもこれ自身は消されません。|
{{{
#include <fstream>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Matrix LuminanceMap1, LuminanceMap2;
long Width=5, Height=1;
void psychlops_main() {
std::ofstream result("dump.txt");
LuminanceMap1.set(Height, Width);
LuminanceMap1 = 1;
LuminanceMap2.set(Height, Width);
LuminanceMap2 = 2;
LuminanceMap1.catRows(LuminanceMap2);
result << LuminanceMap1;
}
}}}
!!reshape(行列配置の変換)
|!Matrix::reshape|reshape(row, col)|要素を変えずに、行と列の数のみを変更します。要素数(行*列)は変更前と変更後で同じでなければなりません。|
|~|~|int row 新しい行数を指定します。|
|~|~|int col 新しい列数を指定します。|
!!reshape操作の内容
{{{
1 2 3
4 5 6
をreshape(3, 2)すると
1 2
3 4
5 6
}}}
!!reshapeの書き方
{{{
#include <fstream>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Matrix LuminanceMap1;
long Width=16, Height=1;
void psychlops_main() {
std::ofstream result("dump.txt");
LuminanceMap1.set(Height, Width);
for(int col; col<Width; col++) {
LuminanceMap1(1, col) = col;
}
result << LuminanceMap1.reshape(4, 4);
}
}}}
!その他
|!Matrix::min|min()|全要素中の最小値を得ます。|
|!Matrix::max|max()|全要素中の最大値を得ます。|
{{{
// 行列の要素に乱数を代入し、その中の最大値と最小値を抽出します。
#include <fstream>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Matrix LuminanceMap1;
long Width=10, Height=10;
void psychlops_main() {
std::ofstream result("dump.txt");
LuminanceMap1.set(Height, Width);
for(int row; row<Width; row++) {
for(int col; col<Width; col++) {
LuminanceMap1(row, col) = Psychlops::random();
}
}
result << LuminanceMap1;
result << LuminanceMap1.max() << " " << LuminanceMap1.min();
}
}}}
|!Matrix::getRows|getRows()|行数を得ます。|
|!Matrix::getCols|getCols()|列数を得ます。|
{{{
#include <fstream>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Matrix LuminanceMap1;
long Width=5, Height=3;
void psychlops_main() {
std::ofstream result("dump.txt");
LuminanceMap1.set(Height, Width);
result << LuminanceMap1.getRows() << " " << LuminanceMap1.getCols();
}
}}}
!実数との演算子
実数と行列の四則演算は、行列の各要素と実数の演算として定義されています。例えば、matrix + 1; というコードは、行列のすべての要素に1を足すことを意味しています。演算子の優先順位はC++の仕様に従います。
{{{
matrix * 10 + 1;
( matrix * 10 ) + 1; // 上の式と同じ
matrix + 1 * 10;
matrix + ( 1 * 10 ); // 上の式と同じ
}}}
*現在の仕様では、実数は必ず演算子の後ろに書いてください。演算子の前に実数があるとコンパイルエラーになります。
{{{
matrix + 1; // OK
1 + matrix; // NG
}}}
|!Matrix 演算子 実数|>|
|+|行列の要素すべてに実数を加算します|
|-|行列の要素すべてに実数を減算します|
|*|行列の要素すべてに実数を乗算します|
|/|行列の要素すべてに実数を除算します|
!! プログラム例
{{{
#include <fstream>
#include <psychlops.h>
using namespace Psychlops;
void psychlops_main() {
std::ofstream result("dump.txt");
Psychlops::Matrix a(5, 5), b(5, 5);
a = 3;
b = a + 5;
result << b;
b(2,2) = 0;
b = b * 10 - 6;
result << b;
}
}}}
!行列との演算子
行列同士の演算は一般的な線形代数の定義に従います。行列の要素同士の乗算(Matlabでのドット演算子)は *~ 演算子でサポートしています。行列同士の除算は定義されていません。
演算の実行前には、行列どうしの行数・列数が適切かどうかチェックし、不適切であればC++の例外機構によってプログラムが強制終了します。コンパイル時にはチェックされません。
実数との演算と行列どうしの演算は同じ式中に混在させることができます。演算子の優先順位はC++の仕様に従います。
|!Matrix 演算子 線形代数演算|>|
|+|行列の加算をします(可換)。両辺の行数・列数は等しくなければなりません。|
|-|行列の減算をします(不可換)。両辺の行数・列数は等しくなければなりません。|
|*|行列の乗算をします(不可換)。左辺項の行数と右辺項の列数は等しくなければなりません。|
|*~|行列の要素どうしを乗算します。Matlabでの.演算子に相当します(可換)。両辺の行数・列数は等しくなければなりません。|
不可換な演算子は、右と左のMatrixを入れ替えると演算結果が変わります。
{{{
Matrix A, B;
A+B == B+A;
A-B != B-A;
A*B != B*A;
A*~B == B*~A;
}}}
!! プログラム例
{{{
#include <fstream>
#include <psychlops.h>
using namespace Psychlops;
void psychlops_main() {
std::ofstream result("dump.txt");
Psychlops::Matrix a(5, 5), b(5, 5), c(5, 5);
a = 3;
b = 2;
c = a + b;
result << c;
b(2,2) = 0;
c = b * a - 6;
result << c;
}
}}}
!演算時の制限
Psychlopsの行列は、四則演算において最低限の最適化のために、= 演算子で代入を行ったときにすべての計算を行うよう定義されています。このため、=演算子による代入が行われないと機能しないことがあります。
以下のコードは機能します。
{{{
Matrix a, b, c, d;
c = a - b;
d = max(c, d);
}}}
以下のコードは機能しません
{{{
Matrix a, b, c;
d = max(a - b, c);
}}}
また、再帰的代入(代入先の左辺の変数が右辺にも出てくること)を行う場合は、当該行列が右辺に2回以上出現すると問題が生じる可能性があります。なるべく再帰的代入は行わないでください。
{{{
Matrix a, b;
a = a + a * a; // 右辺にaが2回以上出てくるのは危険
b = a + a * a; // 問題なし
}}}
MatrixとImageは両方とも2次元に並べられた値(輝度や色)で、相互に変換が可能です。ImageをMatrixの計算機能を使って生成・加工するときに役立ちます。また、Matrixに変換すると各色チャネルが独立して制御しやすいため、各色チャネルを別々に操作する場合などにも役立ちます。
ただし、[[この節の最初で述べたように|6.3 行列演算を行う]]、Matrixクラスの書式とImageクラスの書式がずれていることは注意して下さい。
Matrix / Imageの変換命令はImage型の一部になっています。
!MatrixからImageへの変換
|!Image::from|from( luminance )|Matrixを輝度としてImageに読み込み、グレースケール画像を作成します。|
|~|~|Matrix luminance: |
|~|from( r, g, b )|Matrixを赤、緑、青チャネルとして読み込み、RGB画像を作成します。|
|~|~|Matrix r: |
|~|~|Matrix g: |
|~|~|Matrix b: |
|~|from( r, g, b, a) |Matrixを赤、緑、青、透明度チャネルとして読み込み、RGBA画像を作成します。|
|~|~|Matrix r: |
|~|~|Matrix g: |
|~|~|Matrix b: |
|~|~|Matrix a: |
Imageの中身がすでに確保されていた場合は、破棄されます。
!ImageからMatrixへの変換
|!Image::to|to( luminance )|グレースケール画像の輝度値をMatrixに書き込みます。|
|~|~|Matrix luminance: |
|~|to( r, g, b ) |RGB画像のMatrixを赤、緑、青チャネル値をMatrixに書き込みます。|
|~|~|Matrix r: |
|~|~|Matrix g: |
|~|~|Matrix b: |
|~|to( r, g, b, a ) |RGBA画像の赤、緑、青、透明度チャネルをMatrixに書き込みます。|
|~|~|Matrix r: |
|~|~|Matrix g: |
|~|~|Matrix b: |
|~|~|Matrix a: |
書き込み先のMatrixは空である必要があります。大きさ等がImageと等しくなるように自動的に調整されます。
!Image/Matrix変換の使用例
以下のプログラムは、"sample.png"という名前のPNG画像を読み込み、赤チャネルと緑チャネルの差分を計算して"diff_sample.png"という名前で保存します。
<注>:場合によってはプログラムが正常終了せず、CPUを使い切ってしまうことがあります。そのときは、タスクマネージャ等で強制終了させてください。
{{{
<例1>
#include <psychlops.h>
using namespace Psychlops;
void psychlops_main() {
Image img;
Matrix R, G, B;
Matrix dR, dG;
img.load("sample.png");
img.to(R, G, B);
dR = R - G;
dG = G - R;
B = 0;
dR = max(dR, B);
dG = max(dG, B);
img.from(dR, dG, B);
img.save("diff_sample.png");
}
}}}
!メッシュグリッドとは
メッシュグリッドとは、行列の各要素への代入を、ループを使わず普通の数式に見える形式で書くテクニックです。Matlabでよく使用されています。
5 x 5 の大きさの行列に対する操作を行う場合、まず以下のような行列(メッシュグリッド)を作成します。
{{{
X:
0 1 2 3 4
0 1 2 3 4
0 1 2 3 4
0 1 2 3 4
0 1 2 3 4
Y:
0 0 0 0 0
1 1 1 1 1
2 2 2 2 2
3 3 3 3 3
4 4 4 4 4
}}}
この行列を座標値とみなし、各要素について演算する関数を適用します。
{{{
Patch = cos(X) * sin(Y);
}}}
これは、以下のプログラムと同等の計算を行います。
{{{
Matrix Patch(5,5);
for(int Y = 0; Y<5; Y++) {
for(int X = 0; X<5; X++) {
Patch(X, Y) = cos(X) * sin(Y);
}
}
}}}
!メッシュグリッドの生成
|!Matrix::mesh|mesh(Range row, int col)|行を変動値とするmeshgridを生成します|
|~|~|row: 行(Imageではy座標に相当)の範囲を指定します。行数は範囲から自動決定されます。|
|~|~|col: 列(Imageではx座標に相当)の列数を指定します。|
|~|mesh(int row, Range col)|列を変動値とするmeshgridを生成します|
|~|~|row: 行(Imageではy座標に相当)の行数を指定します。|
|~|~|col: 列(Imageではx座標に相当)の範囲を指定します。行数は範囲から自動決定されます。|
{{{
<使用例>
Range row, col;
Matrix m, y, x;
y = Matrix::mesh(-2<=row<=2, 5);
x = Matrix::mesh(5, -2<=col<=2);
=>
Y;
-2 -2 -2 -2 -2
-1 -1 -1 -1 -1
0 0 0 0 0
1 1 1 1 1
2 2 2 2 2
X;
-2 -1 0 1 2
-2 -1 0 1 2
-2 -1 0 1 2
-2 -1 0 1 2
-2 -1 0 1 2
}}}
行の範囲、列の範囲を同時に指定する方法もあります。引数で指定した行列にメッシュグリッドの内容が入ります。
|!Matrix::mesh|mesh(Range row, Matrix r, Range col, Matrix c)|行、列を変動値とするmeshgridを生成します|
|~|~|row: 行(Imageではy座標に相当)の範囲を指定します。行数は範囲から自動決定されます。|
|~|~|r: 行用のメッシュグリッドを指定します。|
|~|~|col: 列(Imageではx座標に相当)の範囲を指定します。列数は範囲から自動決定されます。|
|~|~|c: 列用のメッシュグリッドを指定します。|
{{{
<使用例>
Range row, col;
Matrix m, y, x;
Matrix::mesh(-2<=row<=2, y,-2<=col<=2,x);
=>
Y;
-2 -2 -2 -2 -2
-1 -1 -1 -1 -1
0 0 0 0 0
1 1 1 1 1
2 2 2 2 2
X;
-2 -1 0 1 2
-2 -1 0 1 2
-2 -1 0 1 2
-2 -1 0 1 2
-2 -1 0 1 2
}}}
!!Meshgridに使用可能な関数
ここに記述されている関数はすべて自己破壊せず、新たな行列を生成して返します。
|!Matrix::sin|sin(Matrix m)|mの各要素に対してsin関数の返り値を代入した行列を返します。|
|!Matrix::cos|cos(Matrix m)|mの各要素にcos関数の返り値を代入した行列を返します。|
|!Matrix::tan|tan(Matrix m)|mの各要素にtan関数の返り値を代入した行列を返します。|
|!Matrix::exp|exp(Matrix m)|mの各要素にexp関数の返り値を代入した行列を返します。|
|!Matrix::sqrt|sqrt(Matrix m)|mの各要素にsqrt関数の返り値を代入した行列を返します。|
|!Matrix::pow|pow(Matrix m, double ex)|mの各要素を引数としてex乗した値を代入した行列を返します。|
{{{
#include <psychlops.h>
using namespace Psychlops;
void psychlops_main()
{
double wave_length;
Canvas display(Canvas::fullscreen);
Range row, col;
Matrix m, y, x;
y = Matrix::mesh(-100<=row<=100, 201);
x = Matrix::mesh(201, -100<=col<=100);
m = pow(sin(x/wave_length * 2*PI) + cos(y/wave_length * 2*PI), 4.0);
Image img;
img.from(m);
while(!Input::get(Keyboard::esc)) {
img.draw();
display.flip();
}
}
}}}
!その他
|!Matrix::min|min(Matrix a, Matrix b)|aとbの各要素を比較し、小さいほうを要素とする行列を返します。両Matrixの大きさは同じでなければなりません。|
|!Matrix::max|max(Matrix a, Matrix b)|aとbの各要素を比較し、大きいほうを要素とする行列を返します。両Matrixの大きさは同じでなければなりません。|
{{{
A =
1 2 3
0 0 2
B =
5 3 0
1 1 1
C = max(A, B);
C =>
5 3 3
1 1 2
}}}
config.cssEdit ={};
config.cssEdit.settings = {
tags:"", //記事のタグ
user:"CSSEditBackup", //作成者名
backupname:"CSSEditBackup" //バックアップタイトル名
}
config.macros.cssEdit = {};
config.macros.cssEdit.handler = function(place,macroName,params){
if(readOnly){return;}
var s = '<form mime="text/plain" name="CSSEditForm">'
+ '<p><textarea name="i" rows="20" style="width:90%;"></textarea></p>'
+ '<p>'
+ '<input type="button" name="go" value="GO" onclick="config.macros.cssEdit.go();"> '
+ '<input type="button" name="ReadStyleSheet" value="Read StyleSheet" onclick="config.macros.cssEdit.readStyleSheet();"> '
+ '<input type="button" name="Backup" value="Backup" onclick="config.macros.cssEdit.Backup();"> '
+ '<input type="button" name="Restore" value="Restore" onclick="config.macros.cssEdit.restore();"> '
+ '<input type="reset" value="reset"> '
+ '</p>'
+ '</form>'
+ '<p style="font-size:small;">CSSEdit Ver 0.1.0 Copyright © 2006 <a href="<a class="linkification-ext" href="<a class="linkification-ext" href="<a class="linkification-ext" href="<a class="linkification-ext" href="<a class="linkification-ext" href="<a class="linkification-ext" href="<a class="linkification-ext" href="<a class="linkification-ext" href="<a class="linkification-ext" href="<a class="linkification-ext" href="<a class="linkification-ext" href="<a class="linkification-ext" href="http://www.potto.client.jp/" title="Linkification: http://www.potto.client.jp/">http://www.potto.client.jp/</a>" title="Linkification: <a class="linkification-ext" href="http://www.potto.client.jp/" title="Linkification: http://www.potto.client.jp/">http://www.potto.client.jp/</a>"><a class="linkification-ext" href="http://www.potto.client.jp/" title="Linkification: http://www.potto.client.jp/">http://www.potto.client.jp/</a></a>" title="Linkification: <a class="linkification-ext" href="http://www.potto.client.jp/" title="Linkification: http://www.potto.client.jp/">http://www.potto.client.jp/</a>"><a class="linkification-ext" href="http://www.potto.client.jp/" title="Linkification: http://www.potto.client.jp/">http://www.potto.client.jp/</a></a>" title="Linkification: <a class="linkification-ext" href="http://www.potto.client.jp/" title="Linkification: http://www.potto.client.jp/">http://www.potto.client.jp/</a>"><a class="linkification-ext" href="http://www.potto.client.jp/" title="Linkification: http://www.potto.client.jp/">http://www.potto.client.jp/</a></a>" title="Linkification: <a class="linkification-ext" href="http://www.potto.client.jp/" title="Linkification: http://www.potto.client.jp/">http://www.potto.client.jp/</a>"><a class="linkification-ext" href="http://www.potto.client.jp/" title="Linkification: http://www.potto.client.jp/">http://www.potto.client.jp/</a></a>" title="Linkification: <a class="linkification-ext" href="http://www.potto.client.jp/" title="Linkification: http://www.potto.client.jp/">http://www.potto.client.jp/</a>"><a class="linkification-ext" href="http://www.potto.client.jp/" title="Linkification: http://www.potto.client.jp/">http://www.potto.client.jp/</a></a>" title="Linkification: <a class="linkification-ext" href="http://www.potto.client.jp/" title="Linkification: http://www.potto.client.jp/">http://www.potto.client.jp/</a>"><a class="linkification-ext" href="http://www.potto.client.jp/" title="Linkification: http://www.potto.client.jp/">http://www.potto.client.jp/</a></a>" title="Linkification: <a class="linkification-ext" href="http://www.potto.client.jp/" title="Linkification: http://www.potto.client.jp/">http://www.potto.client.jp/</a>"><a class="linkification-ext" href="http://www.potto.client.jp/" title="Linkification: http://www.potto.client.jp/">http://www.potto.client.jp/</a></a>" title="Linkification: <a class="linkification-ext" href="http://www.potto.client.jp/" title="Linkification: http://www.potto.client.jp/">http://www.potto.client.jp/</a>"><a class="linkification-ext" href="http://www.potto.client.jp/" title="Linkification: http://www.potto.client.jp/">http://www.potto.client.jp/</a></a>" title="Linkification: <a class="linkification-ext" href="http://www.potto.client.jp/" title="Linkification: http://www.potto.client.jp/">http://www.potto.client.jp/</a>"><a class="linkification-ext" href="http://www.potto.client.jp/" title="Linkification: http://www.potto.client.jp/">http://www.potto.client.jp/</a></a>" title="Linkification: <a class="linkification-ext" href="http://www.potto.client.jp/" title="Linkification: http://www.potto.client.jp/">http://www.potto.client.jp/</a>"><a class="linkification-ext" href="http://www.potto.client.jp/" title="Linkification: http://www.potto.client.jp/">http://www.potto.client.jp/</a></a>" title="Linkification: <a class="linkification-ext" href="http://www.potto.client.jp/" title="Linkification: http://www.potto.client.jp/">http://www.potto.client.jp/</a>"><a class="linkification-ext" href="http://www.potto.client.jp/" title="Linkification: http://www.potto.client.jp/">http://www.potto.client.jp/</a></a>">potto</a></p>';
var e = createTiddlyElement(place,"div",null,null,"");
e.innerHTML = s;
}
config.macros.cssEdit.go=function(){
//setStylesheet(document.CSSEditForm.i.value,"CSSEdit");
var s =document.CSSEditForm.i.value;
var title = "CSSEdit_temp";
config.macros.cssEdit.save(title,s,"temp","");
var html = store.getRecursiveTiddlerText(title,null,10);
store.deleteTiddler(title);
setStylesheet(html,"CSSEdit");
};
config.macros.cssEdit.readStyleSheet=function(){
if (! window.confirm("Read StyleSheet OK ?"))
return;
document.CSSEditForm.i.value = store.getTiddlerText("StyleSheet");
};
config.macros.cssEdit.Backup=function(){
if (! window.confirm("Backup OK?"))
return;
var p = config.cssEdit.settings;
var s = document.CSSEditForm.i.value;
var title = p.backupname;
config.macros.cssEdit.save(title,s,p.user,p.tags)
saveChanges();
};
config.macros.cssEdit.restore=function(){
if (! window.confirm("Restore OK ?"))
return;
var p = config.cssEdit.settings;
document.CSSEditForm.i.value = store.getTiddlerText(p.backupname);
};
config.macros.cssEdit.save = function(title,s,user,tags){
var p = config.HTMLEdit.settings;
var now = new Date();
var tiddler = store.fetchTiddler(title);
var created;
if(tiddler){
created = tiddler.created;
store.deleteTiddler(title);
}else{
tiddler = new Tiddler();
created = now;
}
tiddler.set(title,s,user,now,tags,created);
store.addTiddler(tiddler);
};
|!Canvas::clear()|clear([[Psychlops::Color]] col,TargetSurface)|>|指定したCanvasを指定色で塗りつぶします|
|~|~|[[Psychlops::Color]] col |塗りつぶす色を指定|
|~|~|TargetSurface|塗りつぶす画面を指定|
|~|clear(TargetSurface)|指定したCanvasを塗りつぶします。色の指定には[[Canvas::setClearColor()]]命令を使用します|
|~|~|TargetSurface|塗りつぶす画面を指定|
[[2.1.2 Canvasの基本構造と操作]]
[[Psychlops::Rectangle]]型(source)で指定される範囲内の情報を[[Psychlops::Rectangle]]型(target)の範囲に複写または移動します。移動元と移動先の矩形の大きさは同じにする必要があります。
|!Canvas::copy()|copy([[Psychlops::Rectangle]] source, [[Psychlops::Rectangle]] target, bool copymode, [[Psychlops::Color]] col)|>|! [[Psychlops::Rectangle]]型の情報を[[Psychlops::Image]]型に複写または移動する|>|>|
|~|~|[[Psychlops::Rectangle]] source|移動(複写)元のRectangleを指定|>|
|~|~|[[Psychlops::Rectangle]] target|移動(複写)先のRectangleを指定|>|
|~|~|bool copymode| trueを指定する場合は移動処理(カット), falseを指定する場合は複写処理(コピー)|指定しない場合、false(複写)が指定される|
|~|~|[[Psychlops::Color]] col|移動処理時の移動元の塗りつぶし色を指定|指定しない場合、黒色が指定される|
|~|~|~|~|複写の場合、指定不要|
!!Canvas::copy()の書き方
rect1の内部にある描画内容ををrect2の内部に複写します。
{{{
<例>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Rectangle rect1(100,100);
Psychlops::Rectangle rect2(100,100);
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
rect1.centering();
while(!Input::get(Keyboard::spc)){
sampleA.rect(rect1,Color::yellow);
sampleA.copy(rect1,rect2);
sampleA.flip();
}
}
}}}
表と裏の2つの画面の入れ替え処理です。
描画内容はCanvas::flip() 処理で画面を入れ替えることで初めて画面上に表示されます。
この命令は画面の垂直同期信号(=ビデオカードが1枚の画面を書ききったことを示す信号; Vsync)に同期して、画面の切り替えを行います。
* 参考 [[Tips: Canvas::flip()とコマ落ち]]
|!Canvas::flip()|flip(int)|int入力値のリフレッシュ分描画内容を表示します|
|~|~|引数を入力しない場合(flip())、flip(1)が指定されます|
|~|flipAfter(int wait)|int wait回のリフレッシュ後まで待ってからflip()を実行して描画内容を画面に反映させます|
|~|flipAndWait(int wait)|もっとも近い垂直同期のタイミングでflip()を実行した後に、int wait回ののリフレッシュ分の表示を予約します。次にflip()命令がコールされても、int wait回ののリフレッシュ分が経過するまでは何も起こりません(flip()と全く同じ効果ですが、引数は省略不可)|
flip()系の命令は画面描画の時間制御をする上でもっとも重要な命令です。上の表だけではわかりにくいでしょうから、図と実例を用いて各命令の働きをもう少し詳しく見ていきましょう。
flipAfter(), flipAndWait()命令の動きを図解すると以下のようになります。以下の図ではflipAfter(3)とflip(3)(あるいはflipAndWait(3))の場合を例としてあげました。
[img[image/psychlops_flip.png]]
コード例は[[2.1.3節|2.1.3 Canvasのメンバ取得と変数の表示]]参照
|!Canvas::getXXXX()|getCenter()|画面の中心座標(x,y)を取得します|
|~|getHcenter()|横方向の中心座標(x)を取得します|
|~|getVcenter()|縦方向の中心座標(y)を取得します|
|~|getHeight()|画面の高さ(ピクセル)を取得します|
|~|getWidth()|画面の幅(ピクセル)を取得します|
|~|getColorDepth()|カラーモード(ビット)を取得します|
|~|getRefreshRate()|リフレッシュレート(Hz)を取得します|
[[2.1.3 Canvasのメンバ取得と変数の表示]]
[[Canvas::flip()]]命令で画面の切り替えをする時にコマ落ちする場合があります。
その時に落ちたフレーム数を取得します。
* 参考 [[Tips: Canvas::flip()とコマ落ち]]
|!Canvas::lastFailedFrames()|lastFailedFrames()|!1秒間で落ちたフレーム数を取得します|
!!Canvas::lastFailedFrames()の書き方
フレームが落ちたときにグレーで画面をクリアしています。
(フレームが落ちるのが一瞬のため[[Canvas::var()]]命令で落ちた値を確認することは困難なため)
何枚落ちているかは右上の赤い数字が落ちたフレーム数の和算となっているので確認してください。
Display::~の書き方については [[Tips: CanvasクラスとDisplayクラス]]を参照してください。
{{{
<例>
#include <psychlops.h>
using namespace Psychlops;
void ullmancylinder() {
const int DOTCNT = 15000;
int dotcnt = 100;
double bg_lum = 0.0;
double velocity = 1.0;
double SOA_pp;
int SIZE = 3;
int SOA = 1;
double CylinderRadius=200;
int a1=0;
Range rng;
Independent << dotcnt | "Number of Dots" | 1< rng< DOTCNT | 10 | 1000 ;
Independent << velocity | "Dot Velocity" | 0.0<=rng<=10.0 | 0.1 | 0.05;
Independent << SOA | "SOA Frames" | 1.0<=rng<=60.0 | 1 | 1;
Independent << bg_lum | "Luminance Inv." | 0.0<=rng<=1.0 | 1 | 1;
Independent << SIZE | "Dot Size" | 0< rng<=10 | 1 | 1;
Independent << CylinderRadius| "Cylinder Size" | 0< rng<= 400 | 1 | 50;
Canvas canvas(Canvas::fullscreen);
Psychlops::Rectangle rect(SIZE,SIZE);
Psychlops::Color bgcolor(0), dotcolor(1.0-bg_lum);
double phase=0.0;
double x[DOTCNT], dx[DOTCNT], y[DOTCNT];
for(int i=0; i<DOTCNT; i++) {
x[i] = Psychlops::random(CylinderRadius*2*PI);
y[i] = Psychlops::random(CylinderRadius*2)+200;
}
Display::watchFPS();
while(!Input::get(Keyboard::spc)) {
bgcolor.set(bg_lum);
dotcolor.set(1.0-bg_lum);
canvas.clear(bgcolor);
rect.resize(SIZE,SIZE);
phase += velocity;
SOA_pp =SOA+1;
for(int i=0; i<DOTCNT; i++) dx[i] = (int)(CylinderRadius*sin(x[i]+SOA_pp*phase/100.0))+canvas.getHcenter();//各ドット位置の計算
for(int i=0; i<dotcnt; i++) rect.centering(dx[i], y[i]).draw(dotcolor);
//落ちたフレーム数を返します
a1=Display::lastFailedFrames();
if(a1)Display::clear(Color(0.25,0.25,0.25));
Display::showFPS();
canvas.flip(SOA);
}
}
void psychlops_main()
{
Procedure p;
p.setDesign(Procedure::DEMO);
p.setProcedure(ullmancylinder);
p.run();
}
}}}
|!Canvas::line()|line(double x1, double y1, double x2, double y2, [[Psychlops::Color]] col)|>|!開始座標(X1,Y1)から終端座標(X2,Y2)まで指定色の線を描画します|
|~|~|double x1|描画する線の開始座標x1を指定|
|~|~|double y1|描画する線の開始座標y1を指定|
|~|~|double x2|描画する線の終端座標x2を指定|
|~|~|double y2|描画する線の終端座標y2を指定|
|~|~|[[Psychlops::Color]] col|描画する点の色を指定|
|~|line([[Psychlops::Point]] point1, [[Psychlops::Point]] point2,[[Psychlops::Color]] col)|>|!開始Point座標(x1,x1)から終端Point座標(X2,Y2)まで指定色の線を描画します|
|~|~|[[Psychlops::Point]] point1|開始座標(x1,y1)を指定|
|~|~|[[Psychlops::Point]] point2|終端座標(x2,y2)を指定|
|~|~|[[Psychlops::Color]] col|描画する点の色を指定|
画面上に線を描画するためには、「どこから」「どこに」「何色」の線を引くのかの指定が必要です。
「どこから」は開始座標(x1,y1)、「どこに」は終端座標(x2,y2)、「何色」は[[Psychlops::Color]]型で指定します。
座標はCanvasの左上端が座標の基点(x=0,y=0)となっています。
これらの座標はPoint型の変数を使用して指定することもできます。
[img[image/canvasline.png]]
[[2.2.2 画面上に線を描画する]]
画面上に任意の文字列を表示します。
|!Canvas::msg()|msg(char* string,double x,double y,[[Psychlops::Color]] col)|>|!char* stringを座標(x,y)に指定色で表示します|
|~|~|schar* string|画面表示をする文字列を指定|
|~|~|double x|画面表示をする座標(x)を指定|
|~|~|double y|画面表示をする座標(y)を指定|
|~|~|[[Psychlops::Color]] col|描画する点の色を指定<br>(設定しない場合、白が設定される)|
|~|msg(string str,double x,double y,[[Psychlops::Color]] col)|>|!string strを座標(x,y)に指定色で表示します|
|~|~|string str|画面表示をする文字列を指定|
|~|~|double x|画面表示をする座標(x)を指定|
|~|~|double y|画面表示をする座標(y)を指定|
|~|~|[[Psychlops::Color]] col|描画する点の色を指定<br>(設定しない場合、白が設定される)|
[[2.1.3 Canvasのメンバ取得と変数の表示]]
|!Canvas::oval()|oval([[Psychlops::Rectangle]] rect, [[Psychlops::Color]] col)|>|![[Psychlops::Rectangle]] rectで指定された直径の円を指定色で塗りつぶし描画します|
|~|~|[[Psychlops::Rectangle]] rect|描画する円の直径|
|~|~|[[Psychlops::Color]] col|描画する点の円を指定|
[img[image/canvasoval.png]]
|!Canvas::pix()|pix(double x,double y,[[Psychlops::Color]] col) |>|!座標(x,y)に指定色の点を描画します|
|~|~|double x|描画する点の座標軸xを指定|
|~|~|double y|描画する点の座標軸yを指定|
|~|~|[[Psychlops::Color]] col|描画する点の色を指定|
|~|pix([[Psychlops::Point]] point,[[Psychlops::Color]] col) |>|!Point座標(x,y)に指定色の点を描画します|
|~|~|[[Psychlops::Point]] point |座標(x,y)を指定|
|~|~|[[Psychlops::Color]] col|描画する点の色を指定|
|~|pix(int dotsCnt,double* xArray,double* yArray),[[Psychlops::Color]]* colArray )|>|!配列の座標(x[],y[])に指定色(配列を用いて複数色指定)の点を描画します|
|~|~|int dotsCnt|描画する点の数を指定(後ろの引数の配列の個数を超えてはいけない)|
|~|~|double* xArray| 描画する点の座標軸xが格納された配列名(ポインタ)を指定|
|~|~|double* yArray| 描画する点の座標軸yが格納された配列名(ポインタ)を指定|
|~|~|[[Psychlops::Color]]* colArray|描画する点の色が格納された配列名(ポインタ)を指定|
|~|pix(int dotsCnt,double* xArray,double* yArray,[[Psychlops::Color]] col)|>|!配列の座標(x[],y[])に指定色(一色のみ)の点を描画します|
|~|~|int dotsCnt|描画する点の数(配列数)を指定|
|~|~|double* xArray|描画する点の座標軸xが格納された配列名(ポインタ)を指定|
|~|~|double* yArray|描画する点の座標軸yが格納された配列名(ポインタ)を指定|
|~|~|[[Psychlops::Color]] col|描画する点の色を指定|
|~|pix(int dotsCnt,[[Psychlops::Point]]* pointArray,[[Psychlops::Color]]* colArray)|>|!配列Point座標(x[],y[])に指定色の点を描画します|
|~|~|int dotsCnt|描画する点の数(配列数)を指定|
|~|~|[[Psychlops::Point]]* pointArray|配列の座標(x[],y[])が格納された配列名(ポインタ)を指定|
|~|~|[[Psychlops::Color]]* colArray|描画する点の色が格納された配列名(ポインタ)を指定|
[img[image/canvaspix.png]]
[[2.2.1 画面上に点を描画する-Pointクラス1-]]
Canvas::pix(double, double, double)という命令はコンパイルできないのですか?
できません。最後の色の引数をColor(double)としてColor型にキャスト(変換)してください。
{{{
(正) display.pix(100,100,Color(0.5));
(誤) display.pix(100,100,0.5);
}}}
|!Canvas::progressbar()|void progressbar(X now, X max)|!現在のnowの値のmaxに対する比率をプログレスバーとして表示します。now, maxは数値型であればどの型でもかまいません|>|
|~|~|X now|現在の進行度を示す変数値(型は数値型)|
|~|~|X max|nowの最終的な到達値(型は数値型)|
|~|void progressbar(double ratio)|!ratioで示される比率をプログレスバーとして表示します。|>|
|~|~|double ratio|比率の値(0.0~1.0)|
[[5.2.2 画面に反映されない描画の進行度を表示する-プログレスバー]]
画面上に四角形を描画します。
|!Canvas::rect()|rect([[Psychlops::Rectangle]] rect, [[Psychlops::Color]] col)|>|!四角形を指定色で描画します|
|~|~|([[Psychlops::Rectangle]] rect|描画する四角形を指定|
|~|~|[[Psychlops::Color]] col|描画する四角形の色を指定|
[[2.2.3 画面上に四角形を描画する-Rectangleクラス1-]]
[[Canvas::clear()|2.1.2 Canvasの基本構造と操作]]命令使用時に塗りつぶす色の設定を行います。
setClearColor(Color)命令使用時には必ず[[Canvas::clear()|2.1.2 Canvasの基本構造と操作]]命令を使用してください。
|!Canvas::setClearColor()|setClearColor([[Psychlops::Color]] col)|>|!指定したCanvasを塗りつぶすための色を設定します|
|~|~|[[Psychlops::Color]] col |塗りつぶす色を指定|
!!Canvas::setClearColor()の書き方
今ディスプレイに表示されている画面(紙の表)を白く塗りつぶします。
{{{
<例>
#include <psychlops.h>
using namespace Psychlops;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
sampleA.setClearColor(Color::white);
sampleA.clear(Canvas::FRONT);
while(!Input::get(Keyboard::spc));
}
}}}
他の多くの描画ライブラリ同様、CRTの各画素における輝度レベルとPsychlopsで指定する輝度レベルは線形の関係を維持しません。これを簡易的に補正するために、R,G,Bの各色チャンネルについて変換表(LUTテーブル)を用いた補正を行う方法が広く用いられています。
Psychlopsでは、この変換表を指定することで、[[ガンマ補正|Tips: ガンマ補正]]された色を表示することが可能です。このための命令として、setGammaTable()命令があります。
通常は[[Color::setGammaTable()]]を用いますが、将来的にCanvasごとに別の補正値が必要なときのためにこの関数が予約されています。
|!Canvas::setGammaTable()|setGammaTable(std::vector<double> Cr, std::vector<double> Cg, std::vector<double> Cb)|>|!Canvasが確保している表示装置のDACテーブルを直接書き換え、ガンマ補正を行います。|
|~|~|std::vector<double> Cr|R(赤)チャンネルの変換表|
|~|~|std::vector<double> Cg|G(緑)チャンネルの変換表|
|~|~|std::vector<double> Cb|B(青)チャンネルの変換表|
*2 256個以下でも自動的に線形補完されますが、正確な補完は256個すべて指定することで実現されます。
*3 値は配列またはC++STLの動的配列(std::vector<double>)で指定し、8ビット環境なら256段階の理論値を配列の要素番号とし、それぞれの段階の補償値を配列要素に書き込みます。
コード例は[[ガンマ補正|Tips: ガンマ補正]]参照
他の多くの描画ライブラリ同様、CRTの各画素における輝度レベルとPsychlopsで指定する輝度レベルは線形の関係を維持しません。これを簡易的に補正するために、R,G,Bの各色チャンネルについてGamma関数を用いた補正を行う方法が広く用いられています。
Psychlopsでは、このGamma関数の係数を指定することで、[[ガンマ補正|Tips: ガンマ補正]]された色を表示することが可能です。このための命令として、setGammaValue()命令があります。
通常は[[Color::setGammaValue()]]を用いますが、将来的にCanvasごとに別の補正値が必要なときのためにこの関数が予約されています。
|!Canvas::setGammaValue()|setGammaValue(double Cr, double Cg, double Cb)|>|!Canvasが確保している表示装置のDACテーブルを直接書き換え、ガンマ補正を行います。|
|~|~|double Cr|表示装置のR(赤)チャンネルのガンマ係数|
|~|~|double Cg|表示装置のG(緑)チャンネルのガンマ係数|
|~|~|double Cb|表示装置のB(青)チャンネルのガンマ係数|
* 指定する値は表示装置のガンマ値そのものです。Psychlops側で補償指数を自動決定します。
コード例は[[ガンマ補正|Tips: ガンマ補正]]参照
|!Canvas::showFPS()|void showFPS(void)|!FPSチェッカのレポートを画面左上に表示します|
[[5.2.1 描画の時間精度を確認する-FPSチェッカー]]
[[Psychlops::Rectangle]]型で指定される範囲内の情報を[[Psychlops::Image]]型に複写します。
この命令と''Image::save()''命令を組み合わせることによってImage上以外で描画した画像が保存できます。
|!Canvas::to()|to([[Psychlops::Image]] img, [[Psychlops::Rectangle]] rect)|>|![[Psychlops::Rectangle]]型の情報を[[Psychlops::Image]]型に複写する|
|~|~|[[Psychlops::Image]] img |複写先のImage|
|~|~|[[Psychlops::Rectangle]] rect |複写元のRectangle|
コード例は[[3.3.1節|3.3.1 画像ファイルを保存する]]参照
取得した変数の値を画面に表示します。
|!Canvas::var()|var(string str, double x, double y,[[Psychlops::Color]] col, bool fillForward)|>|>|>|!string strで指定された変数の値を取得して座標(x,y)に指定色で表示します|
|~|~|>|>|string str|画面表示をする変数を指定|
|~|~|>|>|double x|画面表示をする座標(x)を指定|
|~|~|>|>|double y|画面表示をする座標(y)を指定|
|~|~|>|>|[[Psychlops::Color]] col|描画する点の色を指定(設定しない場合、白が設定される)|
|~|~|~|bool fillForward|表示位置の指定|true |指定座標(x,y)から左部分に表示する|
|~|~|~|~|~|false |指定座標(x,y)から右部分に表示する|
|~|~|~|~|>|>|(指定しない場合、falseが設定される)|
[[2.1.3 Canvasのメンバ取得と変数の表示]]
|!Canvas::watchFPS()|void watchFPS(void)|!FPSチェッカをonにします|
[[5.2.1 描画の時間精度を確認する-FPSチェッカー]]
描画モード
|Canvas::fullscreen()|画面いっぱいに描画領域を確保する|
|Canvas::window()|指定サイズの描画領域を確保する|
|!Canvasの宣言|
|~|Canvas(int width,int height,int colordepth,double refreshrate)|>|!全画面表示をします|
|~|~|int width|水平方向の解像度(画面の横幅)を指定|
|~|~|int height|垂直方向の解像度(画面の縦幅)を指定|
|~|~|int colordepth|カラーモード(ビット)を指定|
|~|~|double refreshrate|リフレッシュレート( Hz )を指定|
|~|Canvas(Canvasmode mode, const Display disp)|>|!現在の画面モードで画面を確保する場合に使います|
|~|~|Canvasmode mode|[[表示モード|CanvasMode]](フルスクリーンorウィンドウ)を選択|
|~|~|const [[Display|Displayの指定値]] disp|[[表示するディスプレイ|Displayの指定値]]を選択(省略可能)|
|~|~|>|マルチディスプレイ環境では、最後の引数でどのディスプレイに表示するか指定できます。省略時はプライマリディスプレイになります。詳細は [[マルチディスプレイの選択]]を参照ください。|
|~|Canvas(int width, int height, int colordepth, double refreshrate, const Display disp)|>|!画面モードを指定してフルスクリーン画面を確保します|
|~|~|int width|水平方向の解像度(画面の横幅)を指定|
|~|~|int height|垂直方向の解像度(画面の縦幅)を指定|
|~|~|int colordepth|カラーモード(ビット)を指定|
|~|~|double refreshrate|リフレッシュレート( Hz )を指定|
|~|~|const [[Display|Displayの指定値]] disp|[[表示するディスプレイ|Displayの指定値]]を選択(省略可能)|
|~|~|>|指定した画面モードが選べなかった場合は、例外が送出されプログラムは強制終了します。|
|~|Canvas(int width, int height, CanvasMode mode, const Display disp)|>|!指定の幅と高さでウィンドウを確保します。|
|~|~|int width|水平方向の解像度(画面の横幅)を指定|
|~|~|int height|垂直方向の解像度(画面の縦幅)を指定|
|~|~|Canvasmode mode|[[表示モード|CanvasMode]](フルスクリーンorウィンドウ)を選択|
|~|~|>|指定した画面モードが選べなかった場合は、例外が送出されプログラムは強制終了します。|
|~|Canvas(Rectangle rect, CanvasMode mode , const Display disp)|>|!バーチャルスクリーン上の指定の矩形でウィンドウを確保します。|
|~|~|Rectangle rect|ディスプレイの大きさ、ディスプレイの位置を指定|
|~|~|Canvasmode mode|[[表示モード|CanvasMode]](フルスクリーンorウィンドウ)を選択|
|~|~|const [[Display|Displayの指定値]] disp|[[表示するディスプレイ|Displayの指定値]]を選択(省略可能)|
|~|~|>|指定した画面モードが選べなかった場合は、例外が送出されプログラムは強制終了します。|
* ここではコンストラクタ(宣言)の書式のみ記述していますが、同じ引数セットのset関数がすべて用意されています。
* CRT は機器によってリフレッシュレートが小数点以下の桁でわずかに異なります(60Hzと表示されていても、厳密には60.2Hzなど小数点以下の桁があります)。初期化時には小数点以下の桁は無視して確保しますが、正確なタイミングでflipするには、ディスプレイ装置の設定等で小数点以下の桁をあらかじめ調べて、正確な値を指定することをお勧めします。
* 1.3以降でではウィンドウモードが実装されました。
[[2.1.1 Canvasの宣言]]
* [[描画内容を画面に反映させたい|Canvas::flip()]]
* [[描画内容を画面に反映させて、一定時間表示したい|Canvas::flip()]]
* [[Canvas型の変数のメンバを取得したい|Canvas::getXXXX()]]
* [[プログラム内の変数の値を表示したい|Canvas::var()]]
* [[簡単なメッセージを表示したい|Canvas::msg()]]
* [[画面領域全てを塗りつぶしたい|Canvas::clear()]]
* [[点を打ちたい|Canvas::pix()]]
* [[四角形を描画したい|Canvas::rect()]]
* [[円・楕円を描画したい|Canvas::oval()]]
* [[描画内容を画面上で複写したい|Canvas::copy()]]
* [[描画内容をオフスクリーンへ複写したい|Canvas::to()]]
* [[FPSチェッカを使用したい|5.2.1 描画の時間精度を確認する-FPSチェッカー]]
* [[Canvas::pix(double, double, double)という命令はコンパイルできないのですか?]]
* [[止まった絵を描画するときCanvas.flip()を単独でwhileループの中に書くと、画面がちらつきます。]]
タイマーのカウントをmsec単位に変換します。
|!Clock::at_msec()| double Clock::at_msec(void)| カウントをmsec単位に変換(double)して返す|
詳細とコード例は[[5.1節|5.1 時間を計測する]]参照
Timerに現在のCPUタイマーの値を入力します。
|!Clock::update()| Clock::update(void)| 現在のCPUタイマーの値を入力する|
詳細とコード例は[[5.1節|5.1 時間を計測する]]参照
描画対象に色をつけたい場合、Color()の宣言を行い色の設定をします。
宣言のみの場合、[[Color::set()|2.2.5 描画する図形の色を指定する-Colorクラス-]]で色の詳細情報を設定しますが
宣言文のみでも色の情報の設定が可能です。
|!Color()の宣言|Color()|[[Psychlops::Color]]型の宣言を行います|
|~|Color(int r, int g, int b, int a)|[[Psychlops::Color]]型の宣言を行いr,g,bと透明度aで構成されるカラー情報を設定します|
|~|~|int r:赤の輝度を指定。範囲は0~1|
|~|~|int g:緑の輝度を指定。範囲は0~1|
|~|~|int b:青の輝度を指定。範囲は0~1|
|~|~|int a:透明度を指定。範囲は0~1。0に近いほど透明度が高い|
|~|Color(double r, double g, double b, double a)|[[Psychlops::Color]]型の宣言を行いr,g,bと透明度aで構成されるカラー情報を設定します|
|~|~|double r:赤の輝度を指定。範囲は0.0~1.0|
|~|~|double g:緑の輝度を指定。範囲は0.0~1.0|
|~|~|double b:青の輝度を指定。範囲は0.0~1.0|
|~|~|double a:透明度を指定。範囲は0.0~1.0。0.0に近いほど透明度が高い|
|~|Color(int r, int g, int b)|[[Psychlops::Color]]型の宣言を行いr,g,bで構成されるカラー情報を設定します|
|~|~|int r:赤の輝度を指定。範囲は0.0~1.0|
|~|~|int g:緑の輝度を指定。範囲は0.0~1.0|
|~|~|int b:青の輝度を指定。範囲は0.0~1.0|
|~|~|透明度は1.0が固定で指定されている|
|~|Color(long r, long g, long b)|[[Psychlops::Color]]型の宣言を行いr,g,bで構成されるカラー情報を設定します|
|~|~|long r:赤の輝度を指定。範囲は0.0~1.0|
|~|~|long g:緑の輝度を指定。範囲は0.0~1.0|
|~|~|long b:青の輝度を指定。範囲は0.0~1.0|
|~|~|透明度は1.0が固定で指定されている|
|~|Color(float r, float g, float b)|[[Psychlops::Color]]型の宣言を行いr,g,bで構成されるカラー情報を設定します|
|~|~|float r:赤の輝度を指定。範囲は0.0~1.0|
|~|~|float g:緑の輝度を指定。範囲は0.0~1.0|
|~|~|float b:青の輝度を指定。範囲は0.0~1.0|
|~|~|透明度は1.0が固定で指定されている|
|~|Color(double r, double g, double b)|[[Psychlops::Color]]型の宣言を行いr,g,bで構成されるカラー情報を設定します|
|~|~|double r:赤の輝度を指定。範囲は0.0~1.0|
|~|~|double g:緑の輝度を指定。範囲は0.0~1.0|
|~|~|double b:青の輝度を指定。範囲は0.0~1.0|
|~|~|透明度は1.0が固定で指定されている|
|~|Color(int gray)|[[Psychlops::Color]]型の宣言を行いグレースケールの輝度を設定します|
|~|~|int gray: 輝度を指定。範囲は0.0~1.0|
|~|~|透明度は1.0が固定で指定されている|
|~|Color(long gray)|[[Psychlops::Color]]型の宣言を行いグレースケールの輝度を設定します|
|~|~|long gray: 輝度を指定。範囲は0.0~1.0|
|~|~|透明度は1.0が固定で指定されている|
|~|Color(float gray)|[[Psychlops::Color]]型の宣言を行いグレースケールの輝度を設定します|
|~|~|透明度は1.0が固定で指定されている|
|~|~|float gray: 輝度を指定。範囲は0.0~1.0|
|~|~|透明度は1.0が固定で指定されている|
|~|Color(double gray)|[[Psychlops::Color]]型の宣言を行いグレースケールの輝度を設定します|
|~|~|double gray: 輝度を指定。範囲は0.0~1.0|
|~|~|透明度は1.0が固定で指定されている|
あらかじめ用意された基本的な色指定を呼び出すことが可能です。
呼び出せる色は以下の通りです。
|!Color::white|白(1.0, 1.0, 1.0)|
|!Color::black|黒(0.0, 0.0, 0.0)|
|!Color::gray|灰色(0.5, 0.5, 0.5)|
|!Color::red|赤(1.0, 0.0, 0.0)|
|!Color::green|緑( 0.0, 1.0, 0.0)|
|!Color::blue|青(0.0, 0.0, 1.0)|
|!Color::cyan|シアン(0.0, 1.0, 1.0)|
|!Color::magenta|マゼンタ(1.0, 1.0, 0.0)|
|!Color::yellow|黄(1.0, 1.0, 0.0)|
Color型に格納されている各値を得ます。
|!Color::get()|get(double r, double g, double b, double a)|r,g,bと透明度aで構成されるカラー情報を取得します。|
|~|~|double r:赤の輝度(0.0~1.0)|
|~|~|double g:緑の輝度(0.0~1.0)|
|~|~|double b:青の輝度(0.0~1.0)|
|~|~|double a:透明度(0.0~1.0)。0に近いほど透明度が高い|
|!Color::getX()|getR()|赤チャネルの値を返します|
|~|getG()|緑チャネルの値を返します|
|~|getB()|青チャネルの値を返します|
|~|getA()|透明チャネルの値を返します|
{{{
double r, g, b, a;
Color col
col.set(0.5);
col.get(r, g, b, a);
// r => 0.5, g => 0.5, b => 0.5, a => 1
col.set(1.0, 0.5, 0.0, 0.5);
r = col.getR();
// r => 1.0
}}}
|!Color::set()|set(double r, double g, double b, double a)|r,g,bと透明度aで構成されるカラー情報を設定します。|
|~|~|double r:赤の輝度を指定。範囲は0.0~1.0|
|~|~|double g:緑の輝度を指定。範囲は0.0~1.0|
|~|~|double b:青の輝度を指定。範囲は0.0~1.0|
|~|~|double a:透明度を指定。範囲は0.0~1.0 (0.0に近いほど透明度が高い)|
|~|set(double r, double g, double b)|r,g,bで構成されるカラー情報を設定します。|
|~|~|double r:赤の輝度を指定。範囲は0.0~1.0|
|~|~|double g:緑の輝度を指定。範囲は0.0~1.0|
|~|~|double b:青の輝度を指定。範囲は0.0~1.0|
|~|set(double gray)|グレースケールの輝度を設定します。|
|~|~|double gray: 輝度を指定。範囲は0.0~1.0|
[[2.2.5 描画する図形の色を指定する-Colorクラス-]]
他の多くの描画ライブラリ同様、CRTの各画素における輝度レベルとPsychlopsで指定する輝度レベルは線形の関係を維持しません。
これを簡易的に補正するために、R,G,Bの各色チャンネルについて変換表(LUTテーブル)を用いた補正を行う方法が広く用いられています。
Psychlopsでは、この変換表を指定することで、[[ガンマ補正|Tips: ガンマ補正]]された色を表示することが可能です。
このための命令として、setGammaTable()命令があります。
|!Canvas::setGammaTable()|setGammaTable(double Cr[], double Cg[], double Cb[], int table_size)|この命令以降実行されるPsychlops::Colorに対して変換表によるを自動的に行います^^*1^^|
|~|~|Cr: R(赤)チャンネルの変換表|
|~|~|Cg: G(緑)チャンネルの変換表|
|~|~|Cb: B(青)チャンネルの変換表|
|~|~|table_size: 表の項目数。8ビット環境下ではできれば256個の項目を用意してください。^^*2^^|
|~|setGammaTable(std::vector<double> Cr, std::vector<double> Cg, std::vector<double> Cb)|この命令以降実行されるPsychlops::Colorに対してGamma補正を自動的に行います^^*1^^|
|~|~|Cr: R(赤)チャンネルの変換表|
|~|~|Cg: G(緑)チャンネルの変換表|
|~|~|Cb: B(青)チャンネルの変換表|
*1 Psychlopsはまずハードウェアによるガンマ補正([[Canvas::setGammaTable()]])を試みます。失敗すると、ソフトウェアによるエミュレーションを試みます。
*2 256個以下でも自動的に線形補完されますが、正確な補完は256個すべて指定することで実現されます。
*3 値は配列またはC++STLの動的配列(std::vector<double>)で指定し、8ビット環境なら256段階の理論値を配列の要素番号とし、それぞれの段階の補償値を配列要素に書き込みます。
コード例は[[ガンマ補正|Tips: ガンマ補正]]参照
他の多くの描画ライブラリ同様、CRTの各画素における輝度レベルとPsychlopsで指定する輝度レベルは線形の関係を維持しません。これを簡易的に補正するために、R,G,Bの各色チャンネルについてGamma関数を用いた補正を行う方法が広く用いられています。
Psychlopsでは、このGamma関数の係数を指定することで、[[ガンマ補正|Tips: ガンマ補正]]された色を表示することが可能です。
このための命令として、setGammaValue()命令があります。
|!Canvas::setGammaValue()|setGammaValue(double Cr, double Cg, double Cb)|この命令以降実行されるPsychlops::Colorに対してGamma補正を自動的に行います^^*1^^|
|~|~|Cr: R(赤)チャンネルのガンマ係数|
|~|~|Cg: G(緑)チャンネルのガンマ係数|
|~|~|Cb: B(青)チャンネルのガンマ係数|
*1 Psychlopsはまずハードウェアによるガンマ補正([[Canvas::setGammaValue()]])を試みます。失敗すると、ソフトウェアによるエミュレーションを試みます。
*2 指定する値は表示装置のガンマ値そのものです。Psychlops側で補償指数を自動決定します。
コード例は[[ガンマ補正|Tips: ガンマ補正]]参照
実験時に色指定を厳密に行わせるよう強制させることができます。具体的には、ガンマ補正や色指定で値が0以上1以下にならないケースで警告する、ソフトウェアエミュレーション(後述)を禁止して警告を出す、という規制が行われます。以下のコードをpsychlops_main()の冒頭に記述してください。
{{{
Color::strict_match = true;
}}}
なお、警告はC++の例外機能を使って送出されます。警告を受け入れる構文を書かない限り強制終了します。警告を受け入れるtry..catch構文については、C++の参考書をお読みください。
* [[描画色を設定したい|Color::set()]]
* [[白、黒などのデフォルトの描画色を使用したい|Color::XXXX]]
クラス定数として以下のものが用意されています。
|Display::secondary|その時点でのプライマリディスプレイを取得します|
|Display::primary|その時点でのセカンダリディスプレイを取得します|
より複雑な環境では以下の命令を使ってリストを取得してください。
|!Display::list()|ディスプレイのリストを動的配列(std::vector<Display>)で取得します。|
{{{
class GaborImage {
private:
public:
virtual void draw(Image &area, double ori, double freq, double phase, double Lmean, double *contrast) {
int center_x = area.getCenter().getX(), center_y = area.getCenter().getY();
int width = area.getWidth()/2, height = area.getHeight()/2;
Color tmpcol;
double sigma = (width)/3;
double SINORI, COSORI, _x, gr, ga, ph, R, G, B;
COSORI=cos(-ori*PI/180);
SINORI=sin(-ori*PI/180);
ph=phase*PI/180;
for(int y=-height; y<height; y++) {
for(int x=-width; x<width; x++) {
_x= COSORI * x - SINORI * y;
ga=exp(-0.5*(pow(x/(sigma),2))-0.5*(pow(y/(sigma), 2)));
gr=(1+cos(2*PI*freq*_x+ph))*0.5;
R=Lmean+Lmean*contrast[0]*(gr*ga);
G=Lmean+Lmean*contrast[1]*(gr*ga);
B=Lmean+Lmean*contrast[2]*(gr*ga);
tmpcol.set(R, G, B);
area.pix(x+width,y+height,tmpcol);
}
}
}
virtual void draw(Image &area, double ori, double freq, double phase, double Lmean, double contrast) {
int center_x = area.getCenter().getX(), center_y = area.getCenter().getY();
int width = area.getWidth()/2, height = area.getHeight()/2;
Color tmpcol;
double sigma = (width)/3;
double SINORI, COSORI, _y, gr, ga, ph;
COSORI=cos(ori*PI/180);
SINORI=sin(ori*PI/180);
ph=phase*PI/180;
for(int y=-height; y<height; y++) {
for(int x=-width; x<width; x++) {
_y= SINORI * x + COSORI * y;
ga=exp(-0.5*(pow(x/(sigma),2))-0.5*(pow(y/(sigma), 2)));
gr=(1+cos(2*PI*freq*_y+ph))*0.5;
tmpcol.set(Lmean+Lmean*contrast*(gr*ga));
area.pix(x+width,y+height,tmpcol);
}
}
}
};
}}}
{{{
#include <psychlops.h>
#include <stdio.h>
using namespace Psychlops;
class IndependentVariables
{
int MaxVarNum, MaxStepNum;
double *VariableData;
int *VariableStepNumber;
int conditionNumber;
int repeatNumber;
Psychlops::Matrix CondMtx;
public:
int trialNumber;
IndependentVariables(int n, int m, int repeat): MaxVarNum(n), MaxStepNum(m){
VariableData = (double *)malloc(sizeof(double)*(n+1)*m);
VariableStepNumber = (int *)malloc(sizeof(double)*m);
repeatNumber=repeat;
trialNumber=-1;
}
virtual ~IndependentVariables(void){
free(VariableData);
free(VariableStepNumber);
};
virtual int set(int vnum, int arraynum, double *array){
if(vnum<MaxVarNum+1 && arraynum<MaxStepNum+1){
VariableStepNumber[vnum]=arraynum;
for(int i=0; i<arraynum; i++){*(VariableData+(vnum+1)*MaxStepNum+i)=*(array+i);}
return arraynum;
}
else return -1;
};
virtual int randomize(char* dataname=NULL){
trialNumber=repeatNumber;
for(int i=0; i<MaxVarNum; i++)trialNumber*=VariableStepNumber[i];
if(trialNumber<1){return -1;}
CondMtx.set(trialNumber, MaxVarNum);
int i,j,a;
double temp=1, temp2;
//Make Output Matrix
for(i=0; i<MaxVarNum; i++){
for(j=0; j<i; j++){temp*=(double)VariableStepNumber[j];}
for(j=0; j<trialNumber; j++){CondMtx(j+1,i+1)=(int)ceil(j/temp)% VariableStepNumber[i];}
temp=1;
}
//Randomize Output Matrix
for(i=1; i<trialNumber;i++){
a = Psychlops::random(i-1)+1;
for(j=1;j<MaxVarNum+1;j++){
temp2=CondMtx(a,j);
CondMtx(a,j)=CondMtx(i,j);
CondMtx(i,j)=temp2;
}
}
//swtich of the condition matrix save
if(dataname){
std::ofstream output;
output.open(dataname);
output << CondMtx << std::endl;
output.close();
}
}
virtual double get(int vnum, int trial_now){
if(vnum<MaxVarNum && (int)trial_now<trialNumber)
return *(VariableData+(vnum+1)*MaxStepNum+getCondMtx(vnum, trial_now));
else return -1;
};
virtual int getCondMtx(int vnum, unsigned int trial_now){
if(vnum<MaxVarNum && (int)trial_now<trialNumber)
return CondMtx( trial_now+1, vnum+1);
else return -1;
};
virtual double getIndependent(int vnum, int stepnum){
if(vnum<MaxVarNum && stepnum<VariableStepNumber[vnum])
return *(VariableData+(vnum)*MaxStepNum+stepnum);
else return -1;
};
virtual int getMStep(int vnum){
if(vnum<MaxVarNum)return VariableStepNumber[vnum];
else return -1;
};
};
class GaborImage {
private:
public:
virtual void draw(Image &area, double ori, double freq, double phase, double Lmean, double *contrast) {
int center_x = area.getCenter().getX(), center_y = area.getCenter().getY();
int width = area.getWidth()/2, height = area.getHeight()/2;
Color tmpcol;
double sigma = (width)/3;
double SINORI, COSORI, _x, gr, ga, ph, R, G, B;
COSORI=cos(-ori*PI/180);
SINORI=sin(-ori*PI/180);
ph=phase*PI/180;
for(int y=-height; y<height; y++) {
for(int x=-width; x<width; x++) {
_x= COSORI * x - SINORI * y;
ga=exp(-0.5*(pow(x/(sigma),2))-0.5*(pow(y/(sigma), 2)));
gr=(1+cos(2*PI*freq*_x+ph))*0.5;
R=Lmean+Lmean*contrast[0]*(gr*ga);
G=Lmean+Lmean*contrast[1]*(gr*ga);
B=Lmean+Lmean*contrast[2]*(gr*ga);
tmpcol.set(R, G, B);
area.pix(x+width,y+height,tmpcol);
}
}
}
virtual void draw(Image &area, double ori, double freq, double phase, double Lmean, double contrast) {
int center_x = area.getCenter().getX(), center_y = area.getCenter().getY();
int width = area.getWidth()/2, height = area.getHeight()/2;
Color tmpcol;
double sigma = (width)/3;
double SINORI, COSORI, _y, gr, ga, ph;
COSORI=cos(ori*PI/180);
SINORI=sin(ori*PI/180);
ph=phase*PI/180;
for(int y=-height; y<height; y++) {
for(int x=-width; x<width; x++) {
_y= SINORI * x + COSORI * y;
ga=exp(-0.5*(pow(x/(sigma),2))-0.5*(pow(y/(sigma), 2)));
gr=(1+cos(2*PI*freq*_y+ph))*0.5;
tmpcol.set(Lmean+Lmean*contrast*(gr*ga));
area.pix(x+width,y+height,tmpcol);
}
}
}
};
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
IndependentVariables invar(2,5,10);
double gaborOrientation[5]={0,1,2,3,4}; //Gaborの方位
double duration[3]={100,250,500}; //刺激の提示時間
invar.set(0, 5, gaborOrientation);
invar.set(1, 3, duration);
invar.randomize("ConditionMatrix.dat");
GaborImage gaborIMG;
Psychlops::Image gIMG[10];
double gIMGsize=100, ori;
for(int i=0; i<10; i++){
if(i<5){ ori=90.0-pow(2.0, i);}
else{ ori=90.0+pow(2.0,i-4);}
gIMG[i].set(gIMGsize, gIMGsize);
gaborIMG.draw(gIMG[i], ori, 1.0/30.0, 0.0, 0.5, 0.2);
gIMG[i].centering();
}
const int trialNum=2*5*10;
int answer[trialNum], orientationCondition[trialNum], durationCondition[trialNum], ans;
int ori_now, dura_now;
int direction;
char trial_header[64];
for(int trial=0; trial<trialNum; trial++){
//各試行に対する変数の取得
direction=Psychlops::random(2); //0:right 1:left
ori_now=invar.get(0,trial)+5*direction;
dura_now=sampleA.getRefreshRate()*(invar.get(1,trial)/1000);
//被験者のキー入力待ち
sampleA.clear(0.5);
for(int i=0; i<64; i++) trial_header[i] = 0;// 文字列の初期化
sprintf(trial_header, "%s%d%s%d","Trial: ", trial, " /", trialNum );//試行番号の埋め込み
sampleA.message(trial_header, sampleA.getHcenter(), sampleA.getVcenter()-100, Color::green);//キー待ちメッセージの描画
sampleA.flip();
while(!Input::get(Keyboard::spc));
//刺激描画
sampleA.clear(0.5);
gIMG[ori_now].draw();
sampleA.flip(dura_now);
sampleA.clear(0.5);
sampleA.flip();
//反応の取得と格納
while(1){
if(Input::get(Keyboard::j)){ans=0;break;} //"j"なら右(0)
if(Input::get(Keyboard::f)){ans=1;break;}//"f"なら左(1)
if(Input::get(Keyboard::esc)){exit(0);}
}
if(ans==direction)answer[trial]=1;
else answer[trial]=0;
orientationCondition[trial]=invar.get(0, trial);
durationCondition[trial]=invar.get(1, trial);
}
sampleA.clear(0.5);
sampleA.message("Press space key to exit.", sampleA.getHcenter(), sampleA.getVcenter()-100, Color::green);
Data::savearray("ExptData.dat", "Duration /t Orientation /t Answer", trialNum, durationCondition, orientationCondition, answer );
}
}}}
To get started with this blank TiddlyWiki, you'll need to modify the following tiddlers:
* SiteTitle & SiteSubtitle: The title and subtitle of the site, as shown above (after saving, they will also appear in the browser title bar)
* MainMenu: The menu (usually on the left)
* DefaultTiddlers: Contains the names of the tiddlers that you want to appear when the TiddlyWiki is opened
You'll also need to enter your username for signing your edits: <<option txtUserName>>
[[Psychlops::Image]]型は主に画像を取り扱います。
[[Psychlops::Image]]型の命令を使用する前に必ず宣言が必要です。
宣言のみの場合、[[Image::set()]]でImageの詳細情報を設定しますが
宣言文のみでも情報の設定が可能です。
|!Imageの宣言|Image()|オフスクリーン領域を宣言します|
|~|Image([[Psychlops::Rectangle]] rec,PixelComponentsCnt pixcompcntval)|指定された[[Psychlops::Rectangle]] 型と同じ大きさのオフスクリーン領域を宣言します|
|~|~|[[Psychlops::Rectangle]] rect :[[Psychlops::Rectangle]] 型を指定|
|~|~|PixelComponentsCnt pixcompcntval :[[カラーモード]]を指定。省略した場合はRGBが指定される|
|~|Image(long x, long y, PixelComponentsCnt pixcompcntval)|指定されたx×yと同じ大きさのオフスクリーン領域を宣言します|
|~|~|long x :オフスクリーン領域の横幅を指定|
|~|~|long y :オフスクリーン領域の縦幅を指定|
|~|~|PixelComponentsCnt pixcompcntval :[[カラーモード]]を指定。省略した場合はRGBが指定される|
コード例は[[3.1.1節|3.1.1 オフスクリーン領域の宣言]]参照
!Image::centering()
[[Psychlops::Image]]型の領域を移動します。
座標を指定する場合、その座標に領域の中心が設定されます。
|!Image::centering()|centering()|[[Psychlops::Image]]型の領域を画面中心に移動します|
|~|centering(double x, double y)|[[Psychlops::Image]]型の領域の中心を指定した座標(x,y)に移動します|
|~|~|double x:移動する領域のx座標を指定|
|~|~|double y:移動する領域のy座標を指定|
[[3.1.3 オフスクリーン上の任意の位置に描画する]]
オフスクリーン領域をクリアします。
|!Image::clear()|clear([[Psychlops::Color]] col)|指定したオフスクリーン領域を指定色でクリアします|
|~|~|[[Psychlops::Color]] col:クリアする色を指定|
!!Image::clear()の書き方
Imageをマゼンダでクリアします。
{{{
<例>
#include <psychlops.h>
using namespace Psychlops;
double width=100,height=100;
Psychlops::Rectangle rect1(width,height);
Psychlops::Image Noise1(rect1);
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
Noise1.clear(Color::magenta);
Noise1.draw();
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
!Image::draw()
実際にオフスクリーン領域に描画する際の命令です。
この命令を記述しないといくら[[Image::pix()]]命令や[[Image::rect()]]命令を記述しても描画されませんので注意してください。
|!Image::draw()|draw()|指定したオフスクリーンを描画します|
|~|draw(double left, double top)|指定したオフスクリーン領域を(left,top)を中心とした座標に設定します|
|~|~|double left: 描画するオフスクリーン領域の水平座標値|
|~|~|double top: 描画するオフスクリーン領域の垂直座標値|
コード例は[[3.1.2節|3.1.2 オフスクリーン上に点・四角形を描画する]]参照
|!Image::from|from( luminance )|Matrixを輝度としてImageに読み込み、グレースケール画像を作成します。|
|~|~|Matrix luminance: |
|~|from( r, g, b )|Matrixを赤、緑、青チャネルとして読み込み、RGB画像を作成します。|
|~|~|Matrix r: |
|~|~|Matrix g: |
|~|~|Matrix b: |
|~|from( r, g, b, a) |Matrixを赤、緑、青、透明度チャネルとして読み込み、RGBA画像を作成します。|
|~|~|Matrix r: |
|~|~|Matrix g: |
|~|~|Matrix b: |
|~|~|Matrix a: |
Imageの中身がすでに確保されていた場合は、破棄されます。
[[5.3.5 行列の中身をオフスクリーンに描画する]]
Imageの様々な情報を、Image::getXXXX()命令を使って取得します(XXXXにはそれぞれの情報に対応した名前が入ります)。
|!Image::getXXXX()|getCenter()|オフスクリーン領域の中心座標(x,y)を取得します|
|~|getHcenter()|オフスクリーン領域の横方向の中心座標(x)を取得します|
|~|getVcenter()|オフスクリーン領域の縦方向の中心座標(y)を取得します|
|~|getHeight()|オフスクリーン領域の高さ(ピクセル)を取得します|
|~|getWidth()|オフスクリーン領域の幅(ピクセル)を取得します|
|~|getPix(int x, int y)|オフスクリーン領域上の座標(x,y)の色を取得します|
|~|~|int x :オフスクリーン領域上の座標xを指定|
|~|~|int y :オフスクリーン領域上の座標yを指定|
|~|~|注:オフスクリーン領域の左上を座標(0,0)とする。オフスクリーン領域外の座標は指定しないこと|
!!getXXXX()の書き方
[[Psychlops::Image]]型の情報を取得し画面表示を行います。
画面表示は[[Canvas::var()]]命令と[[Canvas::msg()]]命令を使用します。
{{{
<例>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Image Noise1(300,300);
Psychlops::Point point1;
Psychlops::Color col1,col2;
Psychlops::Rectangle rect1(50,50);
double d1,d2,d3,d4,x,y;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
Noise1.centering();
col1.set(0.8,0.4,0.1);
Noise1.clear(col1);
point1=Noise1.getCenter();
d1=Noise1.getHcenter();
d2=Noise1.getVcenter();
d3=Noise1.getHeight();
d4=Noise1.getWidth();
col2=Noise1.getPix(100,100);
//get命令の内容を画面表示する
x=point1.getX();
y=point1.getY();
sampleA.msg("getCenter_x:",50,200);
sampleA.var(x,200,200);//getCenter:X
sampleA.msg("getCenter_y:",50,250);
sampleA.var(y,200,250);//getCenter:Y
sampleA.msg("getHcenter:",50,300);
sampleA.var(d1,200,300);//getHcenter
sampleA.msg("getVcenter:",50,350);
sampleA.var(d2,200,350);//getVcenter
sampleA.msg("gettHeight:",50,400);
sampleA.var(d3,200,400);//getHeight
sampleA.msg("getWidth:",50,450);
sampleA.var(d4,200,450);//getWidth
//取得した色で円を描画する
sampleA.oval(rect1,col2);
Noise1.draw();
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
オフスクリーン領域に線を描画します。
指定する座標は画面上左上を座標(0,0)にするのではなく
オフスクリーン領域の左上を座標(0,0)とします。
|!Image::line()|line(double x1, double y1, double x2, double y2, [[Psychlops::Color]] col)|座標(x1,y1)から座標(x2,y2)に指定色で線を描画します|
|~|~|double x1 :描画する線の開始水平座標値|
|~|~|double y1 :描画する線の開始垂直座標値|
|~|~|double x2 :描画する線の終端水平座標値|
|~|~|double y2 :描画する線の終端垂直座標値|
|~|~|[[Psychlops::Color]] col:描画する線の描画色|
!!Image::line()の書き方
座標(100,100)から座標(200,200)に赤線を描画します。
指定Image外の座標に設定すると描画されないのでオフスクリーンの位置に注意すること。
{{{
<例>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Image Noise1(500,500);
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
Noise1.line(100,100,200,200,Color::red);
Noise1.draw();
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
!Image::load()
既存の任意の画像ファイルを読み込みます。
|!Image::load()|load(filename)|指定した画像ファイルを読み込みます|>|>|
|~|~|filename: 読み込む画像ファイルの名前を指定|>|>|
|~|~|読込み先を指定しない場合のパス|Mac:Macintosh HD¥ユーザ¥ユーザ名(任意)¥書類¥Psychlops¥|>|
|~|~|~|Win:実行ファイルと同じパス|>|
|~|~|読込みをする場合のパス指定|%APP%|Mac:実行バンドル(.app)と同じパス|
|~|~|~|~|Win:実行ファイルと同じパス|
|~|~|~|%RESOURCES%|Mac:実行バンドルの内部のResourcesフォルダ|
|~|~|~|~|Win:実行ファイルのパスの直下のrscという名前のフォルダ|
|~|~|~|%~USER_HOME%|Mac:"~/"と同じ(/Users/(user)/)|
|~|~|~|~|Win:ユーザープロファイルフォルダ(C:\Documents and Settings\(user)\)|
|~|~|~|%~USER_SETTING%|Mac:"~/Library/Psychlops/"|
|~|~|~|~|Win:"%~AppData%Psychlops"|
|~|~|~|%~USER_DOCUMENTS%|Mac:"~/Documents/Psychlops"|
|~|~|~|~|Win:ユーザープロファイルフォルダ\My Documents\Psychlops|
|~|~|~|%~USER_DOCUMENTS_ROOT%|Mac:"~/Documents"|
|~|~|~|~|Win:ユーザープロファイルフォルダMy Documents\|
**VISTA環境の場合、パス指定が正常に動作しません。VISTA環境の方はお手数ですが保存先を指定しないでお使いください。
[[3.3.2 画像ファイルを読み込み表示する]]
オフスクリーン領域に円を描画します。
円の描画位置はオフスクリーン領域の座標(0,0)に描画されます。
|!Image::oval()|([[Psychlops::Rectangle]] rect, [[Psychlops::Color]] col)|[[Psychlops::Rectangle]] rectで指定された直径の円を指定色で塗りつぶし描画します|
|~|~|[[Psychlops::Rectangle]] rect:描画する円の直径|
|~|~|[[Psychlops::Color]] col:描画する円の色を指定|
!!Image::oval()の書き方
座標(300,300)からはじまるオフスクリーン領域に直径(100,100)の赤円を描画します。
{{{
<例>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Image Noise1(300,300);
Psychlops::Rectangle rect1(100,100);
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
Noise1.centering();
Noise1.oval(rect1,Color::red);
Noise1.draw();
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
!Image::pix()
オフスクリーン領域に点を描画するための命令です。
[[Canvas::pix()|2.2.1 画面上に点を描画する-Pointクラス1-]]命令同様に点を描画するために「座標」と「色の指定」を行います。
色の指定についての詳細は[[2.2.5 描画する図形の色を指定する-Colorクラス-]]を参照してください。
|!Image::pix()|pix(double x, double y, [[Psychlops::Color]] col)|座標(x,y)に[[Psychlops::Color]] col色の点を描画します。|
|~|~|double x: 描画する点の水平座標値|
|~|~|double y: 描画する点の垂直座標値|
|~|~|[[Psychlops::Color]] col:描画する点の描画色|
|~|pix([[Psychlops::Point]] po, [[Psychlops::Color]] col)|Point座標(x,y)に[[Psychlops::Color]] col色の点を描画します|
|~|~|[[Psychlops::Point]] po :描画する点の座標|
|~|~|[[Psychlops::Color]] col:描画する点の色|
コード例は[[3.1.2節|3.1.2 オフスクリーン上に点・四角形を描画する]]参照
オフスクリーン領域に四角形を描画するための命令です。
実際の描画処理は[[Image::draw()]]命令で行われます。
色の指定についての詳細は[[2.2.5 描画する図形の色を指定する-Colorクラス-]]を参照してください。
|!Image::rect()|rect([[Psychlops::Rectangle]] rect, [[Psychlops::Color]] col)|[[Psychlops::Rectangle]]型の四角形を[[Psychlops::Color]] col色で描画します|
|~|~|[[Psychlops::Rectangle]] rect : 矩形の描画範囲|
|~|~|[[Psychlops::Color]] col:描画する四角形の描画色|
コード例は[[3.1.2節|3.1.2 オフスクリーン上に点・四角形を描画する]]参照
|!Image::save()|save(filename)|指定したImageを保存します|>|>|
|~|~|filename: 保存するImageの画像ファイル名称(拡張子まで)を指定|>|>|
|~|~|保存先を指定しない場合のパス|Mac:Macintosh HD¥ユーザ¥ユーザ名(任意)¥書類¥Psychlops¥|>|
|~|~|~|Win:実行ファイルと同じパス|>|
|~|~|保存をする場合のパス指定|%APP%|Mac:実行バンドル(.app)と同じパス|
|~|~|~|~|Win:実行ファイルと同じパス|
|~|~|~|%RESOURCES%|Mac:実行バンドルの内部のResourcesフォルダ|
|~|~|~|~|Win:実行ファイルのパスの直下のrscという名前のフォルダ|
|~|~|~|%~USER_HOME%|Mac:"~/"と同じ(/Users/(user)/)|
|~|~|~|~|Win:ユーザープロファイルフォルダ(C:\Documents and Settings\(user)\)|
|~|~|~|%~USER_SETTING%|Mac:"~/Library/Psychlops/"|
|~|~|~|~|Win:"%~AppData%Psychlops"|
|~|~|~|%~USER_DOCUMENTS%|Mac:"~/Documents/Psychlops"|
|~|~|~|~|Win:ユーザープロファイルフォルダ\My Documents\Psychlops|
|~|~|~|%~USER_DOCUMENTS_ROOT%|Mac:"~/Documents"|
|~|~|~|~|Win:ユーザープロファイルフォルダMy Documents\|
**VISTA環境の場合、パス指定が正常に動作しません。VISTA環境の方はお手数ですが保存先を指定しないでお使いください。
[[3.3.1 画像ファイルを保存する]]
宣言時に何の指定もしなかった場合、Image::set()命令を使用します。
|!Image::set()|set([[Psychlops::Rectangle]] rect, PixelComponentsCnt pixcompcntval)|指定された[[Psychlops::Rectangle]] 型と同じ大きさのオフスクリーン領域を設定します|
|~|~|[[Psychlops::Rectangle]] rect :オフスクリーンの大きさを与える[[Psychlops::Rectangle]] 型を指定(座標は引き継がれない)|
|~|~|PixelComponentsCnt pixcompcntval :[[カラーモード]]を指定。省略した場合はRGBが指定される|
|~|set(long x, long y, PixelComponentsCnt pixcompcntval)|指定されたx×yと同じ大きさのオフスクリーン領域を設定します|
|~|~|long x :オフスクリーン領域の横幅|
|~|~|long y :オフスクリーン領域の縦幅|
|~|~|PixelComponentsCnt pixcompcntval :[[カラーモード]]を指定。省略した場合はRGBが指定される|
コード例は[[3.1.1節|3.1.1 オフスクリーン領域の宣言]]参照
[[Psychlops::Image]]型の領域を移動します。
元の座標(x1,y1)を基点に入力値(x,y)だけ移動します。
|!Image::shift()|shift(double h, double v)|座標(X,Y)を(h,v)だけ移動します|
|~|~|double x:右水平方向の移動量|
|~|~|double y:下垂直方向の移動量|
コード例は[[3.1.3節|3.1.3 オフスクリーン上の任意の位置に描画する]]参照
|!Image::to|>|>|!グレースケール画像の輝度値をMatrixに書き込みます|
|~|to( Matrix luminance )|
|~|~|Matrix luminance|輝度値の行列|
|~|to( Point po, double width, double height, Matrix luminance)|
|~|~|Point po |コピー元領域の左上座標<br>(省略時は画像左上を起点とします)|
|~|~|double width |コピー元領域の幅(pixel)|
|~|~|double height |コピー元領域の高さ(pixel)|
|~|~|Matrix luminance |輝度値の行列|
|~|to( Point po, Rectangle rect, Matrix lumiannce) |
|~|~|Point po |コピー元領域の左上座標<br>(省略時は画像左上を起点とします)|
|~|~|Rectangle rect |コピー元領域のサイズを持つ矩形|
|~|~|Matrix luminance|輝度値の行列|
|~|to( Interval h_intvl, Interval v_intvl, Matrix luminance) |
|~|~|Interval h_intvl |コピー元領域の横座標の範囲(pixel)|
|~|~|Interval v_intvl |コピー元領域の縦座標の範囲(pixel)|
|~|~|Matrix luminance |輝度値の行列|
|!Image::to|>|>|!RGB画像の赤、緑、青チャネル値をMatrixに書き込みます|
|~|to( r, g, b ) |
|~|~|Matrix r |赤チャンネルの輝度値の行列|
|~|~|Matrix g |緑チャンネルの輝度値の行列|
|~|~|Matrix b |青チャンネルの輝度値の行列|
|~|to( Point po, double width, double height, Matrix r, Matrix g, Matrix b) |
|~|~|Point po |コピー元領域の左上座標<br>(省略時は画像左上を起点とします)|
|~|~|double width |コピー元領域の幅(pixel)|
|~|~|double height |コピー元領域の高さ(pixel)|
|~|~|Matrix r |赤チャネルの輝度値の行列|
|~|~|Matrix g |緑チャネルの輝度値の行列|
|~|~|Matrix b |青チャネルの輝度値の行列|
|~|to( Point po, Rectangle rect, Matrix r, Matrix g, Matrix b) |
|~|~|Point po |コピー元領域の左上座標<br>(省略時は画像左上を起点とします)|
|~|~|Rectangle rect |コピー元領域のサイズを持つ矩形|
|~|~|Matrix r |赤チャンネルの輝度値の行列|
|~|~|Matrix g |緑チャンネルの輝度値の行列|
|~|~|Matrix b |青チャンネルの輝度値の行列|
|~|to( Interval h_intvl, Interval v_intvl, Matrix r, Matrix g, Matrix b) |
|~|~|Interval h_intvl |コピー元領域の横座標の範囲(pixel)|
|~|~|Interval v_intvl |コピー元領域の縦座標の範囲(pixel)|
|~|~|Matrix r |赤チャネルの輝度値の行列|
|~|~|Matrix g |緑チャネルの輝度値の行列|
|~|~|Matrix b |青チャネルの輝度値の行列|
|!Image::to|>|>|!RGBA画像の各チャネルの輝度値・透明度チャネルの値をMatrixに書き込みます|
|~|to( Matrix r, Matrix g, Matrix b, Matrix a) |
|~|~|Matrix r |赤チャネルの輝度値の行列|
|~|~|Matrix g |緑チャネルの輝度値の行列|
|~|~|Matrix b |青チャネルの輝度値の行列|
|~|~|Matrix a |透明度チャネルの行列|
|~|to( Point po, double width, double height, Matrix r, Matrix g, Matrix b, Matrix a ) |
|~|~|Point po |コピー元領域の左上座標<br>(省略時は画像左上を起点とします)|
|~|~|double width |コピー元領域の幅(pixel)|
|~|~|double height |コピー元領域の高さ(pixel)|
|~|~|Matrix r |赤チャネルの輝度値の行列|
|~|~|Matrix g |緑チャネルの輝度値の行列|
|~|~|Matrix b |青チャネルの輝度値の行列|
|~|~|Matrix a |透明度チャネルの行列|
|~|to( Point po, Rectangle rect, Matrix r, Matrix g, Matrix b, Matrix a) |
|~|~|Point po |コピー元領域の左上座標<br>(省略時は画像左上を起点とします)|
|~|~|Rectangle rect |コピー元領域のサイズを持つ矩形|
|~|~|Matrix r |赤チャンネルの輝度値の行列|
|~|~|Matrix g |緑チャンネルの輝度値の行列|
|~|~|Matrix b |青チャンネルの輝度値の行列|
|~|~|Matrix a |透明度チャネルの行列|
|~|to( Interval h_intvl, Interval v_intvl, Matrix r, Matrix g, Matrix b, Matrix a ) |
|~|~|Interval h_intvl |コピー元領域の横座標の範囲(pixel)|
|~|~|Interval v_intvl |コピー元領域の縦座標の範囲(pixel)|
|~|~|Matrix r |赤チャネルの輝度値の行列|
|~|~|Matrix g |緑チャネルの輝度値の行列|
|~|~|Matrix b |青チャネルの輝度値の行列|
|~|~|Matrix a |透明度チャネルの行列|
サンプル
{{{
Img.to(1<=rng<=100, 1<=rng<=50, luminance);
}}}
書き込み先のMatrixはいったん破棄されて作り直されます。
大きさ等はImageのサイズと等しくなるように自動的に調整されます。
[[5.3.5 行列の中身をオフスクリーンに描画する]]
*[[オフスクリーンのサイズを指定したい|Image::set()]]
*[[オフスクリーンを特定の描画色で塗りつぶしたい|Image::clear()]]
*[[オフスクリーン上に点を打ちたい|Image::pix()]]
*[[オフスクリーン上に線を引きたい|Image::line()]]
*[[オフスクリーン上に四角形を描画したい|Image::rect()]]
*[[オフスクリーン上に円を描画したい|Image::oval()]]
*[[オフスクリーンの内容を画面に描画したい|Image::draw()]]
*[[オフスクリーンの描画位置を画面中央や特定の座標にセンタリングしたい|Image::centering()]]
*[[オフスクリーンの描画位置を移動させたい|Image::shift()]]
*[[オフスクリーンのメンバ(情報)を取得したい|Image::getXXXX()]]
*[[オフスクリーンに画像を読み込みたい|Image::load()]]
*[[オフスクリーンの描画内容をファイルとして保存したい|Image::save()]]
*[[Matrix型を用いて計算した描画内容をオフスクリーンへ読み込みたい|Image::to()]]
*[[オフスクリーンの描画内容をMatrix型へ変換したい|Image::to()]]
{{{
class IndependentVariables
{
int MaxVarNum, MaxStepNum;
double *VariableData;
int *VariableStepNumber;
int conditionNumber;
int repeatNumber;
Psychlops::Matrix CondMtx;
public:
int trialNumber;
IndependentVariables(int n, int m, int repeat): MaxVarNum(n), MaxStepNum(m){
VariableData = (double *)malloc(sizeof(double)*(n+1)*m);
VariableStepNumber = (int *)malloc(sizeof(double)*m);
repeatNumber=repeat;
trialNumber=-1;
}
virtual ~IndependentVariables(void){
free(VariableData);
free(VariableStepNumber);
};
virtual int set(int vnum, int arraynum, double *array){
if(vnum<MaxVarNum+1 && arraynum<MaxStepNum+1){
VariableStepNumber[vnum]=arraynum;
for(int i=0; i<arraynum; i++){*(VariableData+(vnum+1)*MaxStepNum+i)=*(array+i);}
return arraynum;
}
else return -1;
};
virtual int randomize(char* dataname=NULL){
trialNumber=repeatNumber;
for(int i=0; i<MaxVarNum; i++)trialNumber*=VariableStepNumber[i];
if(trialNumber<1){return -1;}
CondMtx.set(trialNumber, MaxVarNum);
int i,j,a;
double temp=1, temp2;
//Make Output Matrix
for(i=0; i<MaxVarNum; i++){
for(j=0; j<i; j++){temp*=(double)VariableStepNumber[j];}
for(j=0; j<trialNumber; j++){CondMtx(j+1,i+1)=(int)ceil(j/temp)% VariableStepNumber[i];}
temp=1;
}
//Randomize Output Matrix
for(i=1; i<trialNumber;i++){
a = Psychlops::random(i-1)+1;
for(j=1;j<MaxVarNum+1;j++){
temp2=CondMtx(a,j);
CondMtx(a,j)=CondMtx(i,j);
CondMtx(i,j)=temp2;
}
}
//swtich of the condition matrix save
if(dataname){
std::ofstream output;
output.open(dataname);
output << CondMtx << std::endl;
output.close();
}
}
virtual double get(int vnum, int trial_now){
if(vnum<MaxVarNum && (int)trial_now<trialNumber)
return *(VariableData+(vnum+1)*MaxStepNum+getCondMtx(vnum, trial_now));
else return -1;
};
virtual int getCondMtx(int vnum, unsigned int trial_now){
if(vnum<MaxVarNum && (int)trial_now<trialNumber)
return CondMtx( trial_now+1, vnum+1);
else return -1;
};
virtual double getIndependent(int vnum, int stepnum){
if(vnum<MaxVarNum && stepnum<VariableStepNumber[vnum])
return *(VariableData+(vnum)*MaxStepNum+stepnum);
else return -1;
};
virtual int getMStep(int vnum){
if(vnum<MaxVarNum)return VariableStepNumber[vnum];
else return -1;
};
};
}}}
|!Input::get()|Input::get([[Keyboard::KeyCode|キーコード表]] code, [[Keyboard::KeyState]] state)|キーボードの反応を取得します|
|~|~|code 反応を取得するキーの名前 [[キーコード表]] 参照|
|~|~|state 反応状態のオプション (省略時はpushed) [[Keyboard::KeyState]]参照|
|~|Input::get([[Mouse::ButtonCode|キーコード表]] code, [[Mouse::ButtonState]] state)|マウスの反応を取得します|
|~|~|code 反応を取得するマウスボタンの名前 [[キーコード表]] 参照|
|~|~|state 反応状態のオプション (省略時はpushed) [[Mouse::ButtonState]]参照|
[[4.1 外部装置(キーボード/マウス)の操作内容を取得する]]
キーボードやマウスといった外部入力装置の入出力ルーチンの初期化処理
|!Input::refresh()|refresh(void)|キーボード・マウスの入出力ルーチンを初期化します|
コード例は[[Tips: Psychlopsにおけるキーボードマウスの入出力検出ルーチン]]参照
| !~ButtonState | pressed |ボタンが押し続けられている |
|~| pushed | 判定が行われるまでにボタンが押された |
|~| released | 判定が行われるまでにボタンが離された |
[[このマニュアルの見方について(ここをクリックしてください)]]
[[0. Psyclopsとは]]
[[第I部 Psychlopsの基本]]
[[1. インストールを行う]]
[[2. 基本的な描画を行う]]
[[3. 複雑な描画を行う1(オフスクリーンへの描画)]]
[[4. 複雑な描画を行う2(複数オブジェクトのグループ化と回転・拡大縮小)]]
[[5. 入出力を行う]]
[[6. 時間制御と各種ツールを使用する]]
[[第II部 Psychlopsの応用]]
[[1. 既成クラスを用いた簡単な実験プログラムを作成する]]
[[2. デモを作成する]]
[[3. 透明度を使って刺激描画を高速化する]]
[[4. アナログ入出力を行う]]
付録
[[関数一覧_FunctionList]]
[[関数逆引きとQ&A]]
[[Tips]]
[[MainMenu]]
剰余の計算を行います。
負の数を割ったときの剰余は多くの言語で未定義とされていますが、C++(%演算子やfmod関数)では負の数になります。また、C++の剰余演算子は整数型のみ定義されています。
Math::modは剰余の計算を浮動小数点型に拡張するとともに、負の数を割ったときの挙動を周期関数的にしてあります。視覚実験では周期関数の位相を値とすることが多くありますが、この関数を使うと位相が負でも正でも一定の範囲の剰余が得られます。ifやswitchで値ごとに条件分岐している場合には特に役立ちます。
たとえば、-450を360で割った剰余をとる場合、
{{{
-90 == -450 % 360
270 == Math::mod(-450, 360)
}}}
%演算子では剰余が負の数になってしまいますが、Math::modを使うと正の数で得ることができます。両関数をグラフで描画すると下図のようになります。
[img[image/Math.mod.png]]
|!Math::mod()|mod(double lhs, double rhs)|剰余を計算します。|
|~|~|double lhs: 左辺項(割られる数)を指定します。|
|~|~|double rhs: 右辺項(割る数)を指定します。|
Math::mod()の使用例
以下の例では%演算子とMath::mod()関数の返り値をプロットしています。
上と同様の図が画面に表示されます
{{{
#include <psychlops.h>
using namespace Psychlops;
void psychlops_main() {
Canvas display(Canvas::fullscreen);
double upper_v = display.getVcenter()-100, lower_v = display.getVcenter()+100;
while(!Input::get(Keyboard::esc)) {
//Draw Axis
display.line(display.getHcenter()-300, upper_v, display.getHcenter()+300, upper_v, Color::white);
display.line(display.getHcenter()-300, lower_v, display.getHcenter()+300, lower_v, Color::white);
display.line(display.getHcenter(), upper_v+60, display.getHcenter(), upper_v-60, Color::white);
display.line(display.getHcenter(), lower_v+60, display.getHcenter(), lower_v-60, Color::white);
display.msg("C++ %", display.getHcenter()-100, upper_v -20, Color::white);
display.msg("Math::mod", display.getHcenter()-100, lower_v -20, Color::white);
//Plot Mod Results
for(int i=-300; i<300; i++) {
display.pix(i+display.getHcenter(), upper_v - i%50, Color::red);
display.pix(i+display.getHcenter(), lower_v - Math::mod(i, 50), Color::green);
}
display.flip();
}
}
}}}
配列の中身をシャッフルします。
|!Psychlops::shuffle()|shuffle(X array, int num)|配列の中身をシャッフルします。|
|~|~|X array: 配列変数を指定します。型=代入が可能であればなんでもかまいません。|
|~|~|int num: 配列の要素数を指定します。|
|~|shuffle(X array, int num, X init)|配列を初期化してから中身をシャッフルします。|
|~|~|X array: 配列変数を指定します。型はint, double等で有効です。|
|~|~|int num: 配列の要素数を指定します。|
|~|~|X init: 配列の初期値です。0を指定すると、配列が0~num-1の数で埋められてから、シャッフルを行います。|
!!shuffleの使い方
{{{
#include <psychlops.h>
using namespace Psychlops;
void psychlops_main() {
Canvas display(Canvas::fullscreen);
const int NUM = 10;
int array[NUM];
Math::shuffle(array, NUM, 0);
while(!Input::get(Keyboard::esc)) {
for(int i=0; i<NUM; i++) {
display.var(array[i], 100, 20*i + 100);
}
display.flip();
}
}
}}}
この行列の下に他の行列を継ぎ足します。
|!Matrix::catRow|catRow(other)|この行列の次の行から別の行列を継ぎ足します。|
|~|~|Matrix other: 付け足す行列を指定します。付け足してもこれ自身は消されません。|
{{{
#include <fstream>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Matrix LuminanceMap1, LuminanceMap2;
long Width=5, Height=1;
void psychlops_main() {
std::ofstream result("dump.txt");
LuminanceMap1.set(Height, Width);
LuminanceMap1 = 1;
LuminanceMap2.set(Height, Width);
LuminanceMap2 = 2;
LuminanceMap1.catRows(LuminanceMap2);
result << LuminanceMap1;
}
}}}
[[Psychlops::Matrix]]型の情報を、Matrix::getXXXX()命令を使って取得します(XXXXにはそれぞれの情報に対応した
名前が入ります)。
|!Matrix::getXXXX()|getRows()|行列の行数を取得します|
|~|getCols()|行列の列数を取得します|
!!Matrix::getXXXX()の書き方
[[Psychlops::Matrix]]型の情報を取得しファイルに出力します。
{{{
#include <fstream>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Matrix LuminanceMap1;
long Width=5, Height=3;
void psychlops_main() {
std::ofstream result("dump.txt");
LuminanceMap1.set(Height, Width);
result << LuminanceMap1.getRows() << " " << LuminanceMap1.getCols();
}
}}}
行列の全要素中の最大値・最小値を得ます
|!Matrix::min|min()|全要素中の最小値を得ます。|
|!Matrix::max|max()|全要素中の最大値を得ます。|
{{{
// 行列の要素に乱数を代入し、その中の最大値と最小値を抽出します。
#include <fstream>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Matrix LuminanceMap1;
long Width=10, Height=10;
void psychlops_main() {
std::ofstream result("dump.txt");
LuminanceMap1.set(Height, Width);
for(int row; row<Width; row++) {
for(int col; col<Width; col++) {
LuminanceMap1(row, col) = Psychlops::random();
}
}
result << LuminanceMap1;
result << LuminanceMap1.max() << " " << LuminanceMap1.min();
}
}}}
要素どうしで大きいものをまとめた行列、小さいものをまとめた行列を返すmax/minもあります。
|!Matrix::min|min(Matrix a, Matrix b)|aとbの各要素を比較し、小さいほうを要素とする行列を返します。両Matrixの大きさは同じでなければなりません。|
|!Matrix::max|max(Matrix a, Matrix b)|aとbの各要素を比較し、大きいほうを要素とする行列を返します。両Matrixの大きさは同じでなければなりません。|
{{{
A =
1 2 3
0 0 2
B =
5 3 0
1 1 1
C = max(A, B);
C =>
5 3 3
1 1 2
}}}
行列をいったん1行の配列に並べ、新たに行を仕切りなおします。
|!Matrix::reshape|reshape(row, col)|要素を変えずに、行と列の数のみを変更します。要素数(行*列)は変更前と変更後で同じでなければなりません。|
|~|~|int row 新しい行数を指定します。|
|~|~|int col 新しい列数を指定します。|
!!reshape操作の内容
{{{
1 2 3
4 5 6
をreshape(3, 2)すると
1 2
3 4
5 6
}}}
!!reshapeの書き方
{{{
#include <fstream>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Matrix LuminanceMap1;
long Width=16, Height=1;
void psychlops_main() {
std::ofstream result("dump.txt");
LuminanceMap1.set(Height, Width);
for(int col; col<Width; col++) {
LuminanceMap1(1, col) = col;
}
result << LuminanceMap1.reshape(4, 4);
}
}}}
行列を90度単位で回転させます。
|!Matrix::rot90|rot90(count)|行列を90度単位で反時計回りに回転します。|
|~|~|int count 回転角度を90度単位で指定します。-3,1,5のとき90度、-2,2,6のとき180度、-1,3,7のとき270度回転します。|
!!回転操作の内容
{{{
1 2 3
4 5 6
をrot90(1)すると
3 6
2 5
1 4
}}}
!!rot90()の書き方
{{{
#include <fstream>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Matrix LuminanceMap1;
long Width=16, Height=1;
void psychlops_main() {
std::ofstream result("dump.txt");
LuminanceMap1.set(Height, Width);
for(int col; col<Width; col++) {
LuminanceMap1(1, col) = col;
}
result << LuminanceMap1.rot90(1);
}
}}}
!Matrix::set()
行列の宣言時に何の指定もしなかった場合、Matrix::set()命令を使用します。
|!Matrix::set()|Matrix(rows, cols)|指定されたrows行cols列の行列を設定します|
|~|~|long rows :行列の行数|
|~|~|long cols :行列の列数|
[[5.3.1 行列の宣言]]
各要素を行・列各方向にずらす命令です。
|!Matrix::slide|slide(row, col)|要素を行方向、列方向にずらします。あふれた分は、反対側に移動します。|
|~|~|int row 行方向にずらす量を指定します。|
|~|~|int col 列方向にずらす量を指定します。|
!!slide操作の内容
{{{
1 2 3
4 5 6
をslide(1,0)すると
4 5 6
1 2 3
1 2 3
4 5 6
をslide(0,1)すると
3 1 2
6 4 5
1 2 3
4 5 6
をslide(1,1)すると
6 4 5
3 1 2
}}}
!!slideの書き方
{{{
#include <fstream>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Matrix LuminanceMap1;
long Width=10, Height=10;
void psychlops_main() {
std::ofstream result("dump.txt");
LuminanceMap1.set(Height, Width);
for(int row; row<Width; row++) {
for(int col; col<Width; col++) {
LuminanceMap1(row, col) = row*Height + col;
}
}
result << LuminanceMap1.slide(1,0);
}
}}}
行列を転置します。
|!Matrix::transpose|Matrix transpose(void)|行列を転置(行と列の読み替え)します。自分自身を書き換えるので注意してください。|
!!転置操作の内容
{{{
1 2 3
4 5 6
をtransposeすると
1 4
2 5
3 6
}}}
!!transposeの書き方
{{{
#include <fstream>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Matrix LuminanceMap1;
long Width=10, Height=10;
void psychlops_main() {
std::ofstream result("dump.txt");
LuminanceMap1.set(Height, Width);
for(int row; row<Width; row++) {
for(int col; col<Width; col++) {
LuminanceMap1(row, col) = row*Height + col;
}
}
result << LuminanceMap1.transpose();
}
}}}
| ~KeyState | pressed | キーが押し続けられている |
|~| pushed | 判定が行われるまでにキーが押された |
|~| released | 判定が行われるまでにキーが離された |
マウスカーソルの表示切り替え。
[[Mouse::show()]]命令と対になっている命令です。
|!Mouse::hide()|hide()|マウスカーソルを非表示にする|
コード例は[[例6: マウスのデモ]]参照
マウスカーソルの表示切り替え。
[[Mouse::hide()]]命令と対になっている命令です。
|!Mouse::show()|show()|マウスカーソルを表示する|
コード例は[[例6: マウスのデモ]]参照
PixelComponentsCntの指定方法
|GRAY|グレースケールの輝度値|
|RGB|色の三原色に基づいた赤・緑・青で構成された色|
|RGBA|RGBに透明度が追加された色|
※省略時にはRGBが指定される
詳しくは[[2.2.5 描画する図形の色を指定する-Colorクラス-]]を参照
点を設定します。
[[Psychlops::Point]]型の命令を使用する前に必ず宣言が必要です。
宣言のみの場合、[[Point::set()]]で点の詳細情報を設定しますが
宣言文のみでも情報の設定が可能です。
|!Pointの宣言|Point()|[[Psychlops::Point]]型の宣言を行います|
|~|Point(double x, double y)|[[Psychlops::Point]]型の宣言を行い座標(x,y)に値を指定します|
|~|~|double x:座標xを指定|
|~|~|double y:座標yを指定|
!!Point()の宣言の書き方
座標(100 × 100)に赤の点を描画します。
{{{
<例>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Point point1;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
point1.set(100,100);
sampleA.pix(point1,Color::red);
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
点を画面中心に描画します。
|!Point::centering()|centering()|座標(x,y)の点を画面の中心に移動します|
|~|centering(Psychlops::Rectangle rect)|rectの中心に点を移動します|
コード例は[[2.2.1節|2.2.1 画面上に点を描画する-Pointクラス1-]]参照
対象の点の座標を取得します。
|!Point::get()|getX()|対象の点のX座標の値を取得します|
|~|getY()|対象の点のY座標の値を取得します|
!!Point::get()の書き方
{{{
<例>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Point point1(100,100);
double d1,d2;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
point1.centering();
sampleA.pix(point1,Color::red);
d1=point1.getX();
d2=point1.getY();
//get命令の内容を画面表示する
sampleA.msg("getX:",50,200);
sampleA.var(d1,200,200);//getX
sampleA.msg("getY:",50,250);
sampleA.var(d2,200,250);//getY
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
座標(x,y),ないし座標X,Yを指定し点を描画します。
Point::set()を使用し座標を取得することでソースの簡略化がなされ
また、修正がしやすくなります。
|!Point::set()|set(double x, double y)|座標(x,y)の値を設定します|
|~|~|double x:水平座標|
|~|~|double y:垂直座標|
|~|setX(double val)|水平座標の値を設定します|
|~|~|double val :水平座標値|
|~|setY(double val)|垂直座標の値を設定します|
|~|~|double val :垂直座標値|
コード例は[[2.2.1節|2.2.1 画面上に点を描画する-Pointクラス1-]]参照
点の移動命令です。
元の座標(x,y)を基点に入力値(h,v)だけ移動します。
|!Point::shift()|shift(double h, double v)|座標(X,Y)を(h,v)だけ移動します|
|~|~|double h:右水平方向の移動量|
|~|~|double v:下垂直方向の移動量|
コード例は[[2.2.1節|2.2.1 画面上に点を描画する-Pointクラス1-]]参照
* [[Point型の変数の座標を設定したい|Point::set()]]
* [[Point型の変数を画面中央やRectangle型の変数の中央に移動したい|Point::centering()]]
* [[Point型の変数の座標を移動させたい|Point::shift()]]
* [[Point型の現在の座標を取得したい|Point::get()]]
*メソッド
**[[Canvasの宣言]]
**[[Canvas::clear()]]
**[[Canvas::flip()]]
**[[Canvas::flipAfter()|Canvas::flip()]]
**[[Canvas::flipAndWait()|Canvas::flip()]]
**[[Canvas::getXXXX()]]
**[[Canvas::var()]]
**[[Canvas::msg()]]
**[[Canvas::pix()]]
**[[Canvas::line()]]
**[[Canvas::rect()]]
**[[Canvas::oval()]]
**[[Canvas::to()]]
**[[Canvas::copy()]]
**[[Canvas::setGammaValue()]]
**[[Canvas::setGammaTable()]]
**[[Canvas::setClearColor()]]
**[[Canvas::progressbar()]]
**[[Canvas::watchFPS()]]
**[[Canvas::showFPS()]]
**[[Canvas::lastFailedFrames()]]
*メンバ
**TargetSurface
*メソッド
**[[Clock::update()]]
**[[Clock::at_msec()]]
*メソッド
** [[Color()の宣言]]
** [[Color::set()]]
** [[Color::get()]]
** [[Color::setGammaValue()]]
** [[Color::strict_match]]
** [[Color::XXXX]]
*メソッド
**[[Image()の宣言]]
**[[Image::set()]]
**[[Image::clear()]]
**[[Image::pix()]]
**[[Image::line()]]
**[[Image::rect()]]
**[[Image::oval()]]
**[[Image::draw()]]
**[[Image::quicken()|3.1.4 発展的な内容]]
**[[Image::centering()]]
**[[Image::shift()]]
**[[Image::getXXXX()]]
**[[Image::load()]]
**[[Image::save()]]
**[[Image::from()]]
**[[Image::to()]]
*メソッド
**[[Input::get()]]
**[[Input::refresh()]]
*メソッド
**[[Keyboad::get()]]
*メソッド
** [[Psychlops::random]]
** [[Math::shuffle]]
** [[Math::mod]]
*メソッド
**[[Matrix()の宣言|6.3.1 行列の宣言]]
**[[Matrix::set()]]
**[[Matrix::要素アクセス・部分行列|6.3.2 行列の要素の操作]]
**[[Matrix::四則演算|6.3.4 行列の加減乗除]]
**[[Matrix::slide()]]
**[[Matrix::transpose()]]
**[[Matrix::rot90()]]
**[[Matrix::catRow()]]
**[[Matrix::reshape()]]
**[[Matrix::getXXXX()]]
**[[Matrix::min() / max()]]
**[[Matrix::mesh()|6.3.6 メッシュグリッドの作成]]
*メソッド
**[[Mouse::hide()]]
**[[Mouse::show()]]
*メソッド
**[[Point()の宣言]]
**[[Point::set()]]
**[[Point::centering()]]
**[[Point::shift()]]
**[[Point::get()]]
*メソッド
**[[Rectangleの宣言]]
**[[Rectangle::set()]]
**[[Rectangle::centering()]]
**[[Rectangle::shift()]]
**[[Rectangle::draw()]]
**[[Rectangle::resize()]]
**[[Rectangle::setColor()]]
**[[Rectangle::getXXXX()]]
**[[Rectangle::include()]]
**[[Rectangle::dup()]]
乱数を返します。標準の擬似乱数生成アルゴリズムは[[dSFMT|http://www.math.sci.hiroshima-u.ac.jp/%7Em-mat/MT/SFMT/index-jp.html]]を利用しています。
|!Psychlops::random()|random()|[0.0, 1.0)の範囲の一様乱数を生成します。|
|~|random(double max)|[0.0, max)の範囲の一様乱数を生成します。|
|~|random(int levels)|[0, levels)の範囲の離散的一様乱数を生成します。|
|~|random(double min, double max)|[min, max)の値をランダムに返します。|
|~|~|double min: ランダムで返す値の最小値を指定|
|~|~|double max: ランダムで返す値の最大値を指定|
引数が実数(double)か整数(int)かで動作が異なりますので、実数で返したい場合は最大値が整数でも必ず1.0などのように小数点をつけて指定してください。整数変数を指定する場合は(double)でキャストしてください。
{{{
random(5) ← [0,1,2,3,4] のいずれかの数がint型で返る
random(5.0) ← [0, 5)の間の浮動小数点値が返る
int i = 5;
random(i) ← [0,1,2,3,4] のいずれかの数がint型で返る
random((double)i) ← [0, 5)の間の浮動小数点値が返る
}}}
以下の例では100 ms毎にrandom命令を使用して作成したランダム配列を元に10000個のドットを画面に表示します。
{{{
#include <psychlops.h>
using namespace Psychlops;
void psychlops_main() {
Canvas display(Canvas::fullscreen);
double wid = 200, hei = 200;
int refresh=display.getRefreshRate()/10;
const int DOTNUM = 1000;
double x[DOTNUM], y[DOTNUM];
while(!Input::get(Keyboard::esc)) {
display.clear(0.0);
for(int i=0; i<DOTNUM; i++) {
x[i] = random(wid)+display.getHcenter()-wid*0.5;
y[i] = random(hei)+display.getVcenter()-hei*0.5;
}
display.pix(DOTNUM, x, y, Color::white);
display.flip(refresh);
}
}
}}}
直接の質問には(開発者と面識がある場合を除いて)お答えしておりません。
Psychlops Authors Mailingリストに入っていただくのが便利です。
このメーリングリストはGoogleグループサービスを利用しております。
このメーリングリストに参加なされたい場合は、[[ここ|http://groups.google.com/group/psychlops/post?sendowner=1]]から申請してください。
可能です。
OpenGL命令に関しては、Psychlopsライブラリのインクルード時に既にOpenGLライブラリのインクルードが行われていますので、そのまま書いていただいてコンパイル・実行可能となっています。
自作ライブラリについては、関数名の衝突が無ければそのまま書いていただいて結構です。関数名が衝突する場合は、スコープ演算子などを用いて衝突を回避すれば利用が可能です。
{{{
void RectLuminance() {
double rect_size = 100;
double rect_lum = 0.5;
double bg_lum = 0.2;
Psychlops::Rectangle rect(rect_size,rect_size);
rect.centering();
Range rng;
Independent << rect_size | "Rect Size" | 1< rng< 500 | 10.0 | 2.0 ;
Independent << rect_lum | "Rect Luminance" | 0.0<=rng<=1.0 | 0.1 | 0.05;
Independent << bg_lum | "BG Luminance" | 0.0<=rng<=1.0 | 0.1 | 0.05;
while(!Input::get(Keyboard::esc)) {
Display::clear(Color(bg_lum));
rect.resize(rect_size,rect_size);
rect.display(rect_lum);
Display::flip();
}
}
void psychlops_main() {
Canvas display(Canvas::fullscreen);
Procedure p;
p.setDesign(Procedure::DEMO);
p.setProcedure(RectLuminance);
p.run();
}
}}}
[[Psychlops::Rectangle]]型の図形を移動します。
[img[image/rect_centering().png]]
座標を指定する場合、その座標に図形の中心が設定されます。
|!Rectangle::centering()|centering()|[[Psychlops::Rectangle]] を画面中心に移動します|
|~|centering(double x, double y)|[[Psychlops::Rectangle]] を指定した座標(x,y)に移動します|
|~|~|double x:移動する図形のx座標|
|~|~|double y:移動する図形のy座標|
|~|centering([[Psychlops::Point]] po)|[[Psychlops::Rectangle]]を指定したpoint座標(x,y)に移動します|
|~|~|[[Psychlops::Point]] po:移動する図形の(x,y)座標|
|~|centering(Rectangle rect)|[[Psychlops::Rectangle]] を指定した[[Psychlops::Rectangle]] rectの中心座標(x,y)に移動します(2つのRectangleを中央ぞろえします)|
|~|~|[[Psychlops::Rectangle]] rect :センタリングの中心座標を与える[[Psychlops::Rectangle]]|
コード例は[[2.2.6節|2.2.6 画面上に縞を描画する-Rectangleクラス2-]]参照
[[Psychlops::Rectangle]]型の図形の描画命令です。
|!Rectangle::draw()|draw()|対象の[[Psychlops::Rectangle]]型を描画します|
|~|draw([[Psychlops::Color]] col)|対象の[[Psychlops::Rectangle]]型を[[Psychlops::Color]] colで塗りつぶし描画します|
|~|~|[[Psychlops::Color]] col:描画する四角形の描画色|
コード例は[[2.2.6節|2.2.6 画面上に縞を描画する-Rectangleクラス2-]]参照
[[Psychlops::Rectangle]]型を複製した一時オブジェクトを作成します。
|!Rectangle::dup()|矩形領域の複製を行います|
{{{
Psychlops::Rectangle rect1(10,10), rect2(10,10);
rect1.centering(); // rect1の中心位置は変わっている
rect2.dup().centering() // rect2の中心位置は変わっていないが、複製がセンタリングされている
}}}
[[Psychlops::Rectangle]]型の情報を、Rectangle::getXXXX()命令を使って取得します(XXXXにはそれぞれの情報に対応した名前が入ります)。
|!Rectangle::getXXXX()|getCenter()|四角形の中心座標(x,y)を取得します|
|~|getHcenter()|横方向の中心座標(x)を取得します|
|~|getVcenter()|縦方向の中心座標(y)を取得します|
|~|getHeight()|四角形の高さ(ピクセル)を取得します|
|~|getWidth()|四角形の幅(ピクセル)を取得します|
|~|getTop()|四角形上辺のX座標(x)を取得します|
|~|getLeft()|四角形左辺のX座標(x)を取得します|
|~|getBottom()|四角形下辺のY座標(y)を取得します|
|~|getRight()|四角形右辺のY座標(y)を取得します|
[img[image/RectangleGet.png]]
!!Rectangle::getXXXX()の書き方
[[Psychlops::Rectangle]]型の情報を取得し画面表示を行います。
画面表示は[[Canvas::var()]]命令と[[Canvas::msg()]]命令を使用します。
{{{
<例>
#include <psychlops.h>
using namespace Psychlops;
double d1,d2,d3,d4,d5,d6,d7,d8,x,y;
Psychlops::Point point1;
Psychlops::Rectangle rect1;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
rect1.set(100,100);
rect1.centering();
sampleA.rect(rect1,Color::cyan);
point1=rect1.getCenter();
d1=rect1.getHcenter();
d2=rect1.getVcenter();
d3=rect1.getHeight();
d4=rect1.getWidth();
d5=rect1.getTop();
d6=rect1.getLeft();
d7=rect1.getBottom();
d8=rect1.getRight();
//get命令の内容を画面表示する
x=point1.getX();
y=point1.getY();
sampleA.msg("getCenter_x:",50,200);
sampleA.var(x,200,200);//getCenter:X
sampleA.msg("getCenter_y:",50,250);
sampleA.var(y,200,250);//getCenter:Y
sampleA.msg("getHcenter:",50,300);
sampleA.var(d1,200,300);//getHcenter
sampleA.msg("getVcenter:",50,350);
sampleA.var(d2,200,350);//getVcenter
sampleA.msg("gettHeight:",50,400);
sampleA.var(d3,200,400);//getHeight
sampleA.msg("getWidth:",50,450);
sampleA.var(d4,200,450);//getWidth
sampleA.msg("getTop:",250,200);
sampleA.var(d5,400,200);//getTop
sampleA.msg("getLeft:",250,250);
sampleA.var(d6,400,250);//getLeft
sampleA.msg("getBottom:",250,300);
sampleA.var(d7,400,300);//getBottom
sampleA.msg("getRight:",250,350);
sampleA.var(d8,400,350);//getRight
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
[[Psychlops::Rectangle]]型の座標の中にに他のRectangleや点が含まれるかどうかを判定します。
|!Rectangle::include()|include(double x, double y)|対象の座標を指定|
|~|include(Point point)|[[Psychlops::Point]] point:判定対象の点|
|~|include(Rectangle rect)|[[Psychlops::Rectangle]] rect:判定対象の四角形|
!!Rectangle::include()の書き方
[[例6: マウスのデモ]]をRectangle::include()を使って判定してみます。
実行すると画面左上に黒い四角形が、中央に白色の四角形が表示されます。
以下の2行でマウスのポインタ位置が白色の四角形の上にあるかどうかを判定しています。
もし、マウスポインタが四角形の上にあって、マウスの左ボタンが押され続けていれば、変数draggingに1が代入されます。
{{{
if(Input::get(Mouse::left, Mouse::pressed) && rect.include(Mouse::x, Mouse::y)) dragging = 1;
if(Input::get(Mouse::left, Mouse::released)) dragging = 0;
}}}
マウスで白色の四角形をドラッグアンドドロップして、黒い四角形に重ねてみましょう。
{{{
#include <psychlops.h>
using namespace Psychlops;
void psychlops_main() {
Canvas display(Canvas::fullscreen);
Mouse::show();
double rectsize=80;
Psychlops::Rectangle rect(rectsize,rectsize);
rect.centering();
Psychlops::Rectangle drop_here(rectsize*2,rectsize*2);
drop_here.shift(10,10);
Color rectcol, dropareacol;
int dragging = 0;
Input::refresh();
while(!Input::get(Keyboard::spc)){
display.clear(Color::gray);
// Checking rects
if(Input::get(Mouse::left, Mouse::pressed) && rect.include(Mouse::x,
Mouse::y)) dragging = 1;
if(Input::get(Mouse::left, Mouse::released)) dragging = 0;
if(dragging) {
rect.centering(Mouse::x, Mouse::y);
rectcol.set(1.0);
} else {
rectcol.set(0.25);
}
if(drop_here.include(rect)) {
dropareacol.set(1.0,0.0,0.0);
} else {
dropareacol.set(0.0,0.0,0.0);
}
drop_here.draw(dropareacol);
display.msg("Drop a rect here!", drop_here.getLeft()+10,
drop_here.getTop()+10);
rect.draw(rectcol);
display.flip();
}
}
}}}
[[Psychlops::Rectangle]]型のサイズの再設定を行います。
|!Rectangle::resize()|resize(double w, double h)|対象の[[Psychlops::Rectangle]]型のサイズをw×hに変更します|
|~|~|double w:変更後の横幅|
|~|~|double h:変更後の高さ|
!!Rectangle::resize()の書き方
200 × 200の四角形を50 × 100に変更します。
{{{
<例>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Rectangle rect1(200,200);
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
rect1.setColor(Color::green);
rect1.resize(50,100);
rect1.centering();
rect1.draw();
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
|!Rectangle::set()|set(double width, double height)|>|!width×heightの四角形を設定します|
|~|~|double width|描画する四角形の横幅|
|~|~|double height|描画する四角形の縦幅|
|~|set(double l, double t, double r, double b)|>|!左上座標(l,t)から右下座標(r,b)までの四角形を設定します|
|~|~|double l|描画する四角形の左上座標x1を指定|
|~|~|double t|描画する四角形の左上座標y1を指定|
|~|~|double r|描画する四角形の右下座標x2を指定|
|~|~|double b|描画する四角形の右下座標y2を指定|
|~|set([[Psychlops::Point]] point1, [[Psychlops::Point]] point2)|>|!左上Point座標(x1,x1)から右下Point座標(X2,Y2)までの四角形を設定します|
|~|~|[[Psychlops::Point]] point1|左上座標(x1,y1)を指定|
|~|~|[[Psychlops::Point]] point2|右下座標(x2,y2)を指定|
[[Psychlops::Rectangle]]型の色設定を行います。
この命令を使用する場合は、[[Rectangle::draw()]]命令を使用してください。
|!Rectangle::setColor()|setColor([[Psychlops::Color]] col)|対象の[[Psychlops::Rectangle]]型を[[Psychlops::Color]] colで塗りつぶします|
|~|~|[[Psychlops::Color]] col:四角形の描画色|
!!Rectangle::setColor()の書き方
{{{
<例>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Rectangle rect1;
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
rect1.set(100,100);
rect1.setColor(Color::red);
rect1.centering();
rect1.draw();
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
[[Psychlops::Rectangle]]型の図形を移動します。
元の座標(x,y)を基点に入力値(h,v)だけ移動します。
|!Rectangle::shift()|shift(double h, double v)|座標(X,Y)を(h,v)だけ移動します|
|~|~|double h:右水平方向の移動量|
|~|~|double v:下垂直方向の移動量|
[img[image/rect_shift().png]]
コード例は[[2.2.6節|2.2.6 画面上に縞を描画する-Rectangleクラス2-]]参照
RectangleはCanvas上に四角形の領域を設定する命令です。
[[Psychlops::Rectangle]]型の命令を使用する前に必ず宣言が必要です。
宣言のみの場合、[[Rectangle::set()|2.2.3 画面上に四角形を描画する-Rectangleクラス1-]]で四角形の詳細情報を設定しますが
宣言文のみでも情報の設定が可能です。
|!Rectangleの宣言|Rectangle()|[[Psychlops::Rectangle]]型の宣言を行います|
|~|Rectangle(double width, double height)|[[Psychlops::Rectangle]]型の宣言を行いwidth×heightの四角形を設定します|
|~|~|double width:描画する四角形の横幅|
|~|~|double height:描画する四角形の縦幅|
|~|Rectangle(double l, double t, double r, double b)|[[Psychlops::Rectangle]]型の宣言を行い左上座標(l,t)から右下座標(r,b)までの四角形を設定します|
|~|~|double l:描画する四角形の左上座標x1を指定|
|~|~|double t:描画する四角形の左上座標y1を指定|
|~|~|double r:描画する四角形の右下座標x2を指定|
|~|~|double b:描画する四角形の右下座標y2を指定|
!!Rectangle()の宣言の書き方
100 × 100の四角形を描画します。
{{{
<例>
#include <psychlops.h>
using namespace Psychlops;
Psychlops::Rectangle rect1(100,100);
void psychlops_main() {
Canvas sampleA(Canvas::fullscreen);
sampleA.rect(rect1,Color::cyan);
sampleA.flip();
while(!Input::get(Keyboard::spc));
}
}}}
* [[Rectangle型の変数を画面中央に持って行きたい|Rectangle::centering()]]
* [[Rectangle型の変数を任意の位置に持って行きたい|Rectangle::shift()]]
* [[Rectangle型の領域を塗りつぶしたい|Rectangle::draw()]]
* [[Rectangle型の変数の画面上のサイズを再設定したい|Rectangle::resize()]]
* [[Rectangle型の変数のメンバを取得したい|Rectangle::getXXXX()]]
* [[Rectanble型の変数に対して包含関係を判定したい|Reectangle::include()]]
<<tabs TabTimeline 'タグ' 'タグ一覧' TabTags '詳細' '詳細' TabMore>>
Version 1.3.5 (2009/September)
Generated by TiddlyWiki
Authored by Psychlops Admin Group
/***
CSSカスタマイズ
***/
body {
background-color: #f0f8f0;
margin: 0 auto;
padding:0;
font-family: arial,helvetica, centurygothic;
}
#displayArea {
color: #1c311c;
background-color: #e0f8e0;
position: relative;
width: 70%;
top : 12px;
margin-left:235px;
font-size: 1.1em;
}
#mainMenu{
color: #1c311c;
font-family: arial,helvetica, centurygothic;
font-size:100%;
background: #ffffcc;
border: 1px dotted #54c354;
position: absolute;
top : 165px;
left: 10px;
width:200px;
text-align: left
}
#sidebar {
position: absolute;
top: 750px;
left: 10px;
width: 200px;
font-size: .9em;
}
/*{{{*/
h1,h2,h3,h4,h5 {
padding-top: 0.3em;
}
.headerShadow {
padding: 2.0em 0em 0.7em 1em;
}
.headerForeground {
padding: 2.0em 0em 0.7em 1em;
}
.siteTitle {
font-size: 3.0em;
}
.siteSubtitle {
font-size: 1.2em;
}
.subtitle {
font-size: 0.9em;
}
/*}}}*/
/*{{{*/
h1,h2,h3,h4,h5 {
color: #1c411c;
background: #8bd78b;
}
.button {
border: 0px;
}
.header {
background: #8bd78b;
}
.shadow .title {
color: #fff;
}
.title {
color: #1c411c;
background: #8bd78b;
}
.viewer th {
background: #996;
}
.viewer pre {
border: 1px solid #6a6;
background: #cfc;
font-size:1.1em;
font-family: Helvetica, Arial;
}
.viewer thead td {
background: #8d8;
}
/*}}}*/
/*{{{*/
.viewer thead td {
background: #8d8;
}
.viewer tfoot td {
background: #bd6;
}
.MyStyle {
background: #aff;
}
/*}}}*/
Canvasの指定方法
|!ALL|すべてのCanvas|
|!BACK|バックグラウンドのCanvas|
|!FRONT|表示中のCanvas|
※省略時にはBACKが指定される
Type the text for 'Psychlops Manual'
!! Psychlopsの基本
* [[Tips: OpenGL 1.4に対応した一般向けビデオチップ]]
* [[Tips: OS環境による関数名の衝突について]]
* [[Tips: ガンマ補正]]
* [[Tips: Canvasとリフレッシュレートの詳細]]
* [[Tips: Canvas::flip()とコマ落ち]]
* [[Tips: CanvasクラスとDisplayクラス]]
* [[Tips: Canvasを宣言しないと動作しない命令群]]
* [[Tips: Image使用上の注意]]
* [[Tips: 時間精度が必要なプログラムを実行するとき]]
* [[Tips: プログラムが暴走したとき]]
* [[Tips: Xcodeで実行時エラーが起きるとフリーズする]]
* [[Tips: intどうしの比率の計算がおかしい]]
* [[Tips: 機器間の同期]]
* [[Tips: Psychlopsにおけるキーボードマウスの入出力検出ルーチン]]
* [[Tips: トリガーとアナログ入力]]
!! Psychlopsの応用
* [[Tips: Shapeクラスの概要]]
* [[Tips: Strokeクラスの概要]]
* [[Tips: ランダムドットステレオグラムの描画コード]]
* [[Tips: Rectangleの配列を用いたドットパターンの描画例]]
* [[Tips: アルファ混色の活用]]
* [[Tips: プログラムを複数のファイルに分割する]]
* [[Tips: QUESTの実装]]
* [[Tips: 自分の機器の描画性能を確かめる]]
* [[Tips: 独自のクラスを作る]]
!! 他ライブラリの命令を使用した機能の補完
* [[Tips: ビープを出す]]
* [[Tips: OpenGL命令の混ぜ方]]
* [[Tips: 画像のフィルタリング処理]]
* [[Tips: トリガーとアナログ入力2-外部機器を利用した例-]]
* [[Tips: Bits++の利用]]
Cambridge Research Systems社Bits++は14bit DACを備えるアナログ画像信号出力用機器で、DVIとUSBにつなぐだけで機能し、特殊な関数などを使う必要はありません。従って、Psychlopsの命令系と問題なく結合することが可能です^^*1^^。 Bits++の動作モード別に説明します。
公式サイト: [[http://www.crsltd.com/catalog/bits++/modes.html]]
なお、Bits++を利用する場合、Psychlops側の一切の[[ガンマ補正]]機能は以下の命令で無効化し、LUTテーブルを管理するなどの方法で自力で補正をかけてください。
{{{
Color::setGammaValue(1,1,1);
}}}
*1 現時点(2008/3/1現在)でBits++で扱える3つのモード(basic/mono/color)のうちで、実際に動作することを確認しているのはmonoモード(下記参照)だけですが、他の2つのモードについても、仕様上は問題なく動作をすると考えられます。この2モードでプログラムコードを実装された方がいらっしゃいましたら、ぜひPsychlops開発フォーラム(http://psychlops.l.u-tokyo.ac.jp/?forum)までご一報ください。
! BITS++ mono
DVI出力のうち、赤8bit+緑6bitを合成して14bitの輝度値として解釈します。 → 全色14bitで、連動します。青8bitについては別に指定したパレットオーバーレイとして動作すると記述されていますが、詳細についてはまだわかりません。使用法としては、Imageであれば、RGB, BYTEで確保した上で値を16bit整数に変換してビットシフトでRとGに書き込む方法があります(確認済)。
{{{
Color luminance_to_Color(double luminance) {
unsigned short bits_mono = (unsigned short)(65535 * luminance);
unsigned char upper = (unsigned char)(bits_mono >> 8), (unsigned char)(lower = bits_mono && 255) ;
return Color(upper/255.0, lower/255.0, 0);
}
Display::pix(1,1,luminance_to_Color(0.567));
}}}
! BITS++ basic(未確認)
単純にDVI出力をトラップし、RGB各色をLUTに従って変換して14bitアナログ出力します。 → 各色14 bit中8 bitの色を使うことができます。 Bits++では、(0,0)から横に数ピクセルが特定の値であるとき、 y軸が0のラインのデジタル値をLUT配列として解釈する、というシステムになっています。従って、パレットとなるImageを(0,0)起点で描画すればLUTを書き換えることができます。パレットは配列アクセス演算子 [] を備えるクラスでくるめば扱いやすいかもしれません。このモードを使用するとLUTアニメーションが使えます。
! BITS++ color(未確認)
DVI出力の2ピクセル分の値を合成して各色16bitの値を得ます(実効14bit)。従って、解像度は半分になります。
実験やデモには、各フレーム(コマ)がCanvas.flip(duration)で指定した正しい時間にに出ている必要があります。しかし、実際には正しい時間にコマが表示されないことがあります。この状態をコマ落ちと呼びます。
フレームが正しく表示されない原因は、フレームを提示する時間内に計算が終わらないためです。
Psychlopsでは指定された時間よりも描画の計算時間が大きくなったときは描画終了時以降でもっとも近い垂直同期にあわせて描画内容を更新します。描画途中のちぎれた画像が表示される可能性はありませんが、その代わり指定された時間に表示されない可能性があります。
現在のコンピュータは同時に多数のソフトが起動しており、その計算に処理時間をとられてフレーム落ちする場合があります。これを回避するには、[[Tips: 時間精度が必要なプログラムを実行するとき]]をご覧ください。
CRTは機器によってリフレッシュレートが小数点以下の桁でわずかに異なります(60Hzと表示されていても、厳密には60.2Hzなど小数点以下の桁があります)。
Psychlopsでは、初期化時には小数点以下の桁は無視して確保しますが、正確なタイミングでflipするには、ディスプレイ装置の設定等で小数点以下の桁をあらかじめ調べて、正確な値を指定することをお勧めします。
下記命令群はCanvasの実体が確保されていることを前提に動作します。
!!! Display::に属するすべての命令
事前に確保されたCanvas実体への抜け道命令群です。
事前にCanvas実体を確保することが必要です。
!!! Figure.draw() / Shape.draw(Color)
Image.draw()やRectangle.draw(Color)はDisplay::に属する命令と同様に、事前に確保されたCanvas実体を探索して実行する抜け道命令です。
事前にCanvas実体を確保することが必要です。
!!! Color::setGammaValue()
この命令はハードウェアガンマを設定するためにCanvas実体を探索します。
Canvas確保前にこの命令を実行しようとすると失敗します。
!!! Independent
デモ用の変数操作コンソールをCanvas上に作ろうとするためにCanvasを探索します。
Canvas確保前にこの命令を実行しようとすると失敗します。
!!! ExperimentalMethods::Demo
Independentと同様ですが、initialize時にCanvasが確保されている必要があります
!!! Widgets::に属するクラスの大半
自身をCanvas上に自動登録しようとするためにCanvasを探索します。
Canvas確保前にこの命令を実行しようとすると失敗します。
Canvasクラスと同様に使えるクラスとして、Displayクラスがあります。
Cの変数は、その変数が定義されている関数やクラス以外からは操作することができません。
{{{
void psychlops_main() {
Canvas display( Canvas::fullscreen );
}
void func() {
display.flip(); // エラー、displayはこの関数からは操作できない
}
}}}
Displayクラスは、最後に宣言したCanvasクラスの別名として機能します。このクラスはどこからでもアクセスすることができますが、Canvasの宣言の代わりはできません。
{{{
void psychlops_main() {
Canvas display( Canvas::fullscreen );
}
void func() {
Display::flip(); // OK
}
}}}
また、この転送はグラフィックボードやマザーボードなどの規格やOSの種類によって速度が違いますが、一般にある程度の時間がかかります。この転送はCanvasの描画内容を画面上へ反映させるよりはずっと大きいもので、かつ信号転送量が大きくなればなるほど、時間が必要になります。
2008年現在の平均的なマシン(iMacやメーカー製のWindowsデスクトップマシン)で、100 Hzの描画に間に合う(つまり10ms以内に転送と描画反映が完了する)Imageのサイズはおおよそ300 x 300 pixel程度でしょう。以上のようなことを考えて、Imageを用いたオフスクリーン描画とCanvasに対する直接の描画を上手く使い分ける必要があります。
また、高速のオフスクリーン転送を実行したい場合には[[Image::quicken()|3.1.4 発展的な内容]]命令が有効な場合があります。詳しくは、関数一覧の[[Image::quicken()|3.1.4 発展的な内容]]の項を参照してください。
ほかのライブラリ、特にOS・コンパイラ標準のライブラリにあるクラスとこの特定のクラスが同じ名前を持っているために、名前が衝突してしまってコンパイルができなくなっていることが考えられます。
このような衝突を起こすことがある命令は以下の通りです。
!! windows.hで問題が生じるシンボル
| !Rectangle | Psychlops::Rectangle |
| !Ellipse | Psychlops::Ellipse |
| !round | Math::round |
| !random | Math::random |
!! MacOSXで問題が生じるシンボル
| !Point | Psychlops::Point |
| !round | Math::round |
| !random | Math::random |
OpenGL 1.4仕様は2002年7月24日に発行されており、基本的にはそれ以降に設計されたビデオチップはこれに対応しています。
http://www.opengl.org/documentation/specs/version1.4/glspec14.pdf
現在対応を確認しておりますのは、
|!開発元|!汎用カード|!OpenGL特殊化カード|
|nVidia|GeForce 6000(NV30)系、FX5000(NV40)系 以降|Quadro FX系|
|AMD(旧ATI)|RADEON 8000(R200)以降、X系|FireGL 8000以降、X, T, Z, V系|
|Intel|945チップセット(GMA950)以降(915でも一応の動作確認はあり)| |
|VIA(S3 Graphics)|DeltaChrome(S8)以降| |
|SiS|SiS 671以降| |
ただし2008年3月現在で以下のマシンにおける動作不良を確認しております。
これらのマシンについてはドライバレベルでのOpenGL動作の改変が行われているようです。
現在のところ対応は未定です。また、今後の改良のため、動作不良等がございましたら、ぜひ[[Psychlops WIKI Trouble Report|http://psychlops.l.u-tokyo.ac.jp/?Trouble+Report]]までご報告をお願いいたします。
|!製造会社名|!機種名|!搭載ビデオチップ|!動作不良の内容|!暫定的な回避策|
|SONY|Vaio Type-T VGN-TZ92S|Intel 945GM|垂直同期を待たずに画面の描画が行われてしまう|未発見|
|Apple|Mac Pro MA463J/A |Radeon X1900XT(他ビデオボードのものについては未検証) |一定以下の負荷しかない描画内容が画面に反映されないことがある|各フレームの描画命令の前に大きなサイズ(全画面)のRectangleを1~数枚表示する|
より新しい情報は、 [[Psychlops WIKI 動作環境 |http://psychlops.l.u-tokyo.ac.jp/?Environment]]に随時反映されます。
また、これらの動作改変はOpenGLを用いたソフトウェア全般で起こっている可能性があります。これらの他のアプリケーションの情報についてもよろしければ[[Psychlops WIKI Trouble Report|http://psychlops.l.u-tokyo.ac.jp/?Trouble+Report]]に情報をあげていただきますよう、お願い申し上げます。
現在Psychlopsの描画命令形はOpenGLのみで行われており、OpenGLの描画命令を混ぜることができます。この際、いくつかの注意が必要です。
* 回転(glRotate)と拡大縮小(glScale)はほとんどのビデオカードで近似値を使うよう実装されており、ピクセル単位での正しい計算ができなくなりジャギやモアレの原因になります。これを了解した上でお使いください。
* glRotateとglScaleはPsychlops命令形の原点座標を中心に行われます。これらの命令を使った上で表示位置を指定する場合は、回転中心からの相対座標に描画した後、glTranslateをご使用ください。
* Image型はquicken()を適用しないと使うことができません。
Imageを回転させるサンプル(モアレが発生していることをお確かめください)
{{{
#include <psychlops.h>
using namespace Psychlops; // Initially developed with Psychlops Win32 20070313a
void psychlops_main() {
Canvas display(Canvas::fullscreen);
// Internal Image
Image img(100, 100);
Color col;
for(int i=0; i<100; i++) {
for(int j=0; j<100; j++) {
col.set(0.25+sin(i/10.0)/4 + 0.25+sin(j/10.0)/4);
img.pix(i,j,col);
}
}
// External Image File
/*
Image img;
img.load("sample.png");
*/
img.quicken();
double orientation = 0.0;
while(!Input::get(Keyboard::esc)) {
display.clear(0.5);
orientation += PI/10;
glPushMatrix();
glTranslated(display.getHcenter(),display.getVcenter(),0);
glRotated(orientation,0,0,1);
img.draw();
glPopMatrix();
display.flip();
}
}
}}}
Psychlopsのキーボード・マウスの入力監視は、主のプログラムとは独立に自動的に一定の間隔(マシンに依存しますが、通常0.1ミリ秒程度)で行われています。
監視ルーチンは各キーについてPushed, Pressed, Releasedのイベントがあったかどうかの表を内部に保持しています。
監視ルーチンではまず入力デバイスから送られてくるPushed, Releasedのイベントを監視し、イベントがあれば当該キーの当該イベントの表に記録します。この記録はInput::refresh()をするまで残ります。次に、PushedされてからReleasedされるまでの間、Pressedであると表に書き込みます。これはrefreshしなくても常時更新されています。
本来はPressedのデータを基本に取得すべきなのですが、近年のコンピュータではUSB化やOSのセキュリティ強化のためにイベント依存のデータしか取得できないようになっており、このような形式になっています。
マウスの座標系に関してはMac OS X、Windowsともに常時取得・設定が可能です。
現在、多くのUSBキーボード・マウスでは、コンピュータに転送する時間精度は1 msより悪いものとなっています。このため、PsychlopsのKeyboardやMouse命令は、精密な反応時間の取得(数 ms以下のオーダーの精度が必要な反応時間取得)には向きません(ただし、この転送の時間ノイズは数多くの試行を平均加算することで除去できる可能性はあります)。ただし、この値は表示装置の更新間隔(フレームレート)よりは十分に短いため、キーボードやマウスの反応を表示に反映する場合には最悪でも1フレーム遅れる程度にとどまります
QUESTは上下法の一種です。何らかの閾値を測定する場合に恒常法よりも効率的に測定することができます。最尤法とベイズ推定を用いて閾値を推定するのが特徴です。今回は、Psychlopsに追加で組み込めるQUESTのサンプルを実行してみます。
! ダウンロード
まず最初に、[[QUEST Sample|http://satolab.l.u-tokyo.ac.jp/~psychlops_site/download/BasicSamples/QUEST_Sample.zip]]をダウンロードしてください。
圧縮ファイルを展開すると、3つのファイルが生成されます。
* QUEST.h
* QUEST.cpp
QUESTの本体が書かれています。XcodeやReloで新規プロジェクトを作成した後、以上の2ファイルをプロジェクトと同じフォルダにおいてください。次に、[[プロジェクトにこのプログラムを追加|Tips: プログラムを複数のファイルに分割する]]してください
* ~QUESTDemo.cpp
上記QUESTを呼び出してコントラスト閾値測定を行うデモプログラムです。新しいプロジェクトのプログラムにコピーして貼り付けてください。
! QUEST使用の大まかな流れ
このQUESTの実装では、QUEST用の確率表を内部で作成し、それをユーザプログラムからメソッドを呼び出して更新していくモデルをとります。
[img[image/tips/QUEST.png]]
! 初期化
QUESTでは様々な事前情報が必要です。
* 閾値は大体どのくらいにあるか
* その閾値の推測の確からしさはどの程度か
* QUESTで求める閾値は何%に設定するのか
* 被験者がボタンを押し間違える確率はどの程度ぐらいか
* チャンスレベルは何%か
といった情報が必要です。これを実験を始める前に設定します。
{{{
QUEST(double priorEstimatedThreshold, double logPriorSD, double thresholdProbability,
double slope, double errorRate, double chanceLevel,
Range thresholdRange, double logStep = 0.005, Measure placementMeasure=MEAN);
}}}
|!QUEST()|QUEST(double priorEstimatedThreshold, double logPriorSD, double thresholdProbability, double slope, double errorRate, double chanceLevel, Range thresholdRange, double logStep, Measure placementMeasure);|QUEST進行用のクラスを作ります|>|
|~|~|double priorEstimatedThreshold|閾値が大体どのくらいにあるかという推測値です。|
|~|~|double priorSD|推測値の確かさ。閾値が推測値とあまり離れていない場合は小さな値を、離れているかもしれない場合は大きめの値を代入してください。|
|~|~|double thresholdProbability|閾値とみなす正答率。たとえば2肢強制選択(チャンスレベル0.5)の場合は、正答率0.75が閾値とみなされることが多いです。|
|~|~|double slope|心理物理関数の傾き。傾きが既知の場合はその値を代入してください。傾きがわからない場合は効率は落ちますが大きめ値を代入しておいたほうがよいでしょう。|
|~|~|double errorRate|被験者がボタン押しを間違える確率|>|
|~|~|double chanceLevel|チャンスレベル。2肢強制選択なら0.5を代入してください。|
|~|~|Range thresholdRange|閾値の存在範囲を0以上の範囲で指定|
|~|~|double logStep|ステップの値|
|~|~|Measure placementMeasure|推定値を確率分布の最頻値か平均値にしたがって推定します(MODEかMEAN)|
! 実行
初期化が終われば試行を40-100回ほど繰り返します。
各試行は次のステップで行います。
# ループ開始前に刺激の初期値を得ます。
** 初期値はdouble QUEST::nextTrial()を実行することで最適な値を求めることができます。
** 通常このようにして求められた値をそのまま使います(手を加える例もあります)。
# 刺激を提示し、被験者からの反応を得ます。
# 実験結果をQUESTに与え、次の試行で使う値を決定します。
** 2で提示するとき実際に用いた刺激強度と、被験者が成功したか失敗したかをvoid QUEST::next(double intensity, int result)でQUESTに通知します。成功した場合はresultに1を、失敗した場合は0を渡してください。
# 1へと戻り所定の試行数が終わるまでこれを繰り返します
# 所定の試行数が終了したら最後にfinalEstimate()を呼び出すことで実験の結果、推定された閾値を得ることができます。
|!next()|試行の結果をQUESTに通知し、次の試行で用いる値を返します(下記2関数の合成)|>|
|~|double intensity|前回の試行で使われた刺激強度|
|~|int result|前回の試行の被験者の回答。成功した場合はresultに1を、失敗した場合は0を渡してください。|
|!QUEST::nextTrial()|double nextTrial(void)|次の試行で用いる値を返します|>|
|!QUEST::update()|void update(double intencity, int result)|試行の結果をQUESTに通知し、状態をアップデートします。|>|
|~|~|double intensity|前回の試行で使われた刺激強度|
|~|~|int result|前回の試行の被験者の回答。成功した場合はresultに1を、失敗した場合は0を渡してください。|
|!QUEST::finalEstimate()|double finalEstimate(void)|最終結果を返します|>|
{{{
#include <psychlops.h>
using namespace Psychlops;
#include "QUEST.h"
void psychlops_main() {
//initialize QUEST method
const double estimatedThreshold = 0.05; //estimated threshold, mean of prior pdf
const double pdfSD = 2.0; //standard deviation of prior pdf
const double thresholdProbability = 0.75; //the probability defined as threshold
const double slope = 3.5; //slope of psychometric function
const double errorRate = 0.01; //error rate
const double chanceLevel = 0.5; //chance level
Range contrastRange;
0< contrastRange <=1.0;
QUEST quest(estimatedThreshold, pdfSD, thresholdProbability, slope, errorRate, chanceLevel, contrastRange);
//other variable
double contrast = quest.nextTrial(); //contrast of stimuli at each trial
int result; //result of trial
int trial = 50; //number of trial
// Drawing Tools
Canvas canvas(Canvas::fullscreen);
QuickGabor gabor;
gabor.set(50, 50, 1.0, 0, 0).centering();
int display_frames = 1*canvas.getRefreshRate(); // display gabor for 1 second.
//trial loop
for (int i = 0; i<trial; i++) {
//replace this section to your experiment
// displaying stimuli
for(int i=0; i<display_frames; i++) {
canvas.clear(0.5);
gabor.draw();
canvas.flip();
}
// waiting response
canvas.clear(0.5);
canvas.flip();
while(true) {
if(Input::get(Keyboard::left)) { result = 1; break; } // positive reaction
if(Input::get(Keyboard::right)) { result = 0; break; } // negative reaction
}
//update QUEST
gabor.contrast = quest.next(contrast, result);
}
//final estimate
double threshold = quest.finalEstimate();
double sd = quest.getSD();
}
}}}
! 注意点
QUESTでは心理物理関数にWeibull分布を仮定しています。そのため、心理物理関数がWeibull分布で近似できない場合は使えませんのでご注意ください。
ランダムドットパターンなどの複数のドットを使ったパターンの描画で、
ドット密度や各ドットの運動方向を精密に操作したいときは、
[[3.2節|3.2 オフスクリーンを用いた描画例-ランダムドットパターン-]]で紹介した方法よりは、
Rectangleの配列を用いた方法のほうがプログラミングが容易です。
いかにRectangleの配列を用いたドットパターンの描画の典型的なプログラム例をしめします。
ここでは、Rectangleの配列のうちの一部をランダムな方向に運動させ(Noise)、残りを右方向へ運動させています(Signal)。
{{{
#include <psychlops.h>
using namespace Psychlops;
void psychlops_main() {
Canvas display(Canvas::fullscreen);
// Generic Random Dot Parameters
const int DOT_SIZE = 3;
const int MAX_NUM_DOTS = 1000;
const double speed=3.0;
const double noise_dots_ratio = 0.5;
// Parameters of each dots
Psychlops::Rectangle dot[MAX_NUM_DOTS];
Psychlops::Rectangle canvas_rect(display.getWidth(), display.getHeight());
double direction;
// Randomize Dots' Position
for(int i=0; i<MAX_NUM_DOTS; i++) {
dot[i].set(DOT_SIZE, DOT_SIZE);
dot[i].centering(
Psychlops::random(display.getWidth()),
Psychlops::random(display.getHeight())
);
}
while(!Input::get(Keyboard::esc)) {
display.clear();
// Update noise dots' position
for(int i=0; i<MAX_NUM_DOTS*noise_dots_ratio; i++) {
direction=Psychlops::random(2*PI);
dot[i].shift(speed*cos(direction), speed*sin(direction));
if(!canvas_rect.include(dot[i]))
dot[i].centering(
Psychlops::random(display.getWidth()),
Psychlops::random(display.getHeight())
);
dot[i].draw(Color::white);
}
// Update signal dots' position
for(int i=MAX_NUM_DOTS*noise_dots_ratio; i<MAX_NUM_DOTS; i++) {
dot[i].shift(speed, 0.0);
if(!canvas_rect.include(dot[i])) dot[i].shift(-display.getWidth(), 0.0);
dot[i].draw(Color::white);
}
display.flip(3);
}
}
}}}
Shapeクラスに属するクラスは、デフォルトの描画色や枠線を指定することができます。
このデフォルトの色と線Groupでまとめたりする際にとくに有用です。
これらの指定はShapeクラスのメンバ fill, strokeに対して代入を行うことで実行します。
|!Shape::fill|デフォルトの塗り色です。Colorが代入できます。初期状態では無色です。|
|!Shape::stroke|デフォルトの枠線をStroke型で指定します。初期状態では太さ0の線です。|
枠線を指定ためにはstrokeクラスの変数を宣言して、書式指定を行っておく必要があります。
書式指定には宣言時に指定をする方法とset()命令を使用する方法があります。
これらの詳細については[[Tips: Strokeクラスの概要]]をご覧ください。
{{{
#include <psychlops.h>
using namespace Psychlops;
void psychlops_main() {
Canvas canvas(Canvas::window);
Psychlops::Rectangle rect(100,100);
rect.centering().shift( 100, 0);
rect.stroke.set(Color::red, 5, Stroke::DOTTED);
Shape::Colored<Psychlops::Ellipse> ellipse;
ellipse.set(rect);
ellipse.centering().shift(-100, 0);
ellipse.fill = Color::white;
while(!Keyboard::esc.pushed()) {
canvas.clear();
ellipse.draw();
rect.draw();
canvas.flip();
}
}
}}}
この命令セットのうちでここまで扱わなかったものとしてgetDatum()があります。
|!Point Shape::getDatum()|getDatum(void)|Figureの「基準座標」をPoint型で返します. <br> 基準座標の定義は各クラスによりますが、原則として中心点や起点(図形の左上の点)です|
Strokeクラスは、線の書式を表現するためのクラスです。
描画系の関数にColorの代わりに Stroke構造体を与えることで線を描画します。
!現状の制限
* ImageにStrokeを描画することはできません。
* 線が角ごとに途切れるうえ、円が多角形近似のため、ある程度以上太くしたり破線を指定したりするとアラが目に見えてわかります。
!関数
コンストラクタ, set関数 (Color color, double thick, Stroke::Pattern pattern)
color
線の色です。
thick
線の太さです。現状ピクセル単位になります。
pattern
バターンの種類はSOLID, DASHED, DOTTEDです
コード例
{{{
Stroke solid(Color::red, 1, Stroke::SOLID);
// 宣言と設定を同時に行う方法は関数内でのみ使用できます。
Stroke dashed, dotted;
dashed.set(Color::green, 3, Stroke::DASHED);
dotted.color = Color::blue;
dotted.width = 5;
dotted.pattern = Stroke::DOTTED;
Psychlops::Rectangle rect(100,100);
while(!Keyboard::esc.pushed()) {
canvas.clear();
rect.centering();
rect.shift(-150,0).draw(solid);
rect.shift( 150,0).draw(dashed);
rect.shift( 150,0).draw(dotted);
canvas.flip();
}
}}}
実行結果
[img[image/Stroke_Sample.png]]
Windows VistaでPsychlopsを利用する場合、いくつかの注意が必要です。
!! ビデオカードのドライバがOpenGLをサポートしているか確かめる
Windows Vistaでは、DirectXがOpenGLより優先されたため、OpenGLへのサポートが十分でないビデオカードがあります。2008/2/14現在でWindows VistaでOpenGLが十分にサポートされているビデオカードは以下のとおりです。
* nVidia GeForce 7000番台以降
* AMD(ATI) Radeon Xシリーズ
!! インストーラの実行時に管理者権限で実行する
BCC、SetBCC、Psychlopsのインストーラをダブルクリックしても、何も起きない(ように見える)、あるいは明らかに実行に失敗しているように見える場合があります。その場合、実行ファイルを右クリックして「管理者として実行」をクリックし、管理者のパスワードを入力して実行してください。
[img[Vista|image/Win/VistaBCC_1.png]]
!! Borland C++ Compilerの環境変数を設定する
[[BCCのインストール|1.3 インストール作業(Windows)]]を実行した後、追加で以下の作業を行います。
0. スタートボタン → コントロールパネルを開きます
1. 「環境変数」を検索します
2. 「環境変数を編集」をクリックします
3. 開いたウインドウで、「変数」の中に「PATH」があるかどうかを調べます
4.1. 「PATH」があった場合、それを選んで「編集」ボタンを押します。開いたウィンドウで変数値の末尾に以下のテキストを''追加します''。セミコロンは必要なので落とさないようにしてください。
{{{
; C:\borland\bcc55\Bin;
}}}
4.2. 「PATH」がなかった場合、「新規」ボタンを押します。開いたウィンドウで、変数名に「PATH」、変数値に以下のテキストを書き込みます
{{{
C:\borland\bcc55\Bin;
}}}
[img[Vista|image/Win/VistaBCC_2.png]]
Xcodeで実行時エラーが起きると操作を受け付けなくなる現象があります。これは実際は実行時エラー発生時にデバッガが起動しており、フルスクリーンのために見えないだけです。フリーズはしていません。この現象は以下に例示する方法で回避できます。
! 複数ディスプレイを使用する
複数ディスプレイを使用した場合、Psychlopsは主ディスプレイ(メニューバーのあるディスプレイ)に表示され、副ディスプレイを拘束しません。副ディスプレイにデバッガウィンドウを表示しておくことで、デバッガからのプログラム終了やエラー追跡ができます。
! デバッガの起動を防ぐ
デバッガの起動をとめるには、プロジェクト中の「実行可能ファイル」タブから、
* (プロジェクト名) static
* (プロジェクト名) dynamic
* (プロジェクト名) 10.3
と並んでいるうちの一番上のstaticを右クリックまたはctrl+クリックして、「情報を見る」を選びます。
[img[実行ファイルの情報|image/OSX/Psychlops_OSX_dbg1.png]]
開いたダイアログで「デバッガ」タブを選び、すべてのチェックボックスをオフにします。
[img[実行ファイルの情報|image/OSX/Psychlops_OSX_dbg2.png]]
! 待つ
演算性能によりますが、数分間待つと回復することがあります
C++では、intどうしの割り算を行うと小数点切捨ての整数で帰ってきます。このため、特にfor文のカウント変数と最大値から比率を求めようとするときなどで精度差が出ることがあります。これを回避するには型キャストしなければなりません。また、定数項はなるべく「.0」をつけて強制的に浮動小数点値と解釈させると、その項の演算結果を利用する演算子の結果はすべてdouble型にキャストされます。これを覚えておけば「式はあっているはずなのに何も表示されない」といったバグはずいぶんと減るはずです。
{{{
for(int i=0; i<max; i++) {
Display.pix(i, 0, Color( (double)(i)/max ));
Display.pix(i, 2, Color( i*2.0 / max));
}
}}}
Psychlopsが標準で扱うアルファ値(透明度)は、以下のように定義されています。
{{{
結果 = α×上書き色 + (1-α)×元の色
}}}
したがって、アルファ値を0.5とおくと元の色と上書き色の平均値になります。アルファ合成は合成結果が重み付け平均として表せるときに有効です。これ以外の計算方法でアルファ合成を行いたい場合には[[glBlendFunc命令を使用して直接指定をおこなってください|Tips: OpenGL命令の混ぜ方]]。
アルファ合成はビデオカード上で行われるために高速ですが、[[ガンマ補正|Tips: ガンマ補正]]がハードウェア合成の場合にのみ正常に機能し、ソフトウェアエミュレーションである場合には補正済みの値どうしの平均を行ってしまうために誤った値が表示されます。これを防ぐためには、[[Color::strict_match = true;|Color::strict_match]]を指定してソフトウェアエミュレーションを機能させないようにしてください。
以下のコードを実行すると、横縞のガボール刺激に縦縞のダイナミックノイズがかかった刺激が描画されます。ここでは、縦縞のダイナミックノイズをアルファ合成を用いてガボール刺激に重ね合わせています。[[Tips: 独自のクラスを作る]]で例示されている~QuickGaborもアルファ合成を用いています。
[img[image/tips/Sample_alpha.png]]
{{{
#include <psychlops.h>
using namespace Psychlops;
void psychlops_main() {
Canvas display(Canvas::fullscreen);
Psychlops::Rectangle noise;
double noise_lum;
QuickGabor gabor;
gabor.set(30,30).centering();
gabor.orientation = 90.0; // don't
while(!Input::get(Keyboard::esc)) {
display.clear(0.5);
// draw gabor
gabor.phase += 6.0;
gabor.contrast = sin(PI*gabor.phase/360.0);
gabor.draw();
// draw noise
noise.set(1, display.getHeight()).centering(-1,display.getVcenter());
for(int i=0; i<display.getWidth(); i++) {
noise_lum = Psychlops::random();
// **************draw with alpha-blending*****************************
noise.shift(1,0).draw(Color(noise_lum, noise_lum, noise_lum, 0.5));
}
display.flip();
}
}
}}}
ディスプレイやプリンタなど、コンピュータから利用できる画像表示機器では、明るさが指定した値通りに表示されず、歪みが生じます。多くの場合はこの歪みは指数関数に従います。その指数を'''ガンマ値'''と呼び、表示装置の特性を記述するパラメタになります。CRTの場合、ガンマ値は赤、緑、青の蛍光体の間で独立です。
[img[輝度歪み|image/DisplayGamma.png]]
PsychlopsのColor型はガンマ補正機能を備えています。ガンマ補正にはガンマ値による指定のほかに、テーブル(補正表)による指定方法があります。CRT以外の表示装置の特性は必ずしもガンマ値型指数関数に従うとは限りませんので、液晶やプラズマ、DLPプロジェクタなどではテーブルによる指定を試みる必要があります(番、山本、江島、2006)。
表示装置のガンマ値を指定する
{{{
double gamma_r=1.7, gamma_g=1.6, gamma_b = 0.6;
Color::setGammaValue(gamma_r, gamma_g, gamma_b);
}}}
! ハードウェア補正とソフトウェア補正
Psychlopsのガンマ補正は、標準でハードウェアの調整機能を起動し、それに失敗した場合ソフトウェアによるエミュレーションを試みます。
[img[ソフト補正とハード補正|image/ColorCalibrationModel.png]]
!! ハードウェア補正
最近のビデオ表示機器は、理論値と電気出力値の変換テーブルを内部に保持しており、このテーブルに補正値を書き込むことで補正機能を有効にすることができます。書き込みはDVI接続時でも成功しますが、有効に機能するかはハードウェアに依存します。確認するためには実測が必要です。
ハードウェア補正では、以下のOSのAPI関数を呼び出します。
{{{
* Windows
SetDeviceGammaRamp
* Mac OS X
CGSetDisplayTransferByFormula
CGSetDisplayTransferByTable
}}}
!! ソフトウェア補正
ソフトウェア補正では、Color型とImage型の入出力時に変換関数を適用することで、ハードウェア補正と同様の機能を実現します。Color型とImage型の入出力関数で補正を行い、補正後の値を保存することで実現しています。
ソフトウェア補正はハードウェア補正に比べいくつかの点で劣ります。
* 理論値の変化に対して鋭敏に輝度が変化する部分とそうでない部分が生じます。鋭敏でない部分では輝度変化がつぶれてしまいます(下図)
* ガンマ値をプログラム中で変更した場合、それ以前までにsetされたColor情報は古いガンマ値のエミュレーション結果に従った値が保存されており、新しいガンマ値に従わない誤った色が表示されます。いっさいの警告が出ませんのでご注意ください。
* 変換をCPUで計算するため、速度が低下します。
* ソフトウェア補正で補正テーブルを使用した場合、さらに問題が生じます
** 色指定は通常0~1の実数ですが、補正テーブル適用時は入力値がテーブルの行数にあわせて離散化されます。このため、Color.set()で入力した値とget()で取得した値が異なることがあります。
** 複数の理論値に対して同一の補正値が割り当てられている場合、get()で復元する場合に入力理論値と異なることがあります。
[img[ソフトウェア補正の鋭敏さ|image/SoftwareColorCalibration.png]]
!! 表示装置固有の輝度特性測定時の注意
ハードウェア補正が適用されたPsychlopsを用いて表示装置の特性を測定する場合には、OSが標準で行っている補正(ほとんどの場合非線形ですを外すために、以下の関数を実行してください。
{{{
Color::setGammaValue(1,1,1);
}}}
*この記述はver1.0 (Psychlops20080214)以降での注意です。これ以前のバージョンに使用されているsetGammaValue命令は常にソフトウェア補正を用いているために、OSによる標準の補正を気にする必要はありません。
!! 厳密なガンマ補正
特に厳密な色補正を保証したい時に、ガンマ補正や色指定で値が0以上1以下にならないケースやソフトウェア補正が適用される環境下ではプログラムが停止されるように強制させることができます。以下のコードをpsychlops_main()の冒頭に記述してください。
{{{
Color::strict_match = true;
}}}
! 脚注
番浩志、山本洋紀、江島義道 (2006) "Mcalibrator: MATLAB言語を用いたディスプレイ較正のための統合型GUIソフトウェアの開発" 基礎心理学研究、第24巻、149-161頁
脳波やfMRI研究では電気的トリガを出す必要がありますが、[[Tips: 機器間の同期]]で説明したとおり、機器間の同期を取ることが非常に困難であり、視覚刺激提示装置と電気パルスを同期制御することは難しくなっています。
この問題を確実に解決する方法としては、画面の輝度をフォトトランジスタで電気信号に変換する方法があります。フォトトランジスタの照度に対する反応はきわめて高い時間精度を持つため、輝度の状態を得るには適しているといえます。
一般的な電気信号入出力装置は電流ではなく電圧を信号として扱いますので、フォトトランジスタの照度への反応を電圧の変化として取り出す回路が必要です。一般的な回路図を以下に示します。
[img[image/Tips/phototrigger.png]]
フォトトランジスタを吸盤などに埋め込んで画面に設置できるようにして、フォトトランジスタに直列につないだ抵抗器の両端の電圧を電圧計に入力します。
* たとえばCRTの場合、陰極線が画面を走査するには一定の時間が必要です。したがって、画面上部と画面下部では輝度の変化する時間に数ミリ秒の差があります。
* 電圧の範囲は、測定照度、フォトトランジスタの特性、抵抗器の抵抗値、によって決まります。一般的には、抵抗を変えることで必要な電圧範囲になるようにします。
* 刺激提示画面上に電圧計を設置するのが難しい場合、マルチディスプレイを用いて2画面に同じ画像を提示し、実験に使わないほうの画面でトリガーをとる方法があります。この方法では、画面間の同期はとれていませんが、垂直同期間隔は一定です。あらかじめ両画面の同期の時間差を測定しておくことで、正確な同期タイミングを算出することが可能です。
脳波実験等では参照トリガ電圧が必要になることがありますが、近年のコンピュータでは、電気的外部入出力端子(~RS-232Cなど)を持つものが少なく利用しにくくなっています。そこで今回は、比較的容易に入手可能なNational Instruments社製[[USB-6009|http://sine.ni.com/nips/cds/view/p/lang/en/nid/14605]]を用いてアナログ入出力をしてみます。
~USB-6009は電圧データ入出力用機器です。
* アナログ入力 4 ch、アナログ出力 1 ch
* 時間分解能:入力 48 kHz 出力 150 kHz
* 電圧分解能:入力 14 bit 出力 12 bit
と、心理物理学には十分な性能を持っています。価格は35000円程度で、電圧入出力機器としては比較的安価に入手できます。
実際に実験に使う上では、他の入出力機器との時間同期を気にする必要があります。
* 一般的注意
** USBにはキーボード・マウス等、最低限のものしかつながないでください。USBメモリ・ハードディスク・USBオーディオ・Webカメラ等、大量のデータを転送するものとの併用は危険です。
* 入力
** データ取得までの時間遅れは、少なくとも10ms以下程度に抑えられています。したがって、入力を元に画像を制御する用途には用いることができます。
** 入力は4ch同時に取ることができるので、画面のトリガと反応ボックス等を組み合わせて入力することで、入力した機器間で厳密な同期をとることができます。
* 出力
** データを~NI-6009に送信した後、~USB-6009のデジタル入力端子に外部から別のトリガ信号を入力することで、そのトリガに同期してデータを出力することができます。
*** たとえば、VGAの垂直同期信号をデジタル入力端子に誘導しておくと、任意の時点で送信したアナログ出力データを次のflipに同期して出す、といったことが可能になります。
今回は、もっとも簡単なサンプルとして、~USB-6009をひとつ接続し、10ms程度の精度で「電圧値を得る」「電圧を出す」を単発で行う拡張サンプルを実行してみます。高度な時間制御は行っておりません。なお、このサンプルを応用したプログラムを配布する場合は、配布先の環境でも同様の装置が必要です。
! セットアップ
!! Mac OS XでXcodeを使用する場合
* [[NI-DAQmx Base|http://joule.ni.com/nidu/cds/fn/p/sn/n19:MacOS.MACOS10/sb/navsRel/lang/en?q=NI-DAQmx+Base&x=10&y=7]]ドライバの最新版をダウンロードします。
* ダウンロードしたファイルを実行し、ドライバをインストールします(要再起動)。
* [[NIDAQmxBase_Sample_OSX.zip|http://psychlops.l.u-tokyo.ac.jp/download/BasicSamples/NIDAQmxBase_Sample_OSX.zip]]をダウンロードし、解凍します
* 解凍されてできたフォルダから
** 「~NIDAQmxBase_Ext_Installer」をダブルクリックして実行します(下記動作を行うシェルスクリプトです)
*** 失敗した場合、手動で「nidaqmxbase_ex_sample.cpp」を/Library/Frameworks/Psychlops.framework/Headers/Extentionsにコピーします。
新規プロジェクトを作成し、プロジェクトのプロパティでNI-DAQmx Baseドライバを読み込む設定を行います。
* 「External Frameworks and Libraries」をCtrl+クリックし、追加→既存のフレームワークから「nidaqmxbase.framework」を選択
[img[image/OSX/nidacmxbase_OSX_1.png]]
!! WindowsでBCC5.5 + Reloを使用する場合
まず最初に、必要なファイルのセットアップを行います。
* [[NI-DAQmx Base|http://joule.ni.com/nidu/cds/fn/p/sn/n19:Windows,n23:3478/sb/navsRel/lang/en?q=NI-DAQmx+Base&x=0&y=0]]ドライバの最新版をダウンロードします。
* ダウンロードしたファイルを実行し、ドライバをインストールします(要再起動)。
* [[NIDAQmxBase_Sample_win32.zip|http://psychlops.l.u-tokyo.ac.jp/download/BasicSamples/NIDAQmxBase_Sample_win32.zip]]をダウンロードし、解凍します
* 解凍されてできたフォルダから、
** 「~NIDAQmxBase対応Psychlops拡張のインストーラ.bat」をダブルクリックして実行します(nidaqmxbase_ex_sample.cppをExtentionフォルダにコピーします。
** 「~NIDAQmxBaseをBCCに対応させる.bat」をダブルクリックして実行します(bccのツールを呼び出します・要管理者権限)
新規プロジェクトを作成し、プロジェクトのOptionsダイアログで、NI-DAQmx Baseドライバを読み込む設定を行います。
*「Linker」タブの下部にある「Library」に「nidaqmxbase_bcc.lib」を追加(末尾にスペースをひとつ空けて追加します)
* DirectoriesタブにNI-DAQmx Baseを追加する
** 「Extra Include Paths」に「C:\Program Files\National Instruments\NI-DAQmx Base\Include\」を追加
** 「Extra Library Paths」に「C:\Program Files\National Instruments\NI-DAQmx Base\Lib\」を追加
[img[image/win/nidaqmxbase_Win32_1.png]]
! プログラミング
!! アナログ入力
* NI ~USB-6009 の2番(+)、3番(-)ポートに入力線を接続する
* #include <Extentions/Samples/nidaqmxbase_ex_sample.cpp>を追加
* ~NIDAQmxBase_AnalogInput_Dev1 クラスの変数を作成し、ポートを初期化する
* 変数のget()メソッドを呼び、ポート間の電位差を得る
これで、0Vから5Vまでの電位差を得ることが可能になります。
以下のサンプルでは、中心に長方形が表示され、その縦幅が電圧値(V) * 100 ピクセルとしてリアルタイムに表示されます。
{{{
#include <psychlops.h>
using namespace Psychlops;
#include <Extentions/Samples/nidaqmxbase_ex_sample.cpp>
void psychlops_main() {
Canvas canvas(Canvas::fullscreen);
double rect_size = 100;
Psychlops::Rectangle rect;
rect.centering();
NIDAQmxBase_AnalogInput_Dev1 analog_in;
while(!Input::get(Keyboard::esc)) {
canvas.clear();
rect.resize(rect_size, rect_size * analog_in.get()).draw(Color::white);
canvas.flip();
}
}
}}}
このサンプルをフォトトランジスタと接続して照度に連動する実行例が[[Cliplife|http://cliplife.jp/clip/?content_id=usqdz3cv]]にアップロードされています。
!! アナログ出力
* NI USB-6009 の14番、15番ポートに出力線を接続する
* ~NIDAQmxBase_AnalogOutput_Dev1 クラスの変数を作成し、ポートを初期化する
* 変数のput(double voltage)メソッドを呼び、ポート間の電位差を渡す
これで、0Vから5Vまでの電位差の出力が可能になります。
以下のサンプルでは、マウスが画面最上部にあるとき1V、最下部にあるとき0Vになります。
{{{
#include <psychlops.h>
using namespace Psychlops;
#include <Extentions/nidaqmxbase_ex_sample.cpp>
void psychlops_main() {
Canvas canvas(Canvas::fullscreen);
NIDAQmxBase_AnalogOutput_Dev1 analog_out;
Mouse::show();
while(!Input::get(Keyboard::esc)) {
canvas.clear();
analog_out.put(1.0-1.0*Mouse::y/Display::getHeight());
canvas.flip();
}
}
}}}
このサンプルを電圧計で計測した実行例が[[cliplife|http://cliplife.jp/clip/?content_id=opt044zr]]にアップロードされています。
出力を直接ヘッドホンにつなぐと音が鳴りますが、解像度が150 Hzしかないので精度には期待しないほうがいいでしょう。
精度をまったく気にせず単に音がなればいい場合、システムのビープ音を利用する方法があります。
Windowsの場合、以下の命令でPCのビープ音を発生させます。ただし、PCによっては機械ビープが省略されているものがあります。
{{{
Beep(int 持続時間, int 周波数);
}}}
Mac OS Xの場合、以下の命令でOSの警告音を発生させることができます
{{{
SysBeep(int 持続時間);
}}}
この命令にはいくつかの問題があります
* Apple社より、この命令は将来的に廃止されることが予告されています。
* OSの警告音が1度なるだけで、持続時間の指定にはまったく効果はありません。
将来的には、Psychlopsで機種非依存の命令を追加する予定です。
プログラムの実行中に強制的に終了したい場合があります。このような場合はOSの強制終了コマンドを使ってください。
* MacOSX
** ESC + Option + Command(花記号)キーを同時に押す
* Windows
** Alt + F4キーを同時に押す
** 上記方法で失敗した場合、Ctrl + Alt + Delを同時に押して、タスクマネージャから強制終了する
実験間で共通の処理がある場合、管理を容易にするためにその部分のプログラムを別ファイルに分けて使いまわすことができます。
! 既存のファイルの追加
既存のファイルをプロジェクトに追加する場合は、以下の操作を行います。
!! Xcodeの場合
# 「ループとファイル」の「Sources」を右クリックし、「追加」「既存のファイル」と選ぶ
# 開いたウィンドウでファイルを選択し、追加する
[img[image/osx/Xcode_addFiles.png]]
!! Reloの場合
# プロジェクトのファイル一覧を右クリックし、「Add Files」を押す
# 開いたウィンドウでファイルを選択し、追加する
[img[image/win/Relo_addFiles.png]]
! 新規追加
新規にファイルを作ってプロジェクトに追加する場合は、以下の操作を行います。
!! Xcodeの場合
# 「ループとファイル」の「Sources」を右クリックし、「追加」「新規ファイル」と選ぶ
# ファイル名等を決めて新規ファイルを作成
!! Reloの場合
# プロジェクトのファイル一覧を右クリックし、「New Souce File」を押す
# 新しいタブ「untitled」が開かれているので、保存ボタンを押し、名前と保存場所を決める
下の例ではダイナミックランダムドットステレオグラムを描画します。
スペースキーを押すと、ポーズがかかります。
リターンキーを押すと、対応領域の可視化モードが切り替わります。
エスケープキーを押すと、プログラムが終了します。
{{{
#include <psychlops.h>
using namespace Psychlops;
void psychlops_main()
{
Canvas display(Canvas::fullscreen);
const int BUFFER = 10;
const int whole_size = 200;
const int stereo_size = 100;
double disprity = 10.0;
bool pause = false;
int dotsurface = 0;
int SOA = 5;
Image base[2][BUFFER], stereo[2][BUFFER];
for(int i=0; i<BUFFER; i++) {
for(int j=0; j<2; j++) {
base[j][i].set(whole_size, whole_size);
for(int y=0; y<whole_size; y++) { for(int x=0; x<whole_size; x++) { base[j][i].pix(x,y,Color(Psychlops::random())); }}
}
stereo[0][i].set(stereo_size, stereo_size);
stereo[1][i].set(stereo_size, stereo_size);
for(int y=0; y<stereo_size; y++) { for(int x=0; x<stereo_size; x++) { stereo[0][i].pix(x,y,Color(Psychlops::random()*0.5,0.0,0.0)); }}
for(int y=0; y<stereo_size; y++) { for(int x=0; x<stereo_size; x++) { stereo[1][i].pix(x,y,Color(Psychlops::random())); }}
}
int loop = 0;
while(!Input::get(Keyboard::esc)) {
if(Input::get(Keyboard::spc)) pause = !pause;
if(Input::get(Keyboard::rtn)) dotsurface = 1-dotsurface;
if(!pause){
loop++;
loop %= BUFFER;
}
Display::clear();
base[0][loop].centering().shift(-whole_size, 0).display();
stereo[dotsurface][loop].centering().shift(-whole_size, 0).display();
base[1][loop].centering().shift(whole_size, 0).display();
stereo[dotsurface][loop].centering().shift(whole_size+disprity, 0).display();
Display::msg("Press space key to pause.", 400, 100);
Display::msg("Press return key to switch visualize mode.", 400, 120);
Display::msg("Press esc key to exit.", 400, 140);
Display::flip(SOA);
}
}
}}}
現在のコンピュータは同時に多数のソフトが起動しており、その計算に処理時間をとられてフレーム落ちする場合があります。これを回避するにはいくつかの方法があります。
* いったん再起動する(余計なソフトウェアを強制終了させることができます)
* AppState::setThreadPriority( AppState::HIGH );関数を実行する(今後改名するかもしれません)
それでもコマ落ちする場合は、
* 計算量の少ない等価な計算を行う
** forやwhile内で不必要な繰り返し計算を除去する
** pixの配列を書き込む計算の場合、for文をやめてCanvas.pix(ドット数, 配列)の呼び出しを行う
* Imageが原因ならquicken()の使用を検討する
* より処理速度の速いコンピュータを使用する
などの方法が考えられます。
現代のコンピュータはOSやハードウェア規格のレベルで機器間の同期を保証できないシステムに置き換えられています。このTipsでは、各機器の同期精度について説明します。
! 一般論
現代のコンピュータは、互換性の保持、失敗からの回復、暴走の回避を目的として、さまざまなインターフェースが仮想化され、パケット通信化されています。このため、コンピュータのクロックのみに依存した同期確保方法が利用できなくなっています。
ただし時間管理が必要な機器、たとえばCPUのクロック、ビデオカードの垂直同期信号、サウンドカードの連続的音声出力などはそれぞれ独自のクロック信号により精確な時間管理を行うことができます。これらの機器内部で精確なインターバルを得ることはできます。
しかし、機器間で同期を取ることは困難です。視聴覚時間同期といった信号は生成するのが非常に困難です。機器間で同期を取る方法としては、同期を制御する装置をひとつに絞り、その装置の外部にアナログ的な方法で同期装置を設ける方法が推奨されます。この方法は非常に精確です。
! OS
最近のOS(Windows 2000以降やMac OS X)はプリエンプティブマルチタスク方式と呼ばれる、ひとつのアプリケーションが中央演算装置(CPU)を独占できない方式を採用しています。このため、あるアプリケーションが処理される速度は不定で、周辺機器の割り込みを実時間で拾えるとは限りません。
! ビデオカード
近年のビデオカードはダブルバッファリングとバッファ切り替えの垂直同期信号との同期を行うことが標準化されており、ドライバの設定によって(Windowsでは画面の設定で指定、Mac OS Xでは標準)でその機能を利用できるようになり、描画途中で描画内容の転送が起こることのないように設計されています。Psychlopsではこれらのビデオボードに指示を行い、各描画フレームの垂直同期信号との同期を実現しています。
しかし、バッファ切り替えの行われた正確な時間を知ることはできません。
! キーボード、マウス
キーボード、マウスなどHID(Human Interface Devices)は、一般的にはインタラプト転送と呼ばれる、一定間隔で行われる転送を用いて入力されます。これは比較的よい時間精度を持っていますが、一般的なGUIを持つOSでは、ウィンドウの配置を加味してカーネル等から各アプリケーションにプロセス間通信(時間がかかり、同期が維持されない)を用いてHIDイベント(キーを押した、離したなど)の情報が送られます。このため、アプリケーション側がHIDイベントを受け取るタイミングは同期が保証されません。
多くのOSでは、HIDイベントにそれが発生した時間情報が付加されます。アプリケーションが知ることができるHIDの時間情報はそれに制限されます。
HIDイベントに付加された時間情報が十分に正確であるならば、たとえば押し続けた時間は比較的正確に得ることができます。しかし視覚刺激提示からの反応時間をとることは、視覚刺激提示装置とHIDの同期が必要であり、そのままではできません。
! サウンドカード
一般的なサウンドカードは、同期信号出力に用いるには向いていません。
* 音声信号が途切れないように大き目のバッファを設けているため、数10ms以上のレイテンシがあります。
** ASIOとよばれる規格に対応したドライバ、サウンドカードをセットで使用すると、低レイテンシでの通信ができることがあります。一般に、5ms以下のレイテンシで動きます。
* 信号の正確性があまり求められていないため、エラー訂正が不十分である場合や、通信失敗時に再送出をあきらめる場合があります。
自分で作成した機能(コード)を多数のプロジェクトで使いまわす場合、ユーザ独自の拡張としてクラス化し、Psychlopsのライブラリフォルダ追加すると簡単に利用することができます。
ここでは、例として扇形(パックマン図形)を多角形近似したものをクラス化してみます。このクラスは、以下の動作を行います。
* 扇の半径、開口角、開口方向を指定して、多角形として作成する。
* Rectangleと同じようにshift, centering, drawできるようにする。
クラスの中身はひとまず置いておき、ひとまずこのクラスを拡張として登録してみましょう。テキストエディタ(Windows: メモ帳、MacOSX: Xcode→メニュバー→ファイル→新規ファイル)に下記コードをコピー&ペーストし、「pacman.cpp」という名前をつけて保存します。
{{{
#include <psychlops_lib.h>
using namespace Psychlops;
// Polygonally approximated Pacman(sector) shape
class Pacman {
// Sector Elements
Psychlops::Point center;
Psychlops::Point *vertices;
int num_vertices;
public:
// Initialize
Pacman(double radius, double open_angle_radian, double orientation_radian) {
int end = floor(radius * (1 - Math::mod(open_angle_radian, 2*PI)/(2*PI)));
num_vertices = end+2;
vertices = new Psychlops::Point[num_vertices];
double offset = Math::mod(orientation_radian+open_angle_radian/2.0, 2*PI);
vertices[0].set(0,0);
for(int i=0; i<end; i++) {
vertices[i+1].set(
radius*cos(offset+2.0*((double)i/radius)*PI),
radius*sin(offset+2.0*((double)i/radius)*PI)
);
}
vertices[num_vertices-1].set(
radius*cos(offset+2*PI-open_angle_radian),
radius*sin(offset+2*PI-open_angle_radian)
);
}
~Pacman() {
delete [] vertices;
}
// Other Functions
Pacman& shift(double x, double y) {
center.set(x, y);
for(int i=0; i<num_vertices; i++) vertices[i].shift(x,y);
return *this;
}
Pacman& centering() {
shift(Display::getHcenter()-center.getX(), Display::getVcenter()-center.getY());
return *this;
}
Pacman& draw(Color col) {
Display::polygon(vertices, num_vertices, col);
return *this;
}
};
}}}
次に、pacman.cppをPsychlops拡張フォルダ(以下のフォルダの中の適当な場所)にコピーします。この際、MacOSXやWindows Vistaでは管理者パスワードを求められることがあります。
* C:\Library\Frameworks\Psychlops.framework\Headers\Extentions
* /Library/Frameworks/Psychlops.framework/Headers/Extentions
ここでは「Samples」というフォルダをつくり、ここに置いてみます。
次に、呼び出し側コードで今回作った独自拡張を#incluide指令を使って呼び出します。#include指令のオプションには、/Library/Frameworks/Psychlops.framework/Headers/より後の部分のファイルパスを指定します。
{{{
#include <Extentions/Samples/pacman.cpp>
}}}
{{{
#include <psychlops.h>
using namespace Psychlops;
#include <Extentions/Samples/pacman.cpp>
void psychlops_main() {
Canvas display(Canvas::fullscreen);
Pacman pacman(100, PI/2, 0); // radius: 100 px, opening: 90 deg, orientation: 0 deg (right)
pacman.centering();
while(!Input::get(Keyboard::esc)) {
display.clear();
pacman.draw(Color::red);
display.flip();
}
}
}}}
#includeでPacman拡張を呼び出すことで、いつでもPacman図形を使用することができるようになりました。
!! 上級者向け:ソース配布時の注意
Extentionとして別ファイルに分離したものを配布する際は、必要なすべてのファイルを配布する必要があります。もしExtentionフォルダに入れることが前提のファイルを配布するのであれば、その旨を添付テキスト等に記述し、登録するよう指示しましょう。拡張ではない通常の複数ファイル使用の場合は、プロジェクトへの追加が必要な旨を書きましょう。
!! 上級者向け:複数ファイルから拡張を呼び出す場合
この解説は、ユーザ側のプログラムが単一のファイルになるという初心者向けの状況を想定して書かれています。単一バイナリを生成するのに複数のファイルを使い、それらが同時にひとつの拡張を#includeするような場合は、この方法は使うことができません。このような場合、ヘッダファイルと実装の分離をする必要がありますが、これについては[[Wikipediaの項目|http://ja.wikipedia.org/wiki/%E3%83%98%E3%83%83%E3%83%80%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB]]を参照ください。
!! 上級者向け:クラス設計の解説
クラスを作る際には、
* 保持するデータ
* 自動的な初期化処理と廃棄処理
* 各種命令(メソッド)
の3つを用意します。
簡単な解説については、下記サイトが参考になります。
[[C++入門:クラス|http://wisdom.sakura.ne.jp/programming/cpp/cpp5.html]]
また、psychlops.hの中には真のmain / WinMain関数が入っているため、これを2回以上includeするとコンパイラは本体にも拡張側にもmain関数があるかのようにみなし、衝突してしまいます。これを防ぐために、拡張側ではpsychlops_lib.hをインクルードしてください。
{{{
#include <psychlops_lib.h>
}}}
視覚刺激の中には特定の空間周波数でローパス・ハイパスのフィルタをかけた画像を使用することがあります。現在Psychlops本体ではFFT([[高速フーリエ変換|http://ja.wikipedia.org/wiki/%E9%AB%98%E9%80%9F%E3%83%95%E3%83%BC%E3%83%AA%E3%82%A8%E5%A4%89%E6%8F%9B]])機能を持っていませんが、既存のライブラリと併用することで実現できます。ここでは、MATLABでも採用されている[[FFTW|http://www.fftw.org/]]をPsychlops拡張化して利用してみます。
* FFTWはGPLと有料ライセンスの選択制です。GPLを選択される場合、GPLの通称「感染条項」によりこのライブラリを使用したブログラムはすべてGPLとしなければなりません。このプログラムの配布に当たっては、下記の添付ファイルを必ず添付し、パッケージ作成に必要なソースコードをすべて公開しなければなりません。
! FFTWのPsychlops拡張のインストール
!! Mac OS X向け
# [[FFTW_demo_osx.zip|http://psychlops.l.u-tokyo.ac.jp/download/BasicSamples/FFTW_demo_osx.zip]]をダウンロードし、解答します。
# 「~FFTW-Installer」をダブルクリックして拡張ををインストールします(要管理者権限)。
#* インストール時にはTerminalが開き、ユーザのパスワードを聞かれますので、入力してください。
次に、拡張を使ったプロジェクトを作成します。
# 新しくプロジェクトを作成します。
# 「External Frameworks and Libraries」を右クリックし、「追加」→「既存のファイルの追加」をクリックします
## ファイル選択ウィンドウが開いたら、右上の検索ボックスで以下のパスを''手打ちしてください''(コピー&ペースト不可)
##* /usr/local/lib/
## 開いたフォルダで以下のファイルを選択してください。リンクするライブラリ一覧に追加されます。
##* libfftw3.a
# デモを実行する場合、~FilterDemo.cppの内容をコピー&ペーストします
# ビルドして実行してみましょう。
[img[Image/OSX/Xcode_addLib1.png]]
[img[Image/OSX/Xcode_addLib2.png]]
!! Windows向け
# [[FFTW_demo_win.zip|http://psychlops.l.u-tokyo.ac.jp/download/BasicSamples/FFTW_demo_win.zip]]をダウンロードし、解凍します。
# 「FFTWのPsychlops拡張インストーラ.bat」を使って拡張ををインストールします(要管理者権限)。
次に、拡張を使ったプロジェクトを作成します。
# 新しくプロジェクトを作成します。
# いったんテンプレートをビルドします。
# 作成された実行ファイルと同じフォルダに「libfftw3-3.dll」をコピーします
# プロジェクトのオプションを開き、リンクするライブラリに「libfftw3-3_bcc.lib」を加えます(Codeblocks+gccであれば「libfftw3-3.def」を加えます)
# デモを実行する場合、~FilterDemo.cppの内容をコピー&ペーストします
# ビルドして実行してみましょう。
[img[Image/Win/Relo_addLib_1.png]]
[img[Image/Win/Relo_addLib_2.png]]
! 使用法
このサンプルでは、FFTWを用いてローパスフィルタを作成するための関数群が用意されています。配列の大きさが奇数の配列に対しては適用しないでください。
!関数一覧
!! フィルタリング関数
|!Matrix makeLowPassFilter()|makeLowPassFilter(int height, int width, double frequency)|高さheight, 幅width, カットオフ周波数がfrequencyのローパスフィルターとなるMatrixを作成します。|
|~|~|int height: ローパスフィルタの高さ|
|~|~|int width: ローパスフィルタの幅|
|~|~|double frequency: カットオフ周波数|
内部では、カットオフより内側が1、カットオフより外側が0の行列を作成します。
|!Matrix filterImage()|filterImage(Matrix image, Matrix filter)|imageに対してfilterで指定した行列を元にフィルタリングを適用します。|
|~|~|Matrix image: フィルタをかける元画像|
|~|~|Matrix filter: 適用するフィルタ|
内部では、
# imageにFFTをかけ、周波数表現を作る
# 周波数表現でフィルタを要素ごとに乗算する
# フィルタリング後の周波数表現を逆FFTする
という作業が行われています。
[img[image/tips/sample_FFTW.png]]
!! FFT・逆FFT
2次元のFFTを実行します。ただし、fft2やifft2で使う配列は行および列が偶数である必要があります。奇数の場合は正常に動作しない可能性があります。
fft2を用いてFFTを行った後の行列は中心に直流成分が存在し、周辺部に高周波数成分が存在します。行列の大きさがMatrix(rows, cols)の場合、直流成分はMatrix(rows / 2 + 1, cols / 2 + 1)に存在します。
周波数分解能Δf(Matrixの要素が1つずれた場合にどれだけ周波数が変化するか)は、通常の視覚実験においてΔfは1 / 画像の大きさ(視角)となります。
FFTにおけるΔfはサンプリング周波数 / データ数です。視覚実験においてはサンプリング周波数は視角1度が何ピクセルかに相当します。データ数は画像の高さもしくは幅のピクセル数となります。画像の高さや幅(ピクセル数)はサンプリング周波数に画像の高さや幅(視角)をかけたものに等しくなります。このことから通常の視覚実験においてΔfは1 / 画像の大きさ(視角)となります。
|!void fft2()|fft2(Matrix src, Matrix dstReal, Matrix dstImaginary)|2次元のフーリエ変換を実行します。|
|~|~|Matrix src:フーリエ変換を適用する配列。|
|~|~|Matrix dstReal:フーリエ変換後の配列の実部が代入されます。|
|~|~|Matrix dstImaginary:フーリエ変換後の配列の虚部が代入されます。|
|!void fft2()|fft2(Matrix srcReal, Matrix srcImaginary, Matrix dstReal, Matrix dstImaginary)|2次元のフーリエ変換を実行します。|
|~|~|Matrix srcReal:フーリエ変換を適用する配列の実部。|
|~|~|Matrix srcImaginary:フーリエ変換を適用する配列の虚部。|
|~|~|Matrix dstReal:フーリエ変換後の配列の実部が代入されます。|
|~|~|Matrix dstImaginary:フーリエ変換後の配列の虚部が代入されます。|
実行後の配列は中心部に低周波数領域があります。
|!void ifft2()|ifft2(Matrix srcReal, const Matrix srcImaginary, Matrix dst)|2次元の逆フーリエ変換を実行します。|
|~|~|Matrix srcReal:逆フーリエ変換を適用する配列の実部。|
|~|~|Matrix srcImaginary:逆フーリエ変換後の配列の虚部。|
|~|~|Matrix dst:逆フーリエ変換後の配列の絶対値が代入されます。|
|!void ifft2()|ifft2(Matrix srcReal, Matrix srcImaginary, Matrix dstReal, Matrix dstImaginary)|2次元の逆フーリエ変換を実行します。|
|~|~|Matrix srcReal:逆フーリエ変換を適用する配列の実部。|
|~|~|Matrix srcImaginary:逆フーリエ変換後の配列の虚部。|
|~|~|Matrix dstReal:逆フーリエ変換後の配列の実部が代入されます。|
|~|~|Matrix dstImaginary:逆フーリエ変換後の配列の虚が代入されます。|
!! 調整用関数
|!Matrix normalize()|normalize(Matrix matrix)|行列の各要素の平均値が0、標準偏差が1となるよう正規化します。|
|~|~|Matrix matrix: 正規化対象の行列|
フィルター処理をしたあとの画像は[0, 1]の範囲に収まらず、表示可能な領域からはみ出してしまいます。そこで表示可能な範囲に収める処理が必要となります。フィルター処理をした後の画像の輝度値の分布は正規分布に従うことが知られています。そのため、一度正規化をします。normalizeはその正規化を行うための関数です。
正規化を行った後はコントラストをかけ、平均輝度を足します。このときのコントラストはマイケルソンコントラストではなく、RMSコントラストです。RMSコントラストが1/3を超えると表示可能な領域からはみ出してしまう点が多くなるのでなるべくこの値を最大値としてください。
最後に、それでも表示可能な領域が残る可能性は存在しますので、それを表示可能な領域に納めます。
|!Matrix makeNoise()|makeNoise(int height, int width)|高さheight、幅widthのホワイトノイズ行列を作成します。|
|~|~|int height: 縦の大きさ|
|~|~|int width: 横の大きさ|
!! 内部用調整関数
以下の関数はfft2やifft2内部で呼び出される関数です。通常ユーザーがこの関数を実行する必要はありません。
|!Matrix fftShift()|fftShift(const Matrix &matrix)|FFTをかけた後の周波数ごとの分布を移動させます。|
|~|~|Matrix matrix: シフトを行う対象を指定します|
FFTWではFFTを行った後の行列は低周波成分が4隅にあり、中心部には高周波成分が存在します。このままではフィルターを作成する際に計算しづらいので、中心部に低周波数成分を、周辺部に高周波数成分が並ぶようにします。そのための関数です。
逆に中心にある低周波数成分を周辺部に、周辺にある高周波数成分を中心にするように並べ替えることもできます。
|!void fftExecute()|fftExecute(const Matrix &srcReal, const Matrix &srcImaginary, Matrix &dstReal, Matrix &dstImaginary, fftw_complex *input, fftw_complex *output, const fftw_plan &plan)|フーリエ変換を実行します。|
|~|~|Matrix srcReal:フーリエ変換を適用する配列の実部。|
|~|~|Matrix srcImaginary:フーリエ変換を適用する配列の虚部。|
|~|~|Matrix dstReal:フーリエ変換後の配列の実部が代入されます。|
|~|~|Matrix dstImaginary:フーリエ変換後の配列の虚部が代入されます。|
|~|~|fftw_complex *input: FFTW作業用の変数|
|~|~|fftw_complex *output: FFTW作業用の変数|
|~|~|fftw_plan plan: FFTW作業用の変数|
Psychlopsの実行速度は環境によって異なります。現在の実行環境での実行速度を知りたい場合は、簡易に計測するプログラムを用意しましたので、これを実行して調べることができます。下記ファイルをダウンロードし、コンパイルして実行してみてください。
[[Benchmark.cpp|http://psychlops.l.u-tokyo.ac.jp/download/BasicSamples/Benchmark.cpp]]
2~5分程度のテストを行い、各種命令の「フレームが落ちずに実行できる最大処理能力」を測ります。結果は終了後に画面に表示されるほか、実行ファイルと同じ場所に結果ファイルを出力します(Mac OS Xの場合、プロジェクトフォルダ/build/Release/)。
現在のバージョンでは4つのテストを行います。各テストの項目のうち、
* 上「今回のテストで使われたリフレッシュレートで、1フレームで表示できる最大数」
* 下「100 Hzで正規化した場合に、1フレームで表示できる最大サイズ」(この値が1000の場合、100 Hz時に1000x1000ピクセルの画像が1枚表示可能ということを示します。)
!!! Pixel test
Canvas.pix命令の処理能力を計測します。
GPUの頂点処理能力と、CPUからGPUに命令を出す帯域幅が主な律速要因になります。
!!! Rectangle test
Canvas.rect命令の処理能力を計測します。
GPUのピクセル処理能力がが主な律速要因になります。
!!! Image test
オフスクリーン(Image.draw)の処理能力を調べます。
メインメモリからグラフィックカードへImageを転送する帯域幅が主な律速要因になります。
!!! VRAM Image test
Image.quicken()されたオフスクリーン画像の処理能力を計測します。
GPUのピクセル処理能力がが主な律速要因になります。
実験を行ううえでの目安としてください。
また、計測結果には10%前後の誤差があります。
PsychlopsではデフォルトのCanvasに対して(インスタンス名を無視して)描画を強制的に行うDisplayクラスが用意されています。
現バージョンでのPsychlopsはCanvasを同時に1つしか確保できない仕様となっているので(つまり宣言したCanvasインスタンスは常にデフォルトになる)、Displayクラスを使って描画を行っても、インスタンス名を使って描画を行った場合と同じ結果が得られます。たとえば、以下の2つのプログラムは同じ結果になります。
{{{
<インスタンス名を使用したソース>
#include <psychlops.h>
using namespace Psychlops;
void psychlops_main() {
Canvas sampleB(Canvas::fullscreen);
sampleB.clear(Color::blue);
sampleB.flip();
while(!Input::get(Keyboard::spc));
}
}}}
{{{
<Displayクラスを使用したソース>
#include <psychlops.h>
using namespace Psychlops;
void psychlops_main() {
Canvas sampleB(Canvas::fullscreen);
Display::clear(Color::blue);
Display::flip();
while(!Input::get(Keyboard::spc));
}
}}}
また、psychlops_main外の関数で描画を行うときには、Canvasのポインタを渡す必要のないDisplayクラスを使った方法のほうが便利かもしれません。下の例を比べて見てください。
{{{
<インスタンス名を使用したソース>
#include <psychlops.h>
using namespace Psychlops;
void stimulusDraw(Canvas &sample){
sample.clear(Color::blue);
sample.flip();
while(!Input::get(Keyboard::spc));
}
void psychlops_main() {
Canvas sampleB(Canvas::fullscreen);
stimulusDraw(sampleB);
}
}}}
{{{
<Displayクラスを使用したソース>
#include <psychlops.h>
using namespace Psychlops;
void stimulusDraw(){
Display::clear(Color::blue);
Display::flip();
while(!Input::get(Keyboard::spc));
}
void psychlops_main() {
Canvas sampleB(Canvas::fullscreen);
stimulusDraw();
}
}}}
* 2.1.1節のCanvasの宣言をマルチスクリーンに対応したバージョンに書き換え
[[2.3 文字列の描画]]
* [[2.3.1 Canvas::msg()命令単体での文字列描画]]
* [[2.3.2 Lettersクラスを用いた文字列描画]]
* [[3.5 文字列のレンダリングの仕様と関連情報]]
* 3.3.1節にCanvas::toの注意事項とImage::release()についての説明を追加。
[[3.3.3 複数の画像ファイルからアニメーションを作成する]]
[[4. 複雑な描画を行う2(複数オブジェクトのグループ化と回転・拡大縮小)]]
[[8. 透明度を使って刺激描画を高速化する]]
[[9. アナログ入出力を行う]]
! [[Tips]]
* [[Tips: Shapeクラスの概要]]
* [[Tips: Strokeクラスの概要]]
* [[Tips: Canvasを宣言しないと動作しない命令群]]
* [[Tips: OS環境による関数名の衝突について]]
* [[Tips: ガンマ補正]]
setGammaTable(std::vector<double> Cr, std::vector<double> Cg, std::vector<double> Cb)のコード例
![[関数一覧]]
* [[Canvasの宣言]]
* [[Image::to()]]
* [[Displayの指定値]]
* [[CanvasMode]]
! [[関数逆引きとQ&A]]
* [[Canvas::pix(double, double, double)という命令はコンパイルできないのですか?]]
* [[止まった絵を描画するときCanvas.flip()を単独でwhileループの中に書くと、画面がちらつきます。]]
* [[特定の命令に対してコンパイル時にエラーが出る]]
/***
CSSカスタマイズ
***/
body {
background: #f0efc0;
}
#mainMenu{
background: #f0efc0;
border: 2px dotted #6a6;
}
/*{{{*/
h1,h2,h3,h4,h5 {
padding-top: 0.3em;
}
.headerShadow {
padding: 2.0em 0em 0.7em 1em;
}
.headerForeground {
padding: 2.0em 0em 0.7em 1em;
}
.siteTitle {
font-size: 3.0em;
}
.siteSubtitle {
font-size: 1.2em;
}
.subtitle {
font-size: 0.9em;
}
/*}}}*/
/*{{{*/
h1,h2,h3,h4,h5 {
color: #fff;
background: #4a7;
}
.button {
border: 0px;
}
.header {
background: #084;
}
.shadow .title {
color: #fff;
}
.title {
color: #fff;
background: #084;
}
.viewer th {
background: #996;
}
.viewer pre {
border: 1px solid #6a6;
background: #cfc;
font-size:0.9em
}
.viewer thead td {
background: #8d8;
}
/*}}}*/
/*{{{*/
.viewer thead td {
background: #8d8;
}
.viewer tfoot td {
background: #bd6;
}
.MyStyle {
background: #aff;
}
/*}}}*/
body{
margin: 0 auto;
padding:0;
width: 100%;
font: 80% arial,sans-serif;
}
#mainMenu {
position: absolute;
width: 16%;
text-align:left;
padding: 0em 1em 0em 1em;
background-color: #eee;
color:#cccccc;
font-size: .9em;
}
#sidebar {
position: absolute;
right: 10px;
width: 10%;
font-size: .9em;
}
.headerShadow,
.headerForeground {
padding: 1em;
}
.viewer blockquote {
font-size: 8pt;
line-height: 150%;
border-left: 3px solid #666666;
padding-left: 1.0em;
margin-left: 2.5em;
}
.siteTitle {
font-size: 24pt;
}
.siteSubtitle {
font-size: 12pt;
}
.title {
color:#55221f;
font-size: 1.5 em;
padding-left:0.1em;
margin-bottom:10px;
margin-top:4px;
border-bottom:2px solid #ccc;
}
http://hsj.jp/junknews/archives/tiddlywiki_susume.html
図の引用
{{{
[img[image/canvasrect.png]]
}}}
このマニュアルはTiddelyWikiによって作成されています。
今、このウィンドウが開いたように、見出しをダブルクリックすると、該当する記事のウィンドウが自動的に開きます。必要なくなった(見終わった)ウィンドウは、見出しの左上のコマンドから"close"をクリックすれば閉じることができます。
Editを押すと内容を改変して、ご自分でコメントをメモすることができますが、改変した部分は元には戻せないのでご注意ください。
また、画面左端にメニューバーから各記事にアクセスしたり記事の内容を検索することもできます。
* [[Psychlopsについて開発者に質問するには?]]
* [[素晴らしいデモプログラムを作成したので、公開したい]]
* [[Psychlopsを既存・自作のC/C++ライブラリと併用することができますか?]]
|GRAY|グレースケールの輝度値|
|RGB|色の三原色に基づいた赤・緑・青で構成された色|
|RGBA|RGBに透明度が追加された色|
詳しくは[[2.2.5 描画する図形の色を指定する-Colorクラス-]]を参照
!キーボード
|アルファベットキー (a~z)|a~zのまま(小文字)|
|キーボード最上段(1~10) |1|2|3|4|5|6|7|8|9|0|
|~|one|two|three|four|five|six|seven|eight|nine|zero|
|特殊キー|Return|Space|Escape|カーソル上|カーソル下|カーソル左|カーソル右|Shift|Control|Alt|
|~| rtn | spc | esc | up | down | left | right | shift | ctrl | alt |
|テンキー|0|1|2|3|4|5|6|7|8|9|
|~|pad0|pad1|pad2|pad3|pad4|pad5|pad6|pad7|pad8|pad9|
|~|ー|>|+|>|*|>|/|>|Enter|>|.|
|~|padminus|>|padpuls|>|padasterisk|>|padslash|>|padenter|>|padperiod|>|
!マウス
|左ボタン|右ボタン|中ボタン|すべてのボタンのいずれか|
| left | right | middle | any |
{{{
*例1: pushedを使ったソース
#include <psychlops.h>
using namespace Psychlops;
int code, oldcode;
Psychlops::Rectangle rect(60,60), rect2(60,60);
void psychlops_main() {
Canvas display(Canvas::fullscreen);
display.clear(Color::gray);
rect.centering();
Input::refresh();
while(!Input::get(Keyboard::spc)){
oldcode=code;
code=-1;
display.clear(Color::gray);
if(Input::get(Keyboard::c, Keyboard::pushed))code=0; //centering
if(Input::get(Keyboard::left, Keyboard::pushed))code=1;
else if(Input::get(Keyboard::right, Keyboard::pushed))code=2;
switch(code){
case -1: break;
case 0: rect.centering(); break;
case 1: rect.shift(-1,0); break;
case 2: rect.shift(1,0); break;
}
rect2.centering().draw(Psychlops::Color(0.75,0.75,0.75));
rect.draw(Psychlops::Color(1.0,1.0,1.0,0.25));
display.flip();
}
}
}}}
{{{
*例2: pressedを使ったソース
#include <psychlops.h>
using namespace Psychlops;
int code, oldcode;
Psychlops::Rectangle rect(60,60), rect2(60,60);
void psychlops_main() {
Canvas display(Canvas::fullscreen);
display.clear(Color::gray);
rect.centering();
Input::refresh();
while(!Input::get(Keyboard::spc)){
oldcode=code;
code=-1;
display.clear(Color::gray);
if(Input::get(Keyboard::c, Keyboard::pushed))code=0; //centering
if(Input::get(Keyboard::left, Keyboard::pressed))code=1;
else if(Input::get(Keyboard::right, Keyboard::pressed))code=2;
switch(code){
case -1: break;
case 0: rect.centering(); break;
case 1: rect.shift(-1,0); break;
case 2: rect.shift(1,0); break;
}
rect2.centering().draw(Psychlops::Color(0.75,0.75,0.75));
rect.draw(Psychlops::Color(1.0,1.0,1.0,0.25));
display.flip();
}
}
}}}
{{{
*例3: releasedを使ったソース
#include <psychlops.h>
using namespace Psychlops;
int code, oldcode;
Psychlops::Rectangle rect(60,60), rect2(60,60);
void psychlops_main() {
Canvas display(Canvas::fullscreen);
display.clear(Color::gray);
rect.centering();
Input::refresh();
while(!Input::get(Keyboard::spc)){
oldcode=code;
code=-1;
display.clear(Color::gray);
if(Input::get(Keyboard::c, Keyboard::pushed))code=0; //centering
//state "pressed" and "released" Demo
if(Input::get(Keyboard::left, Keyboard::pressed))code=1;
else if(Input::get(Keyboard::right, Keyboard::pressed))code=2;
if(Input::get(Keyboard::left, Keyboard::released))code=3;
else if(Input::get(Keyboard::right, Keyboard::released))code=3;
switch(code){
case -1: break;
case 0: rect.centering(); break;
case 1: rect.shift(-1,0); break;
case 2: rect.shift(1,0); break;
case 3: rect.centering(); break;
}
rect2.centering().draw(Psychlops::Color(0.75,0.75,0.75));
rect.draw(Psychlops::Color(1.0,1.0,1.0,0.25));
display.flip();
}
}
}}}
{{{
*例4 KeyStateの使用例
#include <psychlops.h>
using namespace Psychlops;
double x=0,y=0, dX,dY;
int code, oldcode;
Psychlops::Rectangle rect(60,60), rect2(60,60);
void psychlops_main() {
Canvas display(Canvas::fullscreen);
display.clear(Color::gray);
rect.centering();
dX= display.getHcenter();
dY= display.getVcenter();
x=dX;
y=dY;
Input::refresh();
while(!Input::get(Keyboard::spc)){
oldcode=code;
code=-1;
//state "pushed" Demo
if(Input::get(Keyboard::c, Keyboard::pushed))code=0; //centering
if(Input::get(Keyboard::a, Keyboard::pushed))code=1;
else if(Input::get(Keyboard::s, Keyboard::pushed))code=2;
//state "pressed" Demo
if(Input::get(Keyboard::z, Keyboard::pressed))code=3;
else if(Input::get(Keyboard::x, Keyboard::pressed))code=4;
//state "pressed" and "released" Demo
if(Input::get(Keyboard::q, Keyboard::pressed)){
code=5;
if(oldcode==5||oldcode==7)code=7;
}
else if(Input::get(Keyboard::w, Keyboard::pressed)){
code=6;
if(oldcode==6||oldcode==8)code=8;
}
if(Input::get(Keyboard::q, Keyboard::released))code=9;
else if(Input::get(Keyboard::w, Keyboard::released))code=9;
switch(code){
case -1: break;
case 0: rect.centering(); break;
case 1: rect.shift(-1,0); break;
case 2: rect.shift(1,0); break;
case 3: rect.shift(-1,0); break;
case 4: rect.shift(1,0); break;
case 5: x=ceil(rect.getHcenter()); y=ceil(rect.getVcenter()); rect.shift(-1,0);break;
case 6: x=ceil(rect.getHcenter()); y=ceil(rect.getVcenter()); rect.shift(1,0);break;
case 7: rect.shift(-1,0); break;
case 8: rect.shift(1,0); break;
case 9: rect.centering(x,y); break;
}
display.clear(Color::gray);
//Show variables
display.message("KeyState:", dX-100, dY-120, Color::green);
display.var(code, dX,dY-120, Color::green);
display.message("X:", dX-100, dY-100, Color::white);
display.var(ceil(rect.getHcenter()),dX,dY-100, Color::white);
display.message("Y:", dX-100, dY-80, Color::white);
display.var(ceil(rect.getVcenter()),dX,dY-80, Color::white);
if(code>5){
rect2.centering(x,y).draw(Psychlops::Color(0.75,0.75,0.75));
rect.draw(Psychlops::Color(1.0,1.0,1.0,0.25));
}
else rect.draw(1.0);
display.flip();
}
}
}}}
このプログラム内で使用されているCanvas::message, Canvas::var命令については[[6.1 時間を計測する]]を参照してください。
{{{
*例5 MouseStateの使用例
#include <psychlops.h>
using namespace Psychlops;
double x=0,y=0;
int code, oldcode;
Psychlops::Rectangle rect(60,60), rect2(60,60);
void psychlops_main() {
Canvas display(Canvas::fullscreen);
display.clear(Color::gray);
rect.centering();
Input::refresh();
while(!Input::get(Keyboard::spc)){
oldcode=code;
code=-1;
display.clear(Color::gray);
if(Input::get(Keyboard::c, Keyboard::pushed))code=0; //centering
//state "pressed" and "released" Demo
if(Input::get(Mouse::left, Mouse::pressed))code=1;
else if(Input::get(Mouse::right, Mouse::pressed))code=2;
switch(code){
case -1: break;
case 0: rect.centering(); break;
case 1: rect.shift(-1,0); break;
case 2: rect.shift(1,0); break;
}
rect2.centering().draw(Psychlops::Color(0.75,0.75,0.75));
rect.draw(Psychlops::Color(1.0,1.0,1.0,0.25));
display.flip();
}
}
}}}
{{{
*例6 マウスデモ
#include <psychlops.h>
using namespace Psychlops;
double dcX,dcY, rectcol;
double rectsize=80;
int display_cursor=1, in_rect=0;
Psychlops::Rectangle rect(rectsize*2,rectsize*2);
Psychlops::Range rngx,rngy;
void psychlops_main() {
Canvas display(Canvas::fullscreen);
display.clear(Color::gray);
AppState::setThreadPriority(AppState::HIGH);
rect.centering();
dcX= display.getHcenter();
dcY= display.getVcenter();
Input::refresh();
//Move mouse cursor to the center
Mouse::x=dcX;
Mouse::y=dcY;
while(!Input::get(Keyboard::spc)){
display.clear(Color::gray);
rectcol=0.25;
//set current rect area
rect.getHcenter()-rectsize<rngx<rect.getHcenter()+rectsize;
rect.getVcenter()-rectsize<rngy<rect.getVcenter()+rectsize;
if(display_cursor)Mouse::show();
else Mouse::hide();
if(Input::get(Mouse::right, Mouse::pushed))display_cursor=1-display_cursor;
if(in_rect){
rect.centering(Mouse::x, Mouse::y);
rectcol=1.0;
}
rect.draw(rectcol);
display.flip();
if(Input::get(Mouse::left, Mouse::pushed)){
if (rngx.includes(Mouse::x) && rngy.includes(Mouse::y))in_rect=1;
else in_rect=0;
}
if(Input::get(Mouse::left, Mouse::released)) {
if (rngx.includes(Mouse::x) && rngy.includes(Mouse::y))in_rect=0;
}
}
}
}}}
* [[キーボード/マウスの反応を取得したい|Input::get()]]
* [[キーボード/マウスの反応をリセットしたい|Input::refresh()]]
* [[マウスカーソルを表示したい|Mouse::show()]]
* [[マウスカーソルを非表示にしたい|Mouse::hide()]]
* [[描画画面を取得したい|2.1 描画領域の宣言]]
* [[取得した画面の情報を取得したい|2.1.3 Canvasのメンバ取得と変数の表示]]
* [[画面を特定の色で塗りつぶしたい|2.1.2 Canvasの基本構造と操作]]
* [[画面に点を打ちたい|2.2.1 画面上に点を描画する-Pointクラス1-]]
* [[画面に四角形を書きたい|2.2.3 画面上に四角形を描画する-Rectangleクラス1-]]
* [[画面に円を描きたい|2.2.4 画面上に円を描画する]]
* [[描画色を指定したい|2.2.5 描画する図形の色を指定する-Colorクラス-]]
* [[リアルタイムでは描画できない複雑な図形描画を事前に計算したい|3. 複雑な描画を行う(オフスクリーンへの描画)]]
* [[自然画像を読み込み・表示したい|3.3 画像ファイルの取り扱い]]
* [[図形をXX msecだけ提示したい|2.1.2 Canvasの基本構造と操作]]
* [[時間を正確に計測したい|5.1 時間を計測する]]
* [[キーボードの入力を取得したい|4.1 外部装置(キーボード/マウス)の操作内容を取得する]]
* [[マウス入力を取得したい|4.1 外部装置(キーボード/マウス)の操作内容を取得する]]
* [[配列の中身をファイルに記録したい|4.2 ファイルの入出力]]
* [[特定の命令に対してコンパイル時にエラーが出る]]
*[[簡単な行列演算がしたい|5.3 行列演算を行う]]
*[[配列の要素をランダムに並べ替えたい|Math::shuffle]]
*[[負の値、浮動小数点型に対して剰余を求めたい|Math::mod]]
// Shadow tiddlers for emergencies
config.shadowTiddlers.SideBarOptions = "<<search>><<closeAll>><<permaview>><<newTiddler>><<saveChanges>><<slider chkSliderOptionsPanel OptionsPanel 設定 'TiddlyWikiの設定を変更する。'>>";
config.shadowTiddlers.OptionsPanel = "これらの設定はご使用のブラウザ内に保存されます。\n\n署名として使用するあなたの名前をWikiWord(eg JoeBloggs)の形式で入力してください。\n\n<<option txtUserName>>\n<<option chkSaveBackups>> バックアップ取得\n<<option chkAutoSave>> 自動保存\n<<option chkGenerateAnRssFeed>> RSSファイル生成\n<<option chkRegExpSearch>> 正規表現による検索\n<<option chkCaseSensitiveSearch>> 英文字大小区別検索\n<<option chkAnimate>> アニメーション\n\n[[詳細な設定|AdvancedOptions]]";
config.shadowTiddlers.AdvancedOptions = "<<option chkOpenInNewWindow>> 新しいウィンドウでリンクを開く\n<<option chkSaveEmptyTemplate>> 空のテンプレートファイル(empty.html)を保存する\n<<option chkToggleLinks>> 既に開いているTiddlerをクリックした時に閉じる\n^^(override with Control or other modifier key)^^";
config.shadowTiddlers.SideBarTabs = "<<tabs txtMainTab '更新順' '更新順に表示する' TabTimeline 'タグ' 'タグ一覧' TabTags '詳細' '詳細' TabMore>>";
config.shadowTiddlers.TabTimeline = "<<timeline>>";
config.shadowTiddlers.TabTags = "<<allTags>>";
config.shadowTiddlers.TabMore = "<<tabs txtMoreTab '全部' 'すべてのTiddler' TabMoreAll '定義なし' '定義されていないTiddler一覧' TabMoreMissing 'リンク切れ' 'リンク切れしている単独のTiddler' TabMoreOrphans>>";
config.shadowTiddlers.TabMoreAll = "<<list all>>";
config.shadowTiddlers.TabMoreMissing = "<<list missing>>";
config.shadowTiddlers.TabMoreOrphans = "<<list orphans>>";
// メッセージ
config.messages.customConfigError = "カスタムコンフィグにてエラー発生。 - %0";
config.messages.savedSnapshotError = "保存に失敗しました。";
config.messages.subtitleUnknown = "(不明)";
config.messages.undefinedTiddlerToolTip = "'%0'というTiddlerはまだ存在しません。";
config.messages.externalLinkTooltip = "(外部リンク) %0";
config.messages.noTags = "タグの設定されていないTiddler";
config.messages.notFileUrlError = "変更を保存したい場合、このTiddlyWikiをファイルに保存(ダウンロード)する必要があります。";
config.messages.cantSaveError = "このブラウザでは保存することができません。できればFirefoxを使ってください。";
config.messages.invalidFileError = "元のファイル '%0' は妥当なTiddlyWikiのファイルではありません。";
config.messages.backupSaved = "バックアップファイルを保存しました。";
config.messages.backupFailed = "バックアップファイルの保存に失敗しました。";
config.messages.rssSaved = "RSSファイルを保存しました。";
config.messages.rssFailed = "RSSファイルの保存に失敗しました。";
config.messages.emptySaved = "空のテンプレートファイルを保存しました。";
config.messages.emptyFailed = "空のテンプレートファイルの保存に失敗しました。";
config.messages.mainSaved = "TiddlyWikiファイルを保存しました。";
config.messages.mainFailed = "TiddlyWikiファイルの保存に失敗しました。修正内容は保存されていません。";
config.messages.macroError = "マクロ実行時エラー: '%0'";
config.messages.overwriteWarning = "'%0'というTiddlerはすでに存在します。OKを選択すると上書きします。";
config.messages.unsavedChangesWarning = "警告! 保存されていない変更が存在します。\n\nOKを選択:保存\nCancelを選択:編集内容を破棄";
config.messages.months = ["1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月","12月"];
config.messages.dates.days = ["日", "月", "火", "水", "木", "金", "土"];
// Tiddler表示時ツールバーなど
config.views.wikified.tag.labelNoTags = "no tags";
config.views.wikified.tag.labelTags = "タグ = ";
config.views.wikified.tag.tooltip = "タグ'%0'が設定されたTiddlerを閲覧する。";
config.views.wikified.tag.openAllText = "タグ'%0'のTiddlerをすべて開く。";
config.views.wikified.tag.openAllTooltip = "このTiddlerをすべて開く。";
config.views.wikified.tag.popupNone = "タグ'%0'はこれ以外のTiddlerに設定されていません。";
config.views.wikified.toolbarEdit = "編集";
config.views.wikified.toolbarPermalink = "permalink";
config.views.wikified.toolbarReferences = "参照一覧";
config.views.wikified.toolbarReferences.popupNone = "参照されていません。";
config.views.wikified.defaultText = "'%0'はまだ存在していません。ダブルクリックで作成できます。";
// Tiddler編集時ツールバーなど
config.views.editor.tagPrompt = "[[tags]]のスタイルでtagsをスペース区切りに入力します。";
config.views.editor.tagChooser.text = "タグ";
config.views.editor.tagChooser.tooltip = "既存のタグから追加するものを選択してください。";
config.views.editor.tagChooser.popupNone = "タグが設定されていません。";
config.views.editor.toolbarDone = "確定";
config.views.editor.toolbarCancel = "編集中止";
config.views.editor.toolbarDelete = "削除";
config.views.editor.defaultText = "'%0'の内容を入力してください。";
// Each has a 'handler' member that is inserted later
config.macros.search.label = "Wiki内検索";
config.macros.search.prompt = "このTiddlyWiki内を検索します。"
config.macros.search.successMsg = "%0 件見つかりました。- %1";
config.macros.search.failureMsg = "%0 に該当するデータはありません。";
config.macros.timeline.dateFormat = "YYYY年MM月DD日";
config.macros.allTags.tooltip = "タグ'%0'のデータをすべて表示します。";
config.macros.allTags.noTags = "タグが設定されていません。";
config.macros.list.all.prompt = "アルファベット順のTiddler一覧";
config.macros.list.missing.prompt = "リンクはされているが定義されていないTiddler一覧";
config.macros.list.orphans.prompt = "他のどこからもリンクされていないTiddler一覧";
config.macros.closeAll.label = "すべて閉じる";
config.macros.closeAll.prompt = "編集されているもの以外の表示されているすべてTiddlerを閉じます。";
config.macros.permaview.label = "permaview";
config.macros.permaview.prompt = "現在表示されているTiddlerの状態を表示するURLです。"
config.macros.saveChanges.label = "保存";
config.macros.saveChanges.prompt = "すべてのTiddlerを保存します。";
config.macros.newTiddler.label = "新規作成";
config.macros.newTiddler.Title = "新規Tiddler";
config.macros.newTiddler.prompt = "新しいTiddlerを作成します。";
config.macros.newJournal.label = "新規日報";
config.macros.newJournal.prompt = "新しいTiddlerを現在の日時をタイトルとして作成します";
* [[現在のCPU時間を取得したい|Clock::update()]]
* [[clock型の値をmsec単位で変数に代入したい|Clock::at_msec()]]
Psychlopsの仕様として、描画はすべてダブルバッファリングの裏画面に行っています。
このため、描画命令を書いた後に、単にCanvas.flip()命令だけを繰り返すと、2枚に1枚は何も描画されていない画面が表示されます。
止まった絵を表示させたいときにこれを回避するには、2枚の画面の両方に同じ刺激を描画してください。
! 名前空間に関する問題
Psychlopsで使っている命令をコンパイルするときに、ある特定のクラスについて大量のエラーが出ることがあります。
とくに、変数の宣言自体についてエラーが出る場合のの原因の一つとして、ほかのライブラリ、特にOS・コンパイラ標準のライブラリにあるクラスとこの特定のクラスが同じ名前を持っているために、名前が衝突してしまってコンパイルができなくなっていることが考えられます。
このような場合、以下のように スコープ演算子 "Psychlops::" をクラス名の前に付け加えることで問題を解決できます。
!!! (1-1) Macintoshでコンパイル時に、Point型を使った命令すべてに対してエラーが出ます。
Macintoshでは標準ライブラリにPoint型がすでにあるために、名前が衝突してしまい、コンパイルができません。スコープ演算子Psychlops::を使って、衝突を回避してください。
{{{
(コンパイル不可)Point A[n]
(コンパイル可) Psychlops::Point A[n]
}}}
!!! (1-2) Windowsでコンパイル時に、Rectangle型を使った命令すべてに対してエラーが出ます。
(1-1)と同様にWindowsでは標準ライブラリにRectangle型がすでにあるために、名前が衝突してしまい、コンパイルができません。スコープ演算子Psychlops::を使って、衝突を回避してください。
{{{
(コンパイル不可)Rectangle A[n]
(コンパイル可) Psychlops::Rectangle A[n]
}}}
!!! (1-3) random()命令に対してエラーが出ます。
スコープ演算子Psychlops::を使って、衝突を回避してください。
{{{
(コンパイル不可)random()
(コンパイル可) Psychlops::random()
}}}
名前空間の問題についての詳細は、[[OS環境による関数名の衝突について]]をご覧ください。
[[ランダムドットを書きたい|3.2 オフスクリーンを用いた描画例-ランダムドットパターン-]]
[[ダイナミックランダムドットを書きたい|Psychlops::random]]
[[一定方向に運動するランダムドットを書きたい|3.2.3 ランダムドットキネマトグラムの描画]]
[[ランダムドットステレオグラムを書きたい|Tips: ランダムドットステレオグラムの描画コード]]
[[垂直・水平方位の正弦縞を書きたい|2.2.6 画面上に縞を描画する-Rectangleクラス2-]]
[[任意の方位の正弦縞を書きたい|2.2.7 画面上に縞を描画する-Pointクラス2-]]
[[運動する縞を書きたい|2.2.8 運動する図形を描画する-Rectangleクラス3-]]
[[Plaid刺激を書きたい|2.2.7 画面上に縞を描画する-Pointクラス2-]]
[[Gabor Patchを書きたい|6.2 Gaborパッチの事前描画]]
[[インタラクティブなデモプログラムを作成したい|7. デモを作成する]]
[[1. 既成クラスを用いた簡単な実験プログラムを作成する]]
[[1.1 実験計画のプログラミング]]
[[1.2 Gaborパッチの事前描画]]
[[1.3 各試行部分のプログラミング]]
[[1.4 終了処理とまとめ]]
[[2. デモを作成する]]
[[2.1 独立変数Independent]]
[[2.2 デモ環境の表示]]
[[1. インストールを行う]]
[[1.1 必要な環境]]
[[1.1.1 ハードウェア環境]]
[[1.1.2 ソフトウェア環境]]
[[1.2 インストール作業(Mac) ]]
[[1.3 インストール作業(Windows) ]]
[[1.4 更新とアンインストール]]
[[1.5 Psychlopsの基本構造]]
[[1.6 Psychlopsを使ったプログラミング]]
[[1.6.1 プログラミングとは?]]
[[1.6.2 Psychlopsを使ったプログラムの形]]
[[1.6.3 配列]]
[[1.6.4 for命令]]
[[1.6.5 変数型]]
[[2. 基本的な描画を行う]]
[[2.1 描画領域の宣言]]
[[2.1.1 Canvasの宣言]]
[[2.1.2 Canvasの基本構造と操作]]
[[2.1.3 Canvasのメンバ取得と変数の表示]]
[[2.2 図形描画のコマンド]]
[[2.2.1 画面上に点を描画する-Pointクラス1-]]
[[2.2.2 画面上に線を描画する]]
[[2.2.3 画面上に四角形を描画する-Rectangleクラス1-]]
[[2.2.4 画面上に円を描画する]]
[[2.2.5 描画する図形の色を指定する-Colorクラス-]]
[[2.2.6 画面上に縞を描画する-Rectangleクラス2-]]
[[2.2.7 画面上に縞を描画する-Pointクラス2-]]
[[2.2.8 運動する図形を描画する-Rectangleクラス3-]]
[[3. 複雑な描画を行う1(オフスクリーンへの描画)]]
[[3.1 オフスクリーン描画の基本]]
[[3.1.1 オフスクリーン領域の宣言]]
[[3.1.2 オフスクリーン上に点・四角形を描画する]]
[[3.1.3 オフスクリーン上の任意の位置に描画する]]
[[3.1.4 発展的な内容]]
[[3.2 オフスクリーンを用いた描画例-ランダムドットパターン-]]
[[3.2.1 基本的なランダムドットを描画する]]
[[3.2.2 ドットサイズの異なるランダムドットパターンの描画]]
[[3.2.3 ランダムドットキネマトグラムの描画]]
[[3.3 画像ファイルの取り扱い]]
[[3.3.1 画像ファイルを保存する]]
[[3.3.2 画像ファイルを読み込み表示する]]
[[3.4 Canvasのオフスクリーンへの取り込み]]
[[5. 入出力を行う]]
[[5.1 外部装置(キーボード/マウス)の操作内容を取得する]]
[[5.2 ファイルの入出力]]
[[6. 時間制御と各種ツールを使用する]]
[[6.1 時間を計測する]]
[[6.2 各種ツールを使ってみる]]
[[6.2.1 描画の時間精度を確認する-FPSチェッカー]]
[[6.2.2 画面に反映されない描画の進行度を表示する-プログレスバー]]
[[6.3 行列演算を行う]]
[[6.3.1 行列の宣言]]
[[6.3.2 行列の要素の操作]]
[[6.3.3 行列全体の操作]]
[[6.3.4 行列の加減乗除]]
[[6.3.5 行列の中身をオフスクリーンに描画する]]
[[6.3.6 メッシュグリッドの作成]]
[[Visiome Platform|http://platform.visiome.neuroinf.jp]]には様々なデモンストレーションが公開されています。
Psychlopsによるデモプログラムも多数公開されておりますので、是非ご参加ください。
(現在の所このサイトにアップロードするためのアカウント取得には制限があります。詳しくはVisiomeのホームページをご覧下さい。)
*Graphic
**[[Psychlops::Canvas]]
**[[Psychlops::Rectangle]]
**[[Psychlops::Point]]
**[[Psychlops::Image]]
**[[Psychlops::Color]]
*Devices
**[[Psychlops::Input]]
**[[Psychlops::Mouse]]
**[[Psychlops::Clock]]
*Matrix
**[[Psychlops::Matrix]]
**[[Psychlops::Math]]
*[[Canvas型に関して]]
*[[Rectangle型に関して]]
*[[Point型に関して]]
*[[Image型に関して]]
*[[Color型に関して]]
*[[入力系のクラスに関して]]
*[[時間計測系のクラスに関して]]
[[基本的な命令の使用]]
[[特定の種類の刺激描画]]
[[数値計算]]
[[その他]]