« 2009年11月 | メイン | 2010年01月 »

2009年12月 アーカイブ

2009年12月10日

Mavenで個人情報漏洩

Mavenを使っていて自分の個人情報が漏れかかったので書いておく。
Mavenについて簡単に説明すると、Javaでの開発ライフサイクルを管理してくれる非常に便利なツール。
ライブラリのjarファイルのダウンロード・プロジェクトのビルド・パッケージだけでなく、テスト実行やレポートの作成、作成されたjavadocのサイトへの配置、パッケージしたプロジェクトのデプロイ等々、これらを全て半自動化できる。
Maven自体もプラグインで拡張できるため、Windows向けのexeファイル化、Mac向けのappパッケージ化にも対応できる。
そんな便利ツールで個人情報が漏れかかった。
と言ってもセキュリティホールを見つけたという話ではなく、どちらかというと文化の違いに起因していると思う。
また、個人情報と言っても本名だけである。
日本のコミュニティでは、本名を伏せて活動している人が多いと思うが、PCへのログインは本名を使っている人はどれくらいいるのだろうか。
漏洩は、そのような人達がMavenを使う場合に起こりえる。
Mavenがjarファイルをパッケージする時に、マニフェストファイルを作成してくれるのだけど、その中に「Built-By」という項目がある。
ここに個人の本名が入る可能性がある。
「Built-By」にはデフォルトでuser.nameの値、つまりPCのアカウント名が入ってしまう。
MANIFEST.MF
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Created-By: Apache Maven
Build-Jdk: 1.6.0_15
Main-Class: com.kanasansoft.Jyazo.Jyazo
Built-By: ■■■■
■で伏せた部分にアカウント名が出力される
対応策はいくつかあるが、自分が取った方法はm2eclipseと相性が良いpom.xmlに「Built-By」を指定する方法。
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<!--略-->
  <build>
    <plugins>
<!--略-->
      <plugin>
        <artifactId>maven-jar-plugin</artifactId>
        <configuration>
          <archive>
<!--略-->
            <manifestEntries>
<!--略-->
              <Built-By>Kanasansoft</Built-By>
<!--略-->
            </manifestEntries>
          </archive>
        </configuration>
      </plugin>
<!--略-->
    </plugins>
<!--略-->
  </build>
<!--略-->
</project>
この対応方法は以下のページを参考にした。非常に助かった。

2009年12月14日

「Apache Maven2.0入門――Java・オープンソース・ビルドツール」を読んだ

Mavenに惚れ込んだし、この本にも惚れ込んだ。
「Maven」について
MavenはJavaを使った開発の開発ライフサイクルを管理するツール。
プロジェクトで使用するライブラリをダウンロードしたり、ビルドやパッケージング、テストの実行やレポートの作成、デブロイ等を管理し、ほぼ自動化させる事ができる。
例えば、log4jを使いたいと思ったときには、次のようにMavenの設定ファイルに記述するだけで、プログラム内からlog4jを利用できるようになる。
<dependency>
  <groupId>log4j</groupId>
  <artifactId>log4j</artifactId>
