扱えるのはベースラインJPEGで、各色成分がインターリーブしているもの。 簡単に言うと「プログレッシブじゃない普通のJPEG」が読み込める。 規格はITU-T勧告T.81、ISO/IEC10918、JISX4301。 ITUとISO/IECの文書は有料、JISは閲覧だけなら無料で可能。 それと、どういうわけかW3CにITUのPDFが転がっている。
JISが無料で閲覧できるというのはありがたいが、キャッシュをいちいち消してるのか、開くたびに文書をダウンロードしているようだ。 全国津々浦々から文書閲覧されたらどうなるかくらいすぐ分かりそうなもんだが。 うまく開かなかったら開くまで辛抱強く繰り返すことになるが、それがまた状況を悪化させるわけだ。 キャッシュは残しておいてキーだけ送るとか、ページごとにストリームで読むとかできなかったのかね。 ちなみにIE9では問題なく開いた。 Chromeは分からん。
画像のフォーマット(大きさとカラーかグレースケールか)が分かっていれば、JPEG画像を表示するのはとても簡単(少なくともgs-9.06の場合、Cygwinでは表示したいファイルが置いてあるディレクトリをバイナリマウントしないと正しく動きません)。
GS>516 796 8 [ 1 0 0 1 0 1 ] GS<4>(test.jpg) (r) file /DCTDecode filter GS<5>false 3 colorimage
つまり、colorimageの最初の二つの引数に画像の幅と高さを、最後の引数に色成分の数を渡し、入力はDCTDecodeフィルタに渡してcolorimageに食わせるだけでいい。 ただし、デフォルトユーザ空間だと上下が逆さまになるし、画像の大きさはJPEG画像ファイルの中に書いてあるのだから、やはりPostScriptインタープリタ自身にやってほしいところ。
ファイルフォーマットはJISX4301でいうと「付属書B(規定)圧縮データ様式」(31ページ)から載っている。 ビッグエンディアン、マーカは2バイトで最初のバイトが0xff。 マーカセグメント(JISではマーカ部分列)が情報格納の基本単位となる。 これはマーカに続いて2バイトのサイズがあり、そのあとにパラメータが並ぶ。 このサイズにはサイズ自身を含めるが、マーカ自体は含めない。 ちなみに、JISの文書ではいっぱい「H↓i↑」のような記述が出てくるが、これは添え字で、この例なら「Hi」のこと。
ファイル全体はSOIマーカ、フレーム、EOIマーカとなっていて、フレームの中身は、まずオプションで表または種々のマーカをいくつか置くことができ、続いてフレームヘッダ・最初のスキャンライン・オプションでDNL・2番目以降のスキャンライン、と続く。 これらの途中にも表や種々のマーカーを置くことができて、特に表が現れた場合は置き換えることになっている。 JISでは「種々のマーカ」だけにオプションマークがあるように見えるが、原文を見ると全体にかかっている。
フレームヘッダはSOFnマーカから始まるセグメントで、セグメント長さ(2バイト)・サンプル精度(P・1バイト)、ライン数(Y・2バイト)、ラインあたりの標本数(要するに画像の幅、X・2バイト)、色成分数(Nf・1バイト)と続き、そのあとに各色成分の識別子が続く。 とりあえずここまであれば画像形式は分かるので、これ以降の説明は省略。
SOIは0xFFD8で、これにはセグメントはない。 つまり、サイズフィールドそれ自体がない。 続くSOFは、PostScriptインタープリタが扱えるベースラインJPEGの場合はSOF0(0xFFC0)である。 したがって、デコードできる画像はファイルが 0xFF 0xD8 で、その後のテーブルなどのマーカセグメントを読み飛ばして、0xFF 0xC0 というマーカがあるファイルになる。 多くの場合、0xFFE0マーカにJFIFセグメントが入っていることが多いだろう。
DNLというのは、フレームヘッダにライン数を書かずに最初のスキャンラインの後にライン数を書くことも許されていて、その場合のみDNLセグメントがこの位置に現れる。 2番目のスキャンライン以降には出てこない。 これはとりあえず放っておこう。 すると、結局以下のようになっていることが分かる。
| データ | サイズ [bytes] | 備考 | 
|---|---|---|
| 0xFFD8 | 2 | SOI | 
| 0xFFmm | 2 | SOFn以外のマーカ | 
| segsize | 2 | セグメントサイズ | 
| ... | segsize-2 | セグメントの内容 | 
| (0個〜繰り返し) | ||
| 0xFFC0 | 2 | SOF0 | 
| segsize | 2 | SOFセグメントのサイズ、14以上 | 
| P | 1 | サンプルサイズ [bits/sample] | 
| Y | 2 | 画像の高さ [pixels]、0ならDNLを使用 | 
| X | 2 | 画像の幅 [pixels] | 
| Nf | 1 | 色成分数 | 
| Comp-spec-param | 6 | 色成分識別子 | 
| (色成分数だけ繰り返し) | ||
SOF0以外のSOFnに出会った場合はその時点でデコード不可と分かるが、ITU-T T.81ではSOFnは0〜3、5〜7、9〜11、13〜15の13種類が定義されている。 0xFFC4はハフマンテーブル定義、0xFFCCは算術符号定義。 ベースラインの場合、0xFFC4は出てくる可能性があるが0xFFCCは出てこないはずなので、0xFFCCに出会った場合もデコード不可である。 0xFFC8は予約となっていて、これをSOFとみなすのかどうかははっきりしていないようだ。
以上を元に適当に作ったのがこれ。
% errobj errname [ err ] -
/err {
    errordict exch get exec
} def
% value obj key [ vokput ] -
/vokput { 3 -1 roll put } def
% file bytes [ readbytesbe ] (int true | false )
/readbytesbe {
    string readstring {
        0 exch { exch 256 mul add } forall
        true
    } {
        false
    } ifelse
} def
% file [ readwordbe ] ( int true | false )
/readwordbe { 2 readbytesbe } def
% file [ jpegnextmarker ] -
/jpegnextmarker {
    dup readwordbe not { /jpegnextmarker /ioerror err } if  % file len
    1 index fileposition    % file len pos
    add 2 sub setfileposition
} def
% file [preparejpeg] imgdict
/preparejpeg {
    3 dict begin
    /src exch def
    /imgdict <<
        /ImageType 1 /Width 0 /Height 0 /ImageMatrix matrix
        /MultipleDataSources false /DataSource null /BitsPerComponent 8
        /Decode [ 0 1 0 1 0 1 ] /Interpolate false
    >> def
    {
        src readwordbe not { stop } if
        16#ffd8 ne { stop } if  % Start of Image
        {
            src readwordbe not { stop } if
            16#ffc0 eq { exit } if  % Start of Frame, baseline huffman
            src jpegnextmarker
        } loop
        src readwordbe not { stop } if pop  % frame length
        src read not { stop } if imgdict /BitsPerComponent vokput
        src readwordbe not { stop } if imgdict /Height vokput
        src readwordbe not { stop } if imgdict /Width vokput
        src read not { stop } if /colortype exch def
        src 0 setfileposition
        src /DCTDecode filter imgdict /DataSource vokput
        imgdict /ImageMatrix [
            imgdict /Width get 0 0 imgdict /Height get 0 0
        ] put
        colortype 1 eq {    % gray
            imgdict /Decode [ 0 1 ] put /DeviceGray setcolorspace
        } { colortype 3 eq {    % YUV -> RGB
            imgdict /Decode [ 0 1 0 1 0 1 ] put /DeviceRGB setcolorspace
        } { colortype 4 eq {    % YUVK -> CMYK
            imgdict /Decode [ 0 1 0 1 0 1 0 1 ] put /DeviceCMYK setcolorspace
        } {
            stop
        } ifelse  } ifelse } ifelse
    } stopped {
        $error /newerror get {
            stop
        } {
            /preparejpeg /ioerror err
        } ifelse
    } {
        imgdict
    } ifelse
    end
} def
 errはエラー処理。
