んー,まだその私としてもそういう経験が少ないしこれは Netscape-Enterprise にとっては謂れ無き批判かも知れないんですがっていうか要するに Netscape-Enterprise ってぶっちゃけて言えばどうなのよ?
JPEG デコーダ作成の合間に,頭の体操という事で HTTP プロクシを作っています。Netscape-Enterprise なんですが,以前「HEAD リクエストしたらエンティティまで持ってきやがり Mozilla が硬直してしまう」と騒ぎました。あの時は Sun と Netscape のどちらのせいかは特定しませんでしたが ( Mozilla だって,自分で接続を切断してもいいわけですが ),しかしよく考えてみると「HEAD リクエストを GET リクエストと同等にする」なんて機能はつけてはいけない (MUST NOT) わけで,Netscape がやや不利。
そして今日気づいたのは,Netscape-Enterprise が返してくれたこんなレスポンスヘッダ。
HTTP/1.1 200 OK Server: Netscape-Enterprise/4.0 Date: Sat, 08 Mar 2003 16:54:01 GMT Content-type: text/html Connection: close
"Content-Length" も示されていないし "Transfar-encoding: chunked" でもありませんが,これでも正統な HTTP/1.1 レスポンスなのでギャフン(;´Д`)。この場合,クライアントはサーバの切断を以ってエンティティの終了とします。
(;´Д`) いや別にいいんだけどでも以下略。
さすがと言うか当たり前と言うか,Mozilla は HEAD リクエストを投げた後も接続を維持し,保存するファイル名が決定され次第その接続のまま GET リクエストを行います。わざわざコネクションを貼りなおすペナルティを払う必要がないわけで,これが「持続的接続」の醍醐味っすね。
少しずつ解析を進めていますが,出だしはなかなか悪くありません。・・が少し面食らった事に,最初の SOS までに登場する DHT は 2 つのみ。DC 係数をデコードするためのハフマンテーブル定義が 2 種類だけです。AC 係数をデコードする DHT はどこなんだろうと思いながらスキャン内の 0xFF を探してみると,何の脈絡もなくいきなり 0xFF 0xC4 が見つかりました。
F2 A4 AA A4 42 17 A2 CE - B2 2A 2E 10 9F D2 53 FB 2D 1A 44 4A 5A 36 13 FF - C4 00 2D 10 .... ... 0x36 0x13 までは DC 係数のデータ その直後の 0xFF 0xC4 が DHT マーカ
「何の脈絡もなく」とは言うもののおそらく全ての DC 係数をデコードし終えた後に DHT が登場したものと予想はできます。DC 係数までならば,あまりデコードは難しくない気がします。
プログレッシブ JPEG には "Start Of Frame 2" と呼ばれるセグメントがあります。基本 DCT 方式の
JPEG で言うところの "Start Of Frame 0" ですね。マーカ値は 0xffc2。
手持ちのプログレッシブ JPEG をバイナリエディタで覗いて見たところ,おそらくこんな感じの構造である事が分かりました。
| サイズ(bytes) | 意味 | 16 進値 (10 進) | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2 | マーカ | 0xff 0xc2 | ||||||||||||
| 2 | セグメントのサイズ | 0x00 0x11 (17) (※) | ||||||||||||
| 1 | 1 色あたりのビット数 | 0x08 (8) (※) | ||||||||||||
| 2 | 画像の高さ | 任意 | ||||||||||||
| 2 | 画像の幅 | 任意 | ||||||||||||
| 1 | コンポーネント数 | 0x03 (3) (※) | ||||||||||||
| 3 x コンポーネント数 |
|
|||||||||||||
ちなみに (※) の部分は実際には可変です。これを見る限り,要するに Start Of Frame 0 と何ら変わりが無いように思えます。そのまま Decoder::segment_startOfFrame0() に through しちゃっていいのかなあ?
ただでさえ JPEG の詳しいリソースが少ないのに,プログレッシブ処理を勉強するにはなおさらリソースが少なすぎます。ていうか私に才能がないのかなあ ( マイナス思考 )。要するにネタがないって事で,ちょっと脇道にそれてみます。
そう言えば 2003-02-28 にアップしたソースでは使っていませんが,会社ではこんな関数を使っています。
const char *formatted(const char *fmt, ...) {
static char string[129];
va_list argptr;
va_start(argptr, fmt);
_vsnprintf(string, 128, fmt, argptr);
va_end(argptr);
return string;
}
これが威力を発揮するのは,こんなマクロ。
#define HERE formatted("%s, %d", __FILE__, __LINE__)
試しにこんなコードを書いてみると
int main(int ac, const char *av[]) {
puts(HERE);
puts(HERE);
puts(HERE);
return 0;
}
E:\Works\C\test56\main.c, 18 E:\Works\C\test56\main.c, 20 E:\Works\C\test56\main.c, 22
これにより,throw HERE; とした時にどこから例外が飛んでくるのかが分かるようになるのです。悪用ダメゼッタイ! ・・て別に「ネガティブ・コーディング」ネタじゃないけど・・。
あれからレジストリを調べたりネットで検索を繰り返した結果,その手口はどうも JS.Seeker.J などと呼ばれているウィルス ( ワーム ) に酷似している事に気づきました。おそらくそのウィルスに感染したわけではないだろうけど,しかしこの迷惑アプリケーションはその手口を真似ているに違いありません。
結局,今回の件で修復したレジストリは次の通り。かなり大掛かりです。
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\run
キー .. (標準)
書き込まれていた値 .. http://www.seyou.net (REG_SZ 型)
修復方法 .. 削除
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\run
キー .. Start Page
書き込まれていた値 .. http://www.seyou.net (REG_SZ 型)
修復方法 .. 削除
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer
キー .. NoRun
書き込まれていた値 .. 0x00000001 (REG_DWORD 型)
修復方法 .. 削除
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\System
キー .. DisableRegistryTools
書き込まれていた値 .. 0x00000001 (REG_DWORD 型)
修復方法 .. 削除
HKEY_CURRENT_USER\Software\Policies\Microsoft\Internet Explorer\Control Panel
キー .. HomePage
書き込まれていた値 .. 1 (REG_SZ 型)
修復方法 .. 削除
HKEY_CURRENT_USER\Software\Policies\Microsoft\Internet Explorer\Control Panel
キー .. SecChangeSettings
書き込まれていた値 .. 1 (REG_SZ 型)
修復方法 .. 削除
HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main
キー .. Local Page
書き込まれていた値 .. http://www.seyou.net (REG_SZ 型)
修復方法 .. c:\\windows\\SYSTEM\\blank.htm (REG_SZ 型) を上書き
HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main
キー .. Start Page
書き込まれていた値 .. http://www.seyou.net (REG_SZ 型)
修復方法 .. http://www.microsoft.com/isapi/redir.dll?prd={SUB_PRD}&clcid={SUB_CLSID}&pver={SUB_PVER}&ar=home
(REG_SZ 型) を上書き
HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main
キー .. Search Page
書き込まれていた値 .. http://www.seyou.net (REG_SZ 型)
修復方法 .. http://www.microsoft.com/isapi/redir.dll?prd=ie&ar=iesearch
(REG_SZ 型) を上書き
HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main
キー .. Default_Page_URL
書き込まれていた値 .. http://www.seyou.net (REG_SZ 型)
修復方法 .. http://www.microsoft.com/isapi/redir.dll?prd=ie&pver=6&ar=msnhome
(REG_SZ 型) を上書き
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\run
キー .. internat.exe
書き込まれていた値 .. http://www.seyou.net (REG_SZ 型)
修復方法 .. internat.exe (REG_SZ 型) を上書き
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\run
キー .. internet
書き込まれていた値 .. C:\WINDOWS\SYSTEM\internet.url (REG_SZ 型)
修復方法 .. 削除
結果。。。
問題は解決したようだ。再起動しても件のサイトは表示されない。
なんかてこずったーっ。しかし疑問が一つ ── ここまでしてこの迷惑アプリケーションは一体何をしたかったのか,未だに分かりません。。。
ここ数日,せっかく書いたこの雑記をアプしていませんでした。2003-03-02 でそれに気づいてるじゃんか>自分。。。
私,ヤバくないスか?
ある程度レジストリを修正するプログラムを相談者さんに渡し,とりあえず様子を見ることにしました。これで解決すれば OK なんだけど・・・
解決しなかった。コンピュータを再起動したが,相変わらず件のサイトを表示してしまう。他に何かが必要なようだ。
やはり一筋縄ではいかないようです。再び相談者さんのレジストリの情報を取得したところ,厄介な事に,修復したはずの値がまた破壊されていました。
注意深くレジストリを見てみると,何やら奇妙なエントリが。
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\run キー .. internet 値 .. c:\windows\system\internet.url (REG_SZ 型)
なんじゃこりゃ? 少なくとも私のコンピュータのレジストリにはありません。それに一般の常識的なアプリケーションが,"internet" などという誰でも使いたくなるような一般名詞を用いてエントリを作成する事は考えられません。久しぶりに「プログラマの勘」発動です。相談者さんに新しい情報収集プログラムを渡し,この internet.url というファイルを取り寄せてみました。
[InternetShortcut] URL=http://www.seyou.net/ Modified=C0F193204FE0C20156
クリティカルヒット! 敵はもうあらゆる手を使って http://www.seyou.net/ を嫌わせようとしています。笑っちゃいます。(つづく)
ここ数日間の雑記 ( 日記? ) を,書いたはいいけどアプするのを忘れててトホホ。なんか気ばかり焦って無駄な失敗を続けています。可及的速やかにベータエンドルフィンを分泌する必要ありですウヘヘ。
さて,昨日から取り組んでいる迷惑アプリケーションの被害状況はこんなの。
パソコンを起動すると,勝手に普段使っているブラウザが起動し,http://www.seyou.net/ にアクセスする。また InternetExplorer の「ツール」「インターネットオプション」「全般」と辿っていった所の「ホームページ」が http://www.seyou.net/ になり,しかも変更不可になってしまっている。
「パソコンを起動すると,勝手に何かアクションが起きてしまう」という事に対処するには,レジストリや「スタートアップ」フォルダなど,“いつもの部分”を見れば OK。しかしその次の「ホームページの設定が変更できなくなった」というのは一体? むーん,Google 検索開始っ。
・・なんとか見つかりました。ホームページの設定を変更できなくするには,次のレジストリをいじります。
HKEY_CURRENT_USER\Software\Policies\Microsoft\Internet Explorer\Control Panel\
キー .. HomePage
値 .. REG_SZ 型で '1' もしくは REG_DWORD 型で 1
なるほどー,IE はこういう事までも設定可能になってるのかとすっかり感心。インターネットカフェとかからの要望があったのかな? ・・・などと牧歌的になっている場合じゃありませんでした。相談者さんのレジストリ情報を見て唖然。この迷惑アプリケーション,随分とナメた事をやってのけています。
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\run
キー .. internat.exe
値 .. http://www.seyou.net (REG_SZ 型)
ここの本来の値は REG_SZ 型 "internat.exe"。自動で internat.exe というプログラムを起動するために使用されています。このプログラムはタスクトレイの部分でペンの形をしていたり,A という文字だったり あ という文字だったりするアイコンで,現在のキーボードの入力状態を示すもの。無くても重大な問題は起きないようですが,しかし既存のレジストリを利用してこっそりその値を書き換えるとは。。。どう思います?
このネタは続きます。しっかしこの迷惑アプリケーションの作者,無駄に http://www.seyou.net/ の評判を落としたりして,一体何を考えてるんでしょうね?
昨日までの私は画像処理屋ですが,そして無論これからもそうですが,今日はちょっと問題解決屋さん。以前にも迷惑アプリ ( ? ) を撃退した事がありましたが,今回もそれの関係です ( ただし今回の相談者は別の人 )。
ところで ::RegSetValueEx() の挙動でメモ。元々 \some_registry\key に REG_SZ で "foo" という値がセットされてあっても,特にこの値を一旦削除せずとも ::RegSetValueEx() で,例えば REG_DWORD な値をセットする事が出来ます。MS,分かってるなー・・・というか,当然だと言えば当然かも?
プログレッシブの処理で何が問題かって,画像の全ての部分を少しずつ構築しなければならない事。単純に考えた場合,画像がデカければそれだけ JPEG::Decoder クラス内部で保持しておかなければならないわけで。
しっかし,サンプルデコーダとして重宝している jpeg ライブラリ,デコードを始めた途端にあちこちにジャンプしてくれます。デバッガで追いかけても追いつけないという罠・・・。
できました! OK です! 画像処理屋 ( ワナビー ) なめんなよ!
->今日の成果物;
Restart Interval の対応は思ったよりも簡単でした。DRI セグメントを解釈して「いくつの MCU がデコードされる毎に RSTm が出現するか」を保持しておき,とりあえず普通に画像をデコードし始めます。ただし MCU をデコードした数をカウントしておき,RSTm を待ち構えます。そして RSTm が出現すべき場所で 2 バイト読み,確かに出現すべき RSTm である事を確認します。ただそれだけ。
そんな事よりも今日の大発見はこれ:DQT の量子化係数の並び順! てっきり 8x8 ブロックの左上 -> 右上 -> 次の段の左端 -> 右端 -> 次の段の・・・てな順番で並んでいるのかと思いきや,これ,ジグザグシーケンス順に並んでいやがります。どうして,他の常識的なデコーダに比べて圧倒的に画像が汚いのかと思ったら!
この DQT の取得順のミス,私の教科書「JPEG 概念から C++ による実装まで」の「失敗例」として,ほぼそのままの形で載っていました。そういえば昨日批判した失敗例も,ちゃーんと載っています。。。
しかしまだ ver1 とは呼べません。SOS セグメントなどで取得される「成分数」が 1 つのみ,つまり純粋にモノクローム画像として圧縮されている場合の対応を行っていないため,そのような画像を読ませると落ちます。・・落ちはしないかも知れないけど,システムのどこかから例外が投げられます。
私が書いたコード ( ここでは idct.c 以外 ) は K.INABA さんの NYSL をちょっとだけ改変したライセンスの元で配布されています。とりあえずこのライセンスに名前はつけないでおこうかな・・・。
A. 本ソフトウェアは Everyone'sWare です。このソフトを手にした一人一人が、
ご自分の作ったものを扱うのと同じように、自由に利用することが出来ます。
A-1. フリーウェアです。作者からは使用料等を要求しません。
A-2. 有料無料や媒体の如何を問わず、自由に転載・再配布できます。
A-3. いかなる種類の 改変・他プログラムでの利用を行っても構いません。
A-4. 変更したものや部分的に使用したものは、あなたのものになります。
公開する場合は、あなたの名前の下で行って下さい。
B. このソフトを利用することによって生じた損害等について、作者は
責任を負わないものとします。各自の責任においてご利用下さい。
C. 著作者人格権は k|m に帰属します。著作権は放棄します。
上記「ソフト」とは,ここではソースコードのみです。実行バイナリ jdec.exe の方は,idct.c のライセンスがどうなのかが良く分かんないため,上記ライセンスは適用されません。NYSL の項目 D. を削除したのはそのため。私はこの場でこんな風に配布しちゃっていますが,みなさんが再配布したりするのはやめておいた方が・・・。
また上記ライセンスに加え ── これは遂行されているかどうかの確認を取るのはナンセンスなためライセンスには含めませんが ── 教科書「JPEG 概念から C++ による実装まで」を必ず各自入手するように! いやホント買って損はしませんってば ( JPEG のメンバー(1?)でない限り )。
おっと,忘れてた。一体今まで何に苦労していたのかを書いておこうと思います。といっても凡ミスばかりなので,リソースとしての価値は疑問なんだけど。
まず 2002-01-31 のこんなコード。
// JPEGDecoder::read_bits_scan() から
if ( stream_.isAligned() ) {
if ( skip_next_0x00_ ) {
skip_next_0x00_ = false;
if ( stream_.show8() == 0x00 )
stream_.read8();
} else if ( stream_.show8() == 0xff )
skip_next_0x00_ = true;
}
スキャン内では 0xff 0x00 という 2 バイトのデータは 0xff という 1 バイトのデータとして扱い,0x00 は無視します。上記のアルゴリズムは
「そうでなく」なんてやっちゃいけませんでした。このおかげで 0xff 0x00 0xff 0x00 と並んでいる場所で異常を起こします。
次,AC 係数のデコードでこんなコードを書いていますが,
// JPEGDecoder::decode_block() から
// 上位ビット(下位4ビットを除いたもの) .. ゼロの個数
unsigned int zerorun = bits >> 4;
while (zerorun--) {
if (63 < p) { throw "out of bounds. zerorun processing..."; }
coeff[zigzag[p++]] = 0;
}
あらかじめ coeff[] を 0 で初期化しておけば,改めて zigzag に沿って 0 を代入してやる必要はありません。p に加算してやるだけで済みます。特に上記コード bits の下位 4 ビットが 0 である場合,zerorun は必ず 15 であるはずです。
unsigned int zerorun = bits >> 4;
bits &= 0x0f;
if (bits == 0) {
p += 15;
} else {
...
jpeg ライブラリのコードに倣ってみたんだけど ( jpeg ライブラリのコードもやはり +15 となっている ),これがダメでした。bits 下位 4 ビットは AC 係数を表すわけで,つまりここでは合計 16 個のゼロが存在するわけで ( jpeg ライブラリのコードでは,後でちゃんと +1 されている )。
なんとか難関も克服したところで,いよいよ Restart Interval です。
OK,今日はこのままおやすみなさい。
->本日の成果物;
実行ファイル jdec.exe は,簡単な jpeg ファイルをデコードします。ウィンドウに jpeg ファイルをドラッグ & ドロップして下さい。
ただしプログレッシブ形式や Restart Interval には対応していません。処理しなかったセグメントはコンソールに表示されます。
0xec .. APP12 - Application 12 0xee .. APP14 - Application 14 0xfe .. COM - Comment
特に Restart Interval のある場合は,大抵は次のようにハフマン符号のデコードで失敗します。
0xdd .. DRI - Define Restart Interval huffman code exception
さて,次のステップはとりあえずこの Restart Interval をなんとかしてみようと思います。
今,じわりじわりと患部を追い詰めています。この作業が楽しくて私はプログラマの道を歩んだんじゃないかなあと思えるほどの愉しさ。原因を突き止められた暁には,久しぶりに白い米でも食べようかなあ。近ごろ貧乏一直線なので,穀類の中でも最悪のコストパフォーマンスを誇る白米は完全に無視しきっています。「そんなもの,この世には存在しない」と言わんばかりにね。
というワケで今日の雑記もこれだけで終了! 作業続行!
そうか,「どんな値をデコードしたか」や「何ビットをデコードしたか」なんてな値を printf() なんかで手当たり次第に書き出し,私のコードのそれとライブラリのそれとを見比べればよかったのです。今まで思い出せませんでしたが,これはかなり有効な常套手段。
・・・で比べてみたわけですが,何の脈絡もなくいきなり異常な値をデコードしている事が判明。うーんと,状況がうまく記述できませんが,とにかく謎が増えたという事。
こんな感じで毎日日記っていうか雑記を書いてると,ふとネタが無くなった時に無気力になります。JPEG デコーダも,何が悪いのか謎だらけだし・・・
あ〜,世間ではこんなネタが。
「メ〜テレ」の「メ〜」は羊の鳴き声を表す。
うるせえよ。
やはり DHT 周辺で多少の誤解があるみたいですが,謎。私のコードとライブラリとでは,取得したハフマンコードを扱いやすくする処理の回数が違います。
ターゲットの JPEG ファイルに存在する DHT は 4 つ。私のコードではそれらを取得する毎にハフマン木を生成しており,その回数は当然 4 回。対してライブラリでは,同様の ( と思われる ) 処理を 6 回行っています。「DC 係数と AC 係数別で 2 回 × Y Cr Cb 毎に 3 回 = 計 6 回」となっている模様。
もちっとコードを眺める必要があります。謎です。
今気づいたんですが,JPEG デコーダを作り始めて最早 2 ヶ月を悠に越しています。今現在,任意の JPEG が正常にデコードできる確率はおよそ 1 / 2 ほど。こんなに長期戦になるとは思いもしませんでした。
JPEG のライブラリのソースコードを追いかけていますが,あれ,いつの間にか StartOfScan セグメントを通り越してデコードを始めやがります。一体いつの間に DefineHuffmanTable セグメントを通ったよ? いつの間にハフマン木を作ったよ? ・・・そろそろ究極奥義,「バイナリエディタを使って,自分でハフマンコードをデコードしてみる」てな手段を使うべき?