</dependency>
jar間の依存関係にも対応し、必要なjarがあれば一緒にダウンロードしてきてくれる。
これだけでMavenのパワーが伺い知れる。
しかし、これはMavenの機能の一部でしかない。
使用するjarだけでなくMaven自身の機能もプラグインによって拡張できる。
Mavenを使う時に気をつけたいのは、プログラム内から利用するjarファイルはdependencyと呼ばれ、Mavenが使用するjarファイルはpluginと呼び分けられている点。
これを意識しておかないと、結構混乱する。
Mavenを導入する事で得られる生産性の向上は無視できないレベルで、Javaを開発の中心に置いている会社であれば、影響はかなり大きいだろうと思った。
「Apache Maven2.0入門」について
日本語で読めるMavenについてまとまった情報が書かれた数少ない文章のひとつ。
とにかく行間から愛情に近い何かを感じた。
MavenにかJavaにかプログラミングになのか、開発かもしれないし開発チームに対してなのかわからないけど。
全5章構成の5章がとにかく凄くて、Mavenという単語が出てくるがほとんど関係のない話となっている。
「s/Maven[にのはへもやをがで]//g」して公開しても充分通じるレベルのプロジェクト論で熱意を感じる。
まず5章から読んで文中の熱意に打たれ、興奮しながら1章から読んでも良いのではとも思う。
4章に書かれているContinuumはMavenの強力さを更に加速させる事ができる。
このふたつのツールの連携は恐ろしいぐらい。
こんなツールがあるJavaが羨ましい。
「なんでこんなに気持ち悪い書評書いているんだ」と思っても興奮してしまっているので勘弁して欲しい。
最後の最後で誤植と思われる箇所が多くなっているのが非常に勿体ない。
あと、今読むのに残念なのはgitを使った場合でもContinuumとの連携が可能かどうかわからない事。
今のgitの潮流がくる前に出版された本なので仕方がないんだけど...。
メモについて
毎度の事ながら読みながらつけたメモ。
一応、人に読まれることを考慮しながらメモをつけた。
今回は読み終わった後に少し修正している。
これまでと同じく、書き過ぎて著者に迷惑をかけないレベルにし、手元に書籍がないとよくわからないようにした。
メモ
■P3
Mavenは元々Alexandriaというツールの一部だったらしい。
バーションが2.0なので歴史が浅く感じられるが、実際はそれなりの歴史があるようだ。
■P4
加算的アプローチと差分的アプローチという言葉がしっくりときた。
Antを知っている人に説明する時に使ってみようと思う。
■P14
「-D」で始まるオプションはシステムプロパティらしいけど、よくわからない。
実例がもっとみたいところ。
読み進めると出てくるだろうからとりあえず保留。
■P16
Javaから利用するpropertiesファイルや画像ファイル等のリソースは「src/main/resouces」に保存する。
テストで利用するリソースは「src/test/resouces」ディレクトリに。
ここは、実際にMavenを使っていた時にはまったところ。
「archetype:create」を実行した時に、空のリソースディレクトリも作ってくれれば便利なんだけど、作らない理由があるんだろうか。
■P22
Mavenの読み方。
すいません。
最初、「マベン」とか「マーベン」って読んでました。
最近では「メイバン」と読んでるけど...。
■P55~P56
表2.8と表2.9の処理欄が入れ違っている。
■P58
図2.16にあるexported-pom.xmlについての説明がないが、多分packageフェーズでpom.xmlがコピーされたようだ。
■P59~P60
プラグインがどのように作られているかの解説が興味深い。
変則的に感じたのは、プラグイン特有の機能をjavadoc形式のコメント内に記述しているところ。
ここでは「@goal」でクラスを定義している。
普通ならアノテーションを使うところだろうけど、多分java1.4以下でもMavenが使えるようにするための配慮なのだろう。
プラグインを作成するには、「Mojo(Maven Old Java Object)」インターフェイスを実装する必要がある。
■P61
「mvn archetype:create」が実は「mvn org.apache.maven.plugins:maven-archetype-plugin:create」の略。
本来は「[グループID]:[アーティファクトID]:[バーション]:[ゴール名]」で、バーションを省略した場合は最新のものが使用されるが、標準のものは更に省略可能になっているため「archetype:create」のような記述になる。
■P62~P63
プラグインがパラメータを受け取るための方法が書かれている。
ここでもコメントを活用しており「@parameter」で指定している。
値はpom.xmlのconfiguration要素配下に記述しておく。
パラメータを受け取るのは、プラグインクラスのプライベートフィールドになっている。
一見奇妙だけどプラグインクラスはabstractMojoを継承しているので、abstractMojoでjavadocコメントのXMLを見て動的に反映させているんだろうと思う。
必須パラメータの指定もjavadocコメント内に「@required」を記述し指定。
■P64
javadoc形式のコメントがどのように利用されているのかの説明があった。
javadocコメントがXML形式で出力されてclassファイルと共にjarに固められる。
Mavenはプラグインを利用する時にXMLを参照するとの事らしい。
■P64
pom.xmlからではなく、コマンドライン引数でパラメータを渡す方法について書かれている。
「@parameter expression="${key}"」で指定。
■P65
P14にあった「-D」がプラグイン側でどのように利用されているのかという疑問点がここで解消された。
「@parameter expression="${key}"」と定義されているところへ「value」という値を渡したい場合、「-Dkey=value」とすれば良い。
■P65
コマンドライン引数で受け取るパラメータはpom.xmlのproperties要素と等価らしい。
つまり、「@parameter」のみの場合pom.xmlのconfiguration要素配下で指定するが、「expression」が伴う場合はproperties要素配下になる。
コマンドライン引数による指定とproperties要素による指定では、properties要素が優先されるらしい。
一時的に設定を変更するような用途を考えると、コマンドライン引数優先の方が使い勝手が良さそうに見えるが、設定を書き変えにくくしておくのも一理ある。
カプセル化を重要視するJavaの世界だからかもしれない。
■P65
「expression」が指定されていても、configuration要素による指定は有効らしい。
configuration要素による指定ととproperties要素による指定が両方あった場合、configuration要素が優先されるとの事。
■P66~P67
「@phase」を記述する事で、プラグインが実行されるフェーズを指定できる。
これを有効にするには、pom.xmlのexecutions要素の子孫であるgoal要素にゴール名を記述する必要がある。
executions要素配下への指定で、ゴールを指定しなくてもプラグインを読み込みフェーズを実行する事でゴールが実行されるようだ。
「@phase」は、pom.xmlのexecutions要素の配下のphase要素で代替可能。
■P68
maven-help-pluginについて。
コマンドラインからプラグインの詳細な情報を表示させる事ができる。
EclipseのMavenプラグインから実行する方法が知りたいところ。
■P75
m2eclipseはまだMavenの機能を全て使えるわけではないらしいので、コマンドラインからmvnを実行する必要が出てくる。
ここにはプロキシサーバの設定方法が書かれている。
■P77
アップロード先(FTP?SSH?)を設定すると、プロジェクトの成果物をMavenから公開できるようになるらしい。
便利な反面、誤操作が怖い。
■P80
Mavenのリモートリポジトリが落ちていても開発が続けられるようにするために、リポジトリのミラーの設定ができる。
外部リポジトリを登録しなくても、チームで使っているローカルリポジトリぐらいは登録しておいた方が良いかもしれない。
■P81
Maven Eclipse プラグイン(maven-eclipse-plugin)は、MavenがEclipseと連携するためのひとつの解。
Mavenのプラグインとしてインストールし利用する。
■P83
「eclipse:eclipse」ゴールは、本来、Eclipseが作成し利用する「.project」等のファイルを、pom.xmlの情報を元に作成してくれる。
「DdownloadSources」オプションを指定すると、ライブラリのソースもダウンロードしてEclipseに関連付けてくれる。
■P85
maven-eclipse-pluginがMavenのプラグインなのに対して、Maven 2.x Plug-in for Eclipse(m2eclipse)はEclipseのプラグイン。
普段はこれを利用しているが、非常に便利で重宝してる。
ただ、この本を読むまであまり便利さを享受できていなかった。
■P90
m2eclipseのプロキシ設定方法。
ただし、m2eclipseはsettings.xmlも読み込んでくれるようで、P75の設定をしておけば、ここは必要ないはず。
■P92
はじめてEclipseとMavenを連携させようとした時に、sourceとoutput foldersをわけていなかったので、projectディレクトリ配下がグチャグチャになってしまった。
(正確には、最初はよくわからずにさわっていたらグチャグチャになったが、そうなった一番の原因は多分これだろうと考えている。)
■P93
プロジェクトに対してm2eclipseを有効にする方法。
コンテキストメニューから選択するだけ。
通常、コマンドラインからMavenを使っていると、プロジェクトの作成はarchetypeプラグインを使う。
Mavenの解説方法が書かれているサイトもarchetypeプラグインを使うとある。
しかし、m2eclipseでは、まずEclipseの標準機能でプロジェクトを作成し、m2eclipseを有効にする。
m2eclipseを解説しているサイトはほとんどないようなので、この事はこの本を読むまで全くわからなかった。
早くこの本を購入しておけば、読んでおけばと後悔した理由のひとつ。
これで、日常的に実行するMavenの機能は、ほぼ全てEclipse上から実行できるようになる。
■P95
ソースディレクトリ配下に新しいディレクトリを作成した時には、Mavenにその情報を通知しないといけないらしい。
ただ、自分はやった記憶がない。
ディレクトリを作成後に、m2eclipseを有効にした場合は不要なのかもしれない。
■P95
m2eclipseではresourcesフォルダの扱いが少々特殊らしい。
これはEclipseの仕様に合わせての挙動のようだが、知識不足で理解できなかった。
■P96
Dependencyを簡単に追加する方法。
自分の環境では何故か動かないので、pom.xmlを直接編集している。
■P97
ライブラリのソースをダウンロードするには、maven-eclipse-pluginでは引数指定だったが、m2eclipseではチェックボックスひとつで済む。
■P101
Mavenが生成したリソースをEclipseに自動的に認識させる事ができるらしい。
自分の環境のEclipseでは、External Tools(外部ツール)にMavenの項目が表示されないので試せていない。
(Mavenだけでなく他のツールも表示されない...。)
■P104
artifaceIdと誤記あり。
■P107
assembly記述子の標準的な保存先は、「src/main/assembly/」らしい。
この記述も他では見かけなかったのでありがたい情報。
■P108
Mavenの成果物は基本的にtargetディレクトリに出力される。
assemblyプラグインも同じ。
targetディレクトリに出力するプラグインを複数使っていると、どれがどのプラグインの成果物なのか混乱してしまいそうだ。
■P108
assemblyプラグインのバージョン2.1以降で、descriptorパラメータがdeprecatedに変わっているらしい。
動向を注意しておいたほうが良さそう。
■P109
細かいけど、pom.xmlが大文字になっている。
■P109
定義済assembly記述子を使うには、descriptorIdパラメータを使う。
descriptorパラメータと同様に、2.1以降では記述方法が違うとの事。
■P111
fileSet配下のinclude/excludeにはファイル名を指定するが、dependencySet配下のinclude/excludeには「groupId:artifacyId」を指定する。
■P113
Mavenでは開発者が作成した物は基本的に「src/」配下に格納していくが、サイト記述子も「src/site/」配下に格納するとの事。
徹底してる。
■P116
サイトを作成する時に使うコンテンツは、APT(Almost Plain Text)形式、FML形式、Xdoc形式の何れかで記述できる。
どれも初めて聞いた形式だと思う。
APTはWiki風で、FML形式とXdoc形式はHTMLに近い。
FML形式の方が少々特殊か。
■P118
そのまま使用するファイルは、srcやtestと同様に「src/site/resources/」配下に置く。
■P119
siteプラグインは国際化にも対応している。
locates要素に出力する言語をカンマ区切りで指定する。
正確には地域の略称かも。
地域をアルファベット2文字で表す規格がどこかにあったけど、それを指定すると予想。
■P120
各言語のコンテンツの配置方法、サイト識別子を言語毎に準備する方法が書かれている。
■P121
作成されたサイトを公開するwebサーバをpom.xmlに設定すれば、サーバにアップロードしてくれる。
ここではscpによるアップロードを例にしている。
他にどのようなアップロード方法があるかは書いていない。
認証情報等はsetting.xmlに書いておく。
■P121
diployゴールの前にはsiteゴールは実行されない。
■P124
jxrプラグインの説明に出てくるクロスリファレンスの意味がよくわからないので調べてみた。
javadoc等の出力等に参照のためのソースを埋め込む事ができるらしい。
■P125
releaseプラグインが便利そう。
リリースの度にpom.xmlを書き換えるのが面倒なのと忘れそうになるので、そのうち導入する。
■P125
Eclipseを使っていると、TODO等が出力されて鬱陶しく感じる事がある。
急いでいる時に消し忘れた事もあるので、taglistプラグインもそのうち導入しそう。
■P126
コラムなんだけどちょっと目から鱗だった。
マルチバイトに対応していないアプリケーションに、日本の技術者は文句を言う。
しかし、逆の立場だとサマータイムに対応していない日本のアプリケーションに文句を言うだろう。
文句をいう暇があったら、開発者に報告したりパッチを送ろうと。
確かにその通り。
それでこそ開発コミュニティだと思う。
■P133
htpasswdコマンドの-cオプションは、多分createのc。
■P134
soはSharing Objectの事で、WindowsのDLLと同義。
よく忘れるのでメモ。
■P136
P102のマルチモジュールの説明では、階層構造で説明していた。
しかしここではフラットな構造になっている。
Eclipseで開発するのであればフラットが良いようだ。
■P137
CheckStyleが便利そう。
Eclipseには同様の機能があったはずだけど、どこが違うのかは不明。
Eclipse CheckStyle Plug-inとMaven CkeckStyle Pluginがあり、Maven CkeckStyle Pluginはレポートを出力してくれるらしい。
■P139
Maven CkeckStyle Pluginで使用するCheckStyle.xmlの指定方法。
■P142
継続的結合(continuous integration)の重要性について。
結合テスト版のテスト駆動開発みたいなものか。
随分違うか。
■P148
Continuumのデータモデルまで書かれている。
■P151
Continuumに新しいMavenプロジェクトを登録する時のちょっとした制限。
pom.xmlのアップロードによる登録はマルチモジュール構成には対応していないとの事。
親プロジェクトのpom.xmlをSCMから直接登録させれば問題ないらしい。
urlで参照できればどこからでも良いのかもしれない。
■P153
ContinuumはSubversionの認証情報をキャッシュするらしい。
そもそも開発者のid/passを流用するのは以ての外だけど、キャッシュするされるならContunuum専用のアカウントを作るべきだろう。
気になるのは、キャッシュは暗号化されているのだろうかという事。
■P155
サブプロジェクトを再起的にビルドするかどうかをContinuum上で指定する方法。
想像するに、mvnコマンドに指定するオプションそのものだと思われる。
■P156
スケジュールの設定方法。
この手の書式は記号ばかりの為に、非常に検索しにくい。
出典を抑えておかないと、いざ必要になった時に泣きをみることになる。
■P159
中段のsettings.xmlの記述例に誤植発見。
username要素内のcontinuumのスペルが違う。
いや、ユーザ名だから別に問題はないんだけど...。
■P161
突然catalinaという固有名詞っぽいのが出てきたけどなんなのだろうか。
■P161
ディレクトリを削除するような指定がされているけど、これって必要なのかな。
もしかしてjarと違ってwarは実行環境化で実ファイルで展開されるのか。
■P162
ここでプロファイルの説明。
pom.xmlにとってのif文。
条件によってMavenが読み込む論理上のPOMの構造を変更できる。
例えば、OSによって読み込むjarファイルが異なるSWTの設定もプロファイルで分岐が可能になる。
どのような要素を切り換えることができるのかは書かれているが、条件に何が指定できるのかは書かれていないのが残念。
Continuumの説明の後半から妙に駆け足になっていると感じるのは気のせいか。
実際に作業しながらだと、そうでもないのかも知れないけど。
■P164
ブロファイルを使って起動するシェルはOSによって違うが、その違いをプロファイルを使って吸収する例。
ファイル名ごと、もしくはパスごと変数に入れる方が自分好み。
■P170
この習慣化というのが意外に難しい。
でも、習慣化させないとコリジョン起こしてマージが大変になる。
有用性を享受するか一度痛い目を見ないとなかなか身につかないと思う。
■P171
djUnitとcoberturaの名前を頻繁に聞くけど定番のツールなのかな。
■P172
ここに書かれているような犯人探しが不毛なのはその通りなんだけど、かといって原因究明と再発防止を突き詰めていくとどうしても属人的要素に辿り着く。
バグの混入率が高い開発者には重要なモジュールは任せられない事もあるだろうし、どうしてもプロジェクトから外さざるを得ない事もあるだろう。
チームや会社の対外的な部分とのバランスは非常に難しいと思う。
しかしやはりここに書かれていることが前段とするのは重要だろう。
犯人探しが仕事だと思っているプロジェクトリーダーやプロジェクトマネージャーも居るだろうけど。
■P183
USBメモリが使える現場も少なくなったよね。
■P190
「最初の原則は開発者を訓練するときの最初の原則とまったく同じになります。」とあるけど、2番目の原則の間違い。
■P204
directory-inlineゴールの説明がattachedゴールの説明と同じになっている。
「inline」 という文字からは想像できない動作なので、もしかして誤植かもしれない。
■P205
maven-checkstyle-pluginは、コーディングスタイルが固まってきたら利用したいプラグインかも。
チームでコーディングスタイルを揃えるのなら必須なのかもしれない。
■P208
debugパラメータの設定はfalseの方が良いように思うけど、デバッグ情報というのはjarを使う側が使用するものなのかもしれないのでなんとも言えない。
■P209
optimizeパラメータは常にtrueにしておきたい。
■P209
showDeprecationパラメータもtrueにしておいた方が親切かな。
■P211
maven-eclipse-pluginのeclipse:eclipseゴールはEclipseの設定ファイルを出力してくれる。
逆にeclipse:cleanプラグインはEclipseの設定ファイルを削除する。
つまりMaven環境での開発が前提なのであれば、Eclipseの設定ファイルは共有する必要がないということ。
Eclipseの使い方によっては.classpathファイルにフルパスが記述される可能性があリ情報漏洩的に不味いかも。
SCMにはこれらのファイルは登録しない方が良さそう。
■P215
excludesの説明がおかしい。
最後の一文がincludesの説明になってる。
■P216
includesの説明の中に「includeと同様」と説明がある。
P215のexcludesの説明と入れ替わっている模様。
includesの例文の最後にへんな文字列が挿入されてしまっている。
■P219
web.xmlファイルの位置を明示的に指定する方法が書かれているが、Mavenの既定の位置を補足して欲しかった。