vokputは「value object key」put の略で、objectは辞書・配列・文字列のどれでもいい。
普通はdict key value putとするところを、value dict key vokputという順序で使うわけだ。
次のreadbytesbeは指定されたバイト数だけビッグエンディアンで読み込む。
forallの使い方に注目。
この使い方はPLRM 8.2のforallオペレータの使い方に例が載っている。
readwordbeはreadbytesbeを使ってビッグエンディアンで2バイト読み込む。
ここまではpreparepngと共通で使える。
次のjpegnextmarkerはJPEGのマーカセグメントを読み飛ばす。 そしてpreparejpgが本体。 使い方は簡単で、JPEGファイルをオープンしてpreparejpgに渡すと、imageに渡せる辞書が返ってくる。 この辞書は画像を扱うで説明した、ユーザ空間で ( 0, 0 ) から ( 1, 1 ) に描画するような辞書になっているので、やはり画像を扱うで作ったsetimagewidth・setimageheight・vflipimageがそのまま使える。 したがって、使い方はこうなる。
(test.jpg) (r) file dup preparejpeg 72 72 translate % position, in the user space coordinate 72 setimageheight % height of image, in the user space coordinate vflipimage % or use the coordinate downward is positive. image closefile
Copyright (C) 2012 You SUZUKI
$Id: ps-jpeg.htm,v 1.1 2012/11/01 14:17:19 you Exp $