->本日の成果物;
基本的な JPEG ファイルを表示します。app.h と filedesc.h,それに main.cpp の一部は Windows に偏っていますが,それさえ変更すればなんとかどのプラットフォームでも動くんじゃないかな,と。
Windows 用のバイナリも添付しました。実行するとウィンドウ ( と DOS 窓 ) が出るので,適当な JPEG ファイルをドラッグ/ドロップして下さい。運がよければ表示できます。
サンプリングファクタが 1 以外の要素が混じっていると正常にデコードできません。つまり今までのサンプリングファクタに関する考察は完全に無意味でした。私はまだこのあたりについて大きな勘違いをしています。
またプログレッシブ JPEG にも未対応。この資料を集めるのは難しそうです。
無事にデコードできても,他の常識的なデコーダに比べてなんか汚いんですよね。おそらく iDCT のアルゴリズムが動画用に最適化されているからかと。
ちょっと良さげな拡大処理アルゴリズムを思いついて「これって凄くない?」と喜ぼうと思った刹那,「・・・これってブレゼンハム・アルゴリズムだっけ?」と思い出して瞬間冷却。ブレゼンハム・アルゴリズムは,直線や曲線の描画を乗除算なしで行えるという事で非常に有名。その考え方を踏まえて画像の拡大・縮小に応用できる事もよく知られています。
// このコードは元画像 src を
// o 横幅 dx_ratio / sx_ratio 倍
// o 高さ dy_ratio / sy_ratio 倍
// にして dst に格納します
void extend_image() {
int x_ratio = 0, y_ratio = 0;
int sx = 0, sy = 0;
for (int dy=0; dy < dst_height; ++dy ) {
for (int dx=0; dx < dst_width; ++dx) {
dst[dy][dx] = src[sy][sx]; // 画素をコピー
// sx をインクリメントすべきかどうかの判断はこんな感じ
x_ratio += sx_ratio;
if (dx_ratio <= x_ratio) {
++sx;
x_ratio -= dx_ratio;
}
}
// sy をインクリメントすべきかどうかの判断
y_ratio += sy_ratio;
if (dy_ratio <= y_ratio) {
++sy;
y_ratio -= dy_ratio;
}
}
}
この技を使うのに適しているのは,横幅や高さを「m 分の n 倍」としたい時に m と n の整数値が分かっている場合ですね。JPEG では「サンプリングファクタ」のお陰で m と n の算出が非常に楽ですというか,算出するまでもないというか。
しかし作業は進みません。ほんの 8x8 ドットの小さな JPEG さえも正常に出力できないんだもんなあ・・・。
INTERNET Watch より。今に始まった事ではないんだけど,とりあえずなんかギスギスしてます。
画像にメッセージを埋め込む新種の迷惑メールに注意 〜 ActiveStateが迷惑メール対策ソフトに新機能を追加
ActiveState と言えば Perl インタプリタのパッケージでお世話になってたりします。こんな SPAM よけも作ってたとは初耳。
Slammer は UDP 1434 を伝って感染するんですっけ? しかし UDP はパケットをワシワシとロスするプロトコルなので感染は難しそう。世界中のネットのパフォーマンスをかなり下げておきながらも致命傷に至らなかったのはそのお陰なのかなあ。
何にしても 15 分でインターネットをダメにしてしまう可能性が示唆されたわけで。映画が一本出来そうです。
そう,サンプリングファクタによっては Cb 成分を 3/2 倍に拡大しなければならなかったりして UZEEEE。実は私のコードはかなり非効率的である事が分かっています。つまりデコード速度がかなーり遅いって事。それに加えて下手な拡大アルゴリズムを採用するワケには行きません。。。どないやねん。。。
サンプリングファクタについておさらい。なんか混乱しそうなんで大変です。重要なのは 2 点。
各サンプリングファクタが Y = (横 3,縦 2),Cr = (2, 1),Cb = (1,1) の場合,各 MCU を構成する 8x8 ブロックの数は
ただし,画像の右端や下端はこれより少なくなることもある
各サンプリングファクタが Y = (横 3,縦 2),Cr = (2, 1),Cb = (1,1) の場合,8x8 ブロックから実際の画像への変換時には
うし。これを頭の中に入れておきながら JPEG のデコーダの核心部分をコーディングして行きたいんだけど,作業がぜんぜん進まないのは萎え要素。MPEG の方も停滞しております。とほほ。
A C tiveX っすかー。3 連続で書かれてある以上ただのタイプミスではないと思うんだけど,でもじゃあ A c tiveX って何ですか? なんちて。
VC++ のワンポイントはまれにネタを振りまいてくれるのが萌え要素。
親から「正月は帰れ」と言われていたので,( ただし正月は込むので日付をずらして ) 大分に帰省していました。でも仕事の方もシャレになっていないのでもう東京に発つのです。
この時の表現をどうするかが問題なんだけど,つまり「東京に帰ります」なのか「東京に行きます」なのかってこと。私の現住所は東京なので「帰る」なのかなあ? しかし現在の住まいは一時的な借り物であり,「帰れ」と言われたら最終的には大分に帰るのが妥当。その大分から出るわけなので,東京へは「行く」べきなんじゃないかなと。
そういう事を考えながら列車の中でノートパソコンをいじったりしています。文明の利器畏れ。
SOUNDS CONTEST ON THE COMPUTER
"謎の建造物"「オアシス21」にて「サウンズコンテスト on the computer」すなわち DTM を趣味とする人たちが曲を持ち寄り腕を競うコンテストの公開審査が行われました。県中の「情報サービス産業」にかこつけたい企業がこぞって主催 / 後援しているだけあり,会場ではあちこちでビジネスマン挨拶が交わされていましたが,まぁいつもの事です。
で総評なんですが,大分大学の偉い人も言ってました。「既存のパターンに沿ってるだけじゃダメだ」て。バスドラムとスネアドラムを適当に連打しておいて,バックでストリングスをファーと鳴らしながら前面で意味不明な不思議サウンドを奏でておけば,とりあえず質の悪いトランスの完成です。今流行ってますもんね。でもこのコンテストが期待しているのはそういうのじゃなくて,もっとこう,全く別のやつだと思うんです。・・・で「自分はそういう新しい曲が作れるんだろうな」と問われると弱いんですが,でも今年の作品はちょっとありきたりのが多すぎたんでつい・・・。
え? 今私はどこに居るんだって? 大分ですが何か?
->本日の成果物;
正確にはこの雑記を書いている今は 25 日なんですが,これはあくまでも日記風のモノなので気にしません | コードを書いたのは 24 日です。前に JPEG のデコードに成功した C コードを C++ でオブジェクト指向バリバリで書きなおす作業を行っていますが,それの 50% 部分ですかね。JPEG のスキャンが開始する直前までをデコードし,画像の高さ,横幅,画素深度を表示します。ハフマンツリーの内容も表示すればよかったんだけど,どう表示させれば奇麗かが分からなかったので割愛。
main.cpp 見れば即座に分かりますが,なんか例外処理で const char * 型を待ち受けています。それぞれのクラスの要所要所でこんな感じのコードがあるんですね。
if (something != SUCCESS) {
SOME_EXCEPTION();
return FAILED;
}
マクロ SOME_EXCEPTION() は throw "some exception" に置き換えられます。今回はちょっと面倒だったんでアレですが,こういうのは後々良くない事が起こるんで早めにマクロを書き換えなければ。
JPEG などのデコード中には必ず定数が用いられます。それは決して円周率や自然対数の底数のような絶対的な真理に基づいた定数ではありませんが,しかし今さら変更されると世界中の画像処理屋が一斉に暴動を起こすような,どーも微妙な位置にあるような。ていうかつまりセグメントの先頭にあるマーカの事なんですが。
そんなわけでこんな宣言をしてみました。
class JPEGDecoder {
static const unsigned long JUST_A_0xFF = 0xFF00;
static const unsigned long START_OF_FRAME_0 = 0xFFC0;
static const unsigned long DEFINE_HUFFMAN_TABLE =0xFFC4;
static const unsigned long START_OF_IMAGE = 0xFFD8;
static const unsigned long END_OF_IMAGE = 0xFFD9;
static const unsigned long START_OF_SCAN = 0xFFDA;
static const unsigned long DEFINE_QUANTIZATION_TABLE = 0xFFDB;
static const unsigned long APPLICATION_0 = 0xFFE0;
};
そしてコンパイル。名コンパイラ VC++5.0 曰く:
純粋仮想関数の宣言に構文上の誤りがあります。
はあ? 純粋仮想関数なんかねぇよハゲ。
'JUST_A_0xFF' : 関数でない識別子が純粋関数であると指定されています。
純粋関数って何よ! ・・・どうも定数メンバを宣言する static const は最近 C++ に追加された仕様らしく,VC++5.0 は対応してないんですねー。。。今回は enum で完全に代用する事ができます。でも例えば static const double 型は enum では代用できないので,*.cpp に書かないといけません。ダサすぎー。
今日は ( 自分にとって ) ちょっとした記念日。かねてから会社で研究していた MPEG デコーダ,ほんのわずかにマクロブロックの断片を見る事ができました。
macroblock address は全く計算せず動き保証も完全に無視しているため,何らかの絵が表示されたわけではありません。しかし壊れたゲーム機のように 8 x 8 ドットの何かがちらちらと表示されたのは確か。おそらく与えられたビットストリームを正常にデコードし,得るべき変数に正しく格納されている事と考えられます。
あとは各変数の意味や使う場面なんですが・・・仕様書,膨大すぎ。
JPEG や MPEG のデコードに必要な,最も基本的な部分っすね。注目は BitwiseInputStreamReader と BitwiseOutputStreamWriter クラス。これらはそれ自身で何らかのストリームをオープン/クローズするのではなく,それぞれ InputStream インタフェースと OutputStream インタフェースを実装したクラスのインスタンスを受け取り,それを自分達の都合の良いようにコントロールします。またこのクラスは MSB からビットを操作 ( 走査 ) するため,RFC1951 の Deflate のような LSB からのビット操作には向いていません。要するに銀玉鉄砲なんてないんです分かったか自分いつまでも万能ストリームクラスを追い求めるな自分。
main.cpp は ビットストリームクラスを使ってファイルをコピーするコードです。ランダムの長さでビットを取り出しながら,そのビットを格納して行きます。ビットストリームクラスのテストのために作ったものであり実用性ゼロですが,なんだか上手くいったので感動。
JPEG のデコードネタがぜんぜん出てきません。1 ヶ月前からおおかた解説したいトコロをしてしまったので,今さらちょっと進めてもネタという程のモノが出ないんですよね。。。
[WSJ] MSの携帯電話にバグ、一部顧客から苦情 (ZDNN)
・・電話帳に登録された電話番号にランダムに電話をかけたり・・
MS が携帯電話市場にも進出してたとは驚きです。いえ別に今さら MS 叩きでもありません。だって MS の技術レベルは明らかに凄いもん。・・しかし私もプロのプログラマとして 2 年ほど働いていますが,「ランダムで何らかの機能が能動的に発動してしまう」というバグを作りこんでしまう状況が謎。
なまじ巨大企業であるだけにいろんなレベルの人間が集まるはず。だとしたら,悪いことにちょっと技術レベル的にアレな人が任された部分について,このような不可解なバグが出て来ちゃったり。そのバグがよく目立ってセンセーショナルであるほど,MS の評価が無駄に下がってしまうわけで。
つまり OJT うぜぇって事です。弊社にもっと仕事を。
私は基礎がなっていないので,キャストの罠に何度も陥っています。その代表例は 2002-10-27 にハマったような int から char へのキャスト。もうそろそろ飽きた気がします。
JPEG など画像データを扱うストリームは,一度バッファにデータを蓄えておくのがコツ。そうする事でビット単位でのデータの取得を簡単にしたりします。さて今日ハマったのはこんなコード。
#include <stdio.h>
unsigned long func() {
char a = 64;
return (a << 1);
}
void print_bits(unsigned long n, int bits) {
if ( bits ) {
print_bits(n >> 1, bits - 1);
printf("%c", (n & 1) ? '1' : '0');
}
}
int main(int ac, const char *av[]) {
print_bits(func(), 32);
return 0;
}
00000000000000000000000010000000
関数 func() で生成された a は 64。それを出力する案際に 1 ビット左にシフトしています。その結果 8 ビット目に 1。期待通りです。
#include <stdio.h>
unsigned long func() {
char a = 64;
a <<= 1;
return a;
}
void print_bits(unsigned long n, int bits) {
if ( bits ) {
print_bits(n >> 1, bits - 1);
printf("%c", (n & 1) ? '1' : '0');
}
}
int main(int ac, const char *av[]) {
print_bits(func(), 32);
return 0;
}
11111111111111111111111110000000
当たり前ですね。関数 func() 内で変数 a のビットパターンは 10000000 ですが,なんたって 符号付きですもんね。
こういう失敗の原因が即座に判り出したので,ある程度の成長と捕らえていいのかなあ?
とりあえず私は FDIS/IS を持っていない事になっているので,C++ の標準規格について調べるのも一苦労。無事コンパイルできるコードを書くためには簡単な C++ 教本とコンパイラの挙動に頼るしかありません。
template<typename T> struct C1 {
void operator = (T) { }
};
template<typename T> struct C2: public C1<const T *>{
// wow!
// void operator = (const T * a) {
// C1<const T *>::operator = (a);
// }
};
int main(int ac, const char *av[]) {
C2<char> c;
c = "foo"; // <- !!??
return 0;
}
コンパイルは通りません。"!!??" の部分で C2 を通じて C1::operator = (const T *) が呼ばれるのを期待したわけですが,コンパイラはそれを呼んでくれませんでした。
2ch ム板の C++ スレッドで相談したところ,仕様との回答あり。
FDIS です。御大 IS の登場です。こんなものがネット上で読めちゃうとは思いませんでした。
要するに,operator = () はクラス毎に暗黙のうちに定義されてしまうので,基底クラスのそれは呼ばれなくなってしまうわけでした。上記コードでは,きちんと C2::operator = () を定義してやる必要があります。
意外な事に,プログレッシブ JPEG の場合は若干ファイルサイズが小さくなるという情報を入手っ。このサイト以外にも,いたる所で「プログレッシブ JPEG は普通の JPEG よりも小さくなる」との記述を見つける事ができます。
プログレッシブ JPEG はイメージを数ドット毎に分割し,ストリームの最初の方ので既に全体の画像が把握できるように並べ替えられます。私のこれまでの知識ならば,こんな事をすると符号化の効率が落ちてしまいファイルサイズは肥大化するはず。うーん,よく分からないんですが,もしかしたらブロックソート的な効能でも出るのかな? それとも流行りの「ウェーブレット変換」みたいなナニカですか?
とりあえずこれは,プログレッシブ JPEG の仕様を理解するにあたり重要な情報かと。多分。
ファイルストリームをいじるついでに,昔に作っておいたクラスを見直していますが,コンパイラにとって意地悪く見える事をサラっとやってのけているので仰天。臆面もなくよくこんなコードを書いたもんです。コンパイラもよくこんなのを通すなあ。しかも期待通りに動作するし。
#include <stdio.h>
class Base {};
class Der1: public Base{};
class Der2: public Base{};
class DerSpecial: public Base{};
void func(const Base &) {
puts("(´ー`)");
}
void func(const DerSpecial &) {
puts("(`Д´)");
}
int main(int ac, const char *av[]) {
func(Der1());
func(Der2());
func(DerSpecial());
return 0;
}
(´ー`) (´ー`) (`Д´)
func() をオーバーロードさせています。Der1 と Der2 を引数に与えた場合は,その基底クラスである Base クラスに反応して func(const Base &) が呼ばれます。引数に DerSpecial を与えた場合,基底クラスが Base であるにも関わらず func(const Base &) でなくきちんと func(const DerSpecial &) が呼ばれます。すごいっすよね。
いじょ,ちょっとした頭の整理でした。
おっとっと,今日は時間が下がってしまったので簡単なメモだけ。
MSDN によれば,WinAPI の ::SetFilePointer() が失敗すると定数 INVALID_SET_FILE_POINTER が返って来ます。その実際の値は 32 ビットが全て 1 で埋まっている 0xffffffff と決められており,0xffffffff という値は正常終了時にも返って来る場合が当然あり得るわけで,厳密なエラー処理は次のように行います。
long ret = ::SetFilePointer(hFile, low, &high, FILE_BEGIN);
if (ret == INVALID_SET_FILE_POINTER) {
DWORD err = ::GetLastError();
if (err != 0) {
// 本当にエラーが起こった
throw APIFailureException(err);
}
}
で今日の最大の問題点は,INVALID_SET_FILE_POINTER がどこにも定義されてないっぽいってこと。VC++5.0 も 6.0 も同様に,include ディレクトリ内をくまなく grep で検索してみたけど無駄でした。どないやねん?
cppll でちらっと話題に上りましたが,「賢すぎるコード」にも注意を払わなければならないとのこと。XP における教義の一つだそうですが,この言葉を読んだ時,なんかこう,脳みその使われていなかった部分を開発されたような気分になりました。
2003-01-09 にてファイルを開くモードの「一般形」をまとめています。これを完全に実装する事が出来れば,かなり柔軟なファイルの開き方を可能にするファイルハンドリングクラスの出来上がりです。
・・とか何とか言って得意げだったんですが,よく考えると「ファイルが既に存在する場合 - ファイルポインタは末尾から」なんて項目は要らないんですよね。末尾にファイルポインタを移動したければ,ファイルを開いた後で ::SetFilePointer() を実行すればよいわけで。
既存の場合と未存の場合の組み合わせは 6 通りですが,「既存の場合:エラー,未存の場合:エラー」なんて組み合わせはあまりにも馬鹿げているので除外します。すると 5 通り。この数字は ::CreateFile() の dwCreationDistribution に用意された定数の種類と一致するわけですが,内容はどうなんだろう?
| dwCreationDistribution | 既存の場合 | 未存の場合 |
|---|---|---|
CREATE_NEW |
エラー | 生成する |
CREATE_ALWAYS |
空にする | 生成する |
OPEN_EXISTING |
開く | エラー |
OPEN_ALWAYS |
開く | 生成する |
TRUNCATE_EXISTING |
空にする | エラー |
はい,過不足なく全ての組み合わせが登場しました。要するに私は先代が行った議論を自分で勝手に繰り返していただけだったのです。とほほー。でも dwCreationDistribution に用意された定数の根拠がハッキリと理解できたので◎とします。最近マイナス志向な場面が多いので,強制プラス志向。
とりあえず API は既に充分な機能を提供してくれているわけで,それ以上の機能はあまり望むべきじゃないって事でファイナルアンサー? 追及しすぎると「賢すぎるコード」が量産されて大変な事になるのです。私の場合は特に。
::SetFilePointer() ]SetFilePointer(); ちなみに ::GetFilePointer() は存在しません。::SetFilePointer(hFile, 0, iRet, FILE_CURRENT) を用いるべし。JPEG デコードネタは明日あたりから復活の予定。今日は昨日書き漏らした気分転換ネタです。CSS を調べていて不覚にも笑ってしまったモノを見つけました。こんな html を書いてみます。
<h1>list-style-type</h1>
<dl>
<dt>hebrew</dt>
<dd><ul style="list-style-type: hebrew">
<li>要素 1</li> <li>要素 2</li> <li>要素 3</li>
<li>要素 4</li> <li>要素 5</li>
</ul></dd>
<dt>armenian</dt>
<dd><ul style="list-style-type: armenian">
<li>要素 1</li> <li>要素 2</li> <li>要素 3</li>
<li>要素 4</li> <li>要素 5</li>
</ul></dd>
<dt>georgian</dt>
( 略 )
<dt>cjk-ideographic ( 漢字 )</dt>
( 略 )
<dt>hiragana</dt>
( 略 )
<dt>katakana</dt>
( 略 )
<dt>hiragana-iroha</dt>
( 略 )
<dt>katakana-iroha</dt>
( 略 )
</dl>
うわっ,対応してやがるしっ。armenian ( アルメニア数字 ) と georgian ( グルジア数字 ) は別途フォントをインストールしないと "?" しか表示されないので残念。
Mozilla がこれらに対応していたのにも仰天ですが,ていうか,なぜ日本語五十音順のみならず「イロハ」まで定義されてますか。w3c メンバの半数は生粋の日本人が占めているに違いないと思った冬のある日 〜 今日は成人の日だったそうです。私には全く関係ありませんが。