2009年12月19日

Eclipseで同じファイルを別々に表示する

Eclipseを普段使っている人でも知らない人が多かったので書いておく。
EmacsでCtrl+2やCtrl+3で画面分割して、同じファイルの別の場所を見ながらコーディングをよくするが、Eclipseでもエディタのタブをドラッグすることで同じ事ができる。
分割表示の例
分割表示の例
Eclipseでも画面を分割することができるが、「パッケージ・エクスプローラー」で同じファイルを同時に開こうとすると、既に開かれているエディタにフォーカスが移ってしまう。じゃあ、同時には開けないかというとそんな事はない。
同時に開きたいエディタのタブを右クリック
同時に開きたいエディタのタブを右クリック
「新規エディター(E)」を選択
「新規エディター(E)」を選択
同じファイルが違うエディタで同時に開く
同じファイルが違うエディタで同時に開く
タブをドラッグアンドドロップし別のカラムに移動
タブをドラッグアンドドロップし別のカラムに移動
後はEmacsの「Ctrl+x o(other-window)」にあたるショートカットがあれば、ほとんどマウスを使う必要がなくなるんだけどやり方がわからない。

2009年12月24日

Imageのsrcプロパティは非同期で読み込まれる

次のようなプログラムを書いていてまたハマった。
var canvas=document.createElement("canvas");
canvas.width=160;
canvas.height=120;
var ctx=canvas.getContext("2d");
var img=new Image();
img.src="data:image/png;base64,(略)";
ctx.drawImage(img,0,0);
var imageElem=document.createElement("img");
imageElem.src=canvas.toDataURL("image/png");
document.body.appendChild(imageElem);
読み込んだ画像をcanvasに描画しているのだけど、「img.src="..."」の部分で読み込む画像がJavaScriptの実行と非同期で実行されるようで、drawImageが実行された時に画像が読み込み済でないとそこで実行が終了してしまう。画像が大きかったりキャッシュされていない場合に特に顕著だ。以前、同じような状況の時には次のような方法で回避したことがある。
var f=function(img){
    return function(){
        var canvas=document.createElement("canvas");
        canvas.width=160;
        canvas.height=120;
        var ctx=canvas.getContext("2d");
        ctx.drawImage(img,0,0);
        img.parentNode.removeChild(img);
        var imageElem=document.createElement("img");
        imageElem.src=canvas.toDataURL("image/png");
        document.body.appendChild(imageElem);
    }
}
var img=document.createElement("img");
img.style.display="none";
document.body.appendChild(img);
img.addEventListener("load",f(img),false);
img.src="data:image/png;base64,(略)";
読み込みたい画像が多い場合は、画像数をカウントする事で対応可能だ。
var f=function(imgs){
    var cnt=imgs.length;
    return function(){
        if(--cnt!=0){return;}
        //(略)
    }
}
//(略)
var imgs=[];
imgs.push(img1);
imgs.push(img2);
imgs.push(img3);
var handler=f(imgs);
for(var i=0;i<imgs.length;i++){
    imgs[i].addEventListener("load",handler,false);
}
//(略)
画像を読み込むタイミングが一カ所の場合はまだ良い。しかし画像を読み込むタイミングで処理が分断されてしまうので、複数箇所で画像読み込みを行ないたい場合は非常に複雑になってしまう。なんとかうまい方法はないものか...。
2009/12/24 追記
はてなブックマークのコメントでonloadで良いのではないかとツッコミがありました。調べてみるとMDCにも載ってますね。知らなかった。
普段、「onXxx」という書き方は避けて「addEventListener」を使っているのと、DOMに追加しないとイベントは発火しないと思っていたので上記のように書いてました。考えてみれば、一時的に作成されたImageオブジェクトなので、onloadを使っても問題にならないんですね。
で、実験。
var f=function(){
    var canvas=document.createElement("canvas");
    canvas.width=160;
    canvas.height=120;
    var ctx=canvas.getContext("2d");
    ctx.drawImage(this,0,0);
    var imageElem=document.createElement("img");
    imageElem.src=canvas.toDataURL("image/png");
    document.body.appendChild(imageElem);
}
var img=new Image();
img.onload=f;
img.src="data:image/png;base64,(略)";
なるほど。少し簡素化されました。
addEventListenerでもやってみます。
var f=function(){
    var canvas=document.createElement("canvas");
    canvas.width=160;
    canvas.height=120;
    var ctx=canvas.getContext("2d");
    ctx.drawImage(this,0,0);
    var imageElem=document.createElement("img");
    imageElem.src=canvas.toDataURL("image/png");
    document.body.appendChild(imageElem);
}
var img=new Image();
img.addEventListener("load",f,false);
img.src="data:image/png;base64,(略)";
こちらも動きますね。
ただ、このエントリーで言いたかったのは、間にイベントを挟む事で処理が分断されてしまうのをなんとかしたいという事だったので、これでも不十分なんですね。
実際のコードでは、次のような処理をやろうとしていました。
var makeDataScheme=function(){
    var canvas=document.createElement("canvas");
    canvas.width=160;
    canvas.height=120;
    var ctx=canvas.getContext("2d");
    ctx.globalCompositeOperation="lighter";
    var img1=new Image();
    img1.src="data:image/png;base64,(略)";
    ctx.drawImage(img1,0,0);
    var img2=new Image();
    img2.src="data:image/png;base64,(略)";
    ctx.drawImage(img2,0,0);
    return canvas.toDataURL("image/png");
}
functionで画像処理の結果を返したかったので、処理をイベントで分断したくはなかったのです。説明不足でした。
2009/12/25 追記
続き書きました。

2009年12月25日

超強引に画像データを同期して読み込む方法

前のエントリーの続き。
Firefox前提でIEは無視。他のブラウザでは可能かもしれない。ここまで強引にやる必要があるかどうかは置いておいて、画像データを同期で読み込む方法を見つけたので書いておく。画像を送信先に指定しているのでデータスキームでは「Access to restricted URI denied」(NS_ERROR_DOM_BAD_URI)というエラーになる。動作はFirebugで確認している。
(
    function(){

        var parseHTTPHeader=function(responseHeader){
            var headers=responseHeader.split("\n");
            var len=headers.length;
            var parsing=[];
            for(var i=0;i<len;i++){
                if(/^$/.test(headers[i])){
                }else if(/^[\x09\x20]/.test(headers[i])){
                    if(parsing.length==0){
                        throw "SyntaxError:HTTPHeader (first line) "+headers[i];
                    }
                    parsing[parsing.length-1]+="\n"+headers[i];
                }else{
                    parsing.push(headers[i]);
                }
            }
            var len=parsing.length;
            var parsed={};
            for(var i=0;i<len;i++){
                var pair=parsing[i].split(": ",2);
                if(pair.length!=2){
                    throw "SyntaxError:HTTPHeader (format) "+parsing[i];
                }
                if(pair[0] in parsed){
                    throw "SyntaxError:HTTPHeader (repetition) "+pair[0];
                }
                parsed[pair[0]]=pair[1];
            }
            return parsed;
        }

        var loadImage=function(url){
            var img=new Image();
            var xhr=new XMLHttpRequest();
            xhr.open("get",url,false);
            xhr.overrideMimeType("text/plain; charset=x-user-defined");
            xhr.onload=function(){
                var headers=parseHTTPHeader(xhr.getAllResponseHeaders());
                var contentType=("Content-Type" in headers)?
                    headers["Content-Type"]:
                    "application/octet−stream";
                var dataScheme="data:"+contentType+
                    ";base64,"+btoa(
                        xhr.responseText.replace(
                            /[\u0100-\uffff]/g,
                            function(c){
                                return String.fromCharCode(c.charCodeAt(0)&0xff);
                            }
                        )
                    );
                img.src=dataScheme;
            };
            xhr.send("");
            return img;
        }

        var url="http://(略)";
        var img=loadImage(url);
        document.body.appendChild(img);

    }
)();
参考エントリー
コード内で行なっている処理は以下のエントリーを参照の事。
本来推奨されていない同期リクエストを使用しているので注意。

2009年12月27日

有須子時計

○○時計と言うのが流行っているようですね。
1分毎に画像が入れ替わり、画像は24時間分用意されているとか。24時間*60分で1440枚の写真ですか。有須子時計は違います。毎分毎分違う有須子が表示されます。しかもほんの一瞬だけ。
例1
例1
例2
例2
例3
例3
例4
例4
例5
例5
今見た有須子はもう見る事ができません。次に見る有須子ははじめて見る有須子です。いつも新鮮な表情をしてくれる有須子時計をお楽しみ下さい。
注意
unsafeWindowを使用しています。意味が理解できない方はインストールしないで下さい。少なくとも有須子時計がインストールされた状態では信用できないサイトを開かないで下さい。
技術的解説
何故かGreasemonkey配下では2DコンテキストのputImageDataとgetImageDataが動かないため、unsafeWindowを仕様しています。注意して下さい。
今回は、JavaScriptでかなりまじめに画像処理を行なっています。処理の大まかな流れは以下の通り。

事前準備
有須子の画像はBase64化されてGreasemonkey内に記述されています。
「0」〜「9」までの数字と「:」の文字は、赤いハロー効果を強く効かせた画像と弱く効かせた画像の各2種類、合計22枚を有須子画像と同様に保存しています。

(1) Base64の有須子画像をImageオブジェクト化
(2) Imageオブジェクトの有須子画像をCanvasオブジェクト化
(3) Canvasのコンテキストを取得し、三角関数を使って画像を歪ませた新たなCanvasを生成(歪み方は乱数で毎回変化する)
(4) 歪ませた有須子のCanvasからBase64化された画像を取得
(5) Base64の歪んだ有須子画像をImageオブジェクト化
(6) 時刻表示に必要な文字の選定とハローの強弱を決定
(7) Imageオブジェクト化されていない時刻表示用文字をImageオブジェクト化
(8) 有須子画像と同じサイズの時刻画像をCanvasとして生成(乱数で表示位置を散らしている)
(9) 有須子画像同様に、三角関数を使って画像を歪ませた新たなCanvasを生成(定数が有須子と違うため、同じ歪み方にならない)
(10) 歪ませた時刻画像のCanvasからBase64化された画像を取得
(11) 歪んだ時刻画像のBase64のデータからImageオブジェクトを生成
(12) 歪んだ有須子画像と同じく歪んだ時刻画像を合成し表示用画像のCanvasを生成
(13) 表示用画像のCanvasからBase64化されたデータを取得
(14) これまでと同様に、Base64化されたデータからImageオブジェクトを生成
(15) 表示用画像表示処理をタイマーを設定して表示待ち
(2009/12/27 追記:※version1.1では(4)(5)(10)(11)が不要になっています。)

Imageオブジェクトにいちいち変換しているのは、CanvasのコンテキストのdrawImageメソッドがImageオブジェクト以外を引数に指定できないとの事だからです(2009/12/27 追記:Canvasオブジェクトも可能でした。)。また、Image#srcにdataスキームや画像のURLを指定した場合、画像は非同期で読み込まれます。CanvasのコンテキストのdrawImageメソッドは、読み込まれていない画像を引数に渡された場合、処理がそこで終了してしまいます。つまり「image.src="...";」とした直後に「ctx.drawImage(image,0,0);」とした場合、そこそこの頻度で落ちてしまいます。仕方がないので、Imageオブジェクト化する部分はonloadを用いて処理を再開するようにしています。

処理時間で一番かかっているのはこのImageオブジェクト化の部分で、処理時間の大半を占めていました。drawImageメソッドを使わず、全ての合成処理をCanvasPixelArrayで行なう方法もあるらしいのですが、今回は諦めました。2Dコンテキストから取得できるImageDataオブジェクトにはdataプロパティがあり、そこにはCanvasPixelArrayが格納されているらしく、CanvasPixelArrayは全てのピクセルデータのRGBとアルファ値が個別に取得できるらしいので、そちらを使った方が処理時間は速いかもしれません。機会があればそのうち試してみようと思います。10秒以上かかることもあったため、時間がきてから画像処理を実行するのではなく、まず画像を処理してから表示の時間がくるまで待機するような仕組みになっています。(2009/12/27 追記:処理のほとんどは非同期処理部分なので処理が重くはならないはずです。)

処理時間が長いので、アクティブになっているウィンドウやタブのみで実行されるような処理を組み込もうかと思いましたが、うまく動作しませんでした。ソース内でコメントアウトされている部分(2009/12/27 追記:version1.1ではBase64化とImageオブジェクト化の一部もコメントアウトされています。)がその制御部分です。タブやウィンドウを複数開いている場合、それぞれで処理が実行されますので重くなる事があると思います。Firefoxが重くなった場合、まず有須子時計を無効にすることをお勧めします。
参考ページ
開設当初はそうでもなかったのですが、最近は充実してきており、よく参考にさせてもらっています。
関連エントリー
これに関連する技術的な話や、これまでの有須子ネタをご覧になりたい方は以下を参照して下さい。