yunomuのブログ

趣味のこと

JPEGを読む(6) フレームヘッダとスキャンヘッダ

Interpret frame header

 Figure E.7の"Interpret frame header"の部分Decoder#readFrameHeaderを作る。ビットフィールドの定義はTable B.2にあって、それぞれのフィールドの意味はこう。

  • Lf: フレームヘッダの長さ
  • P: サンプル精度。フレーム内のコンポーネントのサンプルのビットの精度らしい。コンポーネントもサンプルも精度もわからない。Annex Aに書いてあるらしいけどまだ読んでない。
  • Y, X: 画像のサンプルのライン数と列数。要するに縦横の画素数のこと。
  • Nf: フレーム内のコンポーネントの数
  • Csi: i番目のコンポーネントのIDらしい
  • Hi, Vi: i番目のコンポーネントの水平サンプリング係数と垂直サンプリング係数らしい。これもAnnex Aで説明されている。
  • Tqi: 量子化表のどれを使うか。ハフマン符号表にもあったThみたいなやつが量子化表にもあって、どの表を使うかをこの値で示している。

 これは順に読めばいいだけなのでコードは長いけど簡単。出力のためにDecoder構造体にも値を追加する。

type componentParam struct {
    c, h, v, tq uint8
}

func (p *componentParam) String() string {
    return fmt.Sprintf("(C=%d H=%d V=%d Tq=%d)", p.c, p.h, p.v, p.tq)
}

type frameHeader struct {
    marker uint8
    p      uint8
    y, x   uint16
    params []*componentParam
}

func (h *frameHeader) String() string {
    return fmt.Sprintf("p=%d (x, y)=(%d, %d) params=%v", h.p, h.x, h.y, h.params)
}

type Decoder struct {
    // (略)
    frameHeader *frameHeader
}

// (略)

func (d *Decoder) readFrameHeader() (*frameHeader, error) {
    m, err := d.readMarker()
    if err != nil {
        return nil, err 
    }   

    if !m.isFrameMarker() {
        return nil, ErrUnexpectedMarker
    }   

    lf, err := d.readUint16()
    if err != nil {
        return nil, err 
    }   

    p, err := d.readUint8()
    if err != nil {
        return nil, err
    }

    y, err := d.readUint16()
    if err != nil {
        return nil, err
    }

    x, err := d.readUint16()
    if err != nil {
        return nil, err
    }

    nf, err := d.readUint8()
    if err != nil {
        return nil, err
    }

    var componentParams []*componentParam
    for i := 0; i < int(nf); i++ {
        c, err := d.readUint8()
        if err != nil {
            return nil, err
        }

        hv, err := d.readUint8()
        if err != nil {
            return nil, err
        }
        h := hv >> 4
        v := hv & 0xF

        tq, err := d.readUint8()
        if err != nil {
            return nil, err
        }

        componentParams = append(componentParams, &componentParam{
            c:  c,
            h:  h,
            v:  v,
            tq: tq,
        })
    }

    ret := &frameHeader{
        p:      p,
        y:      y,
        x:      x,
        params: componentParams,
    }

    return ret, nil
}

// (略)

func (d *Decoder) decodeFrame() error {
    header, err := d.readFrameHeader()
    if err != nil {
        return err
    }
    d.frameHeader = header

...

 これで適当な画像を読み込むとこのような表ができる。

Marker Lf P X Y Nf
0xFFC0 17 8 400 400 3

 Nf=3なのでコンポーネントパラメータは3つある。

Csi Hi Vi Tqi
1 2 2 0
2 1 1 1
3 1 1 1

Decode scan

 スキャンをデコードする。スキャンというのは、実際に符号化されたデータが格納されたセグメントのことらしい。

 図の通り、スキャンヘッダーを読んで、あとはintervalの個数だけ繰り返しペイロードを読む。中身や個数は置いておいて、とりあえずスキャンヘッダを読む。定義はFigure B.3, Table B.4にある。

  • Ls: スキャンヘッダの長さ
  • Ns: スキャン内のコンポーネントの数。
  • Csj: コンポーネントセレクター。フレームヘッダのCiと一致して、以降のTdjTajがどのコンポーネントに適用されるかを示す。ひとつのスキャンに対応するコンポーネントのパラメータには制約があるみたいだけど一旦置いておく。
  • Tdj, Taj: このスキャンのデータを、どのエントロピー符号化表で復号するかを指定する識別子。エントロピー符号化表というのは算術符号化表もしくはハフマン符号化表のことで、TdjはDC用、TajはAC用。ここで複数あったハフマン符号化表のどれを使うかを指定している。ちなみに算術符号orハフマン符号はフレームヘッダで指定される。
  • Ss, Se: DCTの場合は1つの要素に含まれるDCT係数の配列の最初と最後の位置。1つの要素は8x8で64個あるので、それぞれSs=0, *Se"=63で固定となる。階層型DCTの時や可逆変換の時は要素の単位が変わってくるので意味のある値になるらしいけどこれも一旦置いておく。
  • Al, Ah: これらも階層型DCTや可逆変換の時以外は0なので保留。移動小数点を規定しているらしい。

 あとはこれを読むだけ。コードはフレームの時とほとんど同じで読むだけなので省略。読むと以下のような表ができる。

Ls Ns Ss Se Ah Al
12 3 0 63 0 0

 ここでもNs=3なのでスキャンコンポーネントのパラメータは3つ。こういう感じになっている。

Csi Tdi Tai
1 0 0
2 1 1
3 1 1

まだわかってないこと

 続いて、スキャンの中身を読み始める前にそろそろ全体的な構造を把握しないとわからないことが増えてきた。

  • intervalの数はどこで決まる?
  • スキャンのペイロードの長さはどこで把握する?
  • コンポーネントって何?
  • サンプルって何?
  • 精度Pって何?

 このあたりがわかっていないのでこのまま読み進めることができない。なのでAnnex Aに戻って定義を読み直す。

 その前に、一旦はペイロードの内容を全部読み飛ばしてファイルを最後まで読めるようにしてもいい。こんな感じで。

func (d *Decoder) decodeResetInterval() error {
    for {
        _, err := d.readByte()
        if err != nil {
            return err 
        }
    }   
}

 これをつなげて動けば一応、読み取ったヘッダなどを出力したりして遊ぶことはできる。

JPEGを読む(5) JPEGファイル全体の構造を読めるようにする

 一旦JPEGファイルの全体の構造に立ち戻る。構造のフローチャートはややこしいので一旦飛ばして。Annex E "Encoder and decoder control procedures"のE.2デコードの章を見ていく。

Decode image

 Decoder_setupと、SOF(start of frame)マーカーが出てくるまでマーカーを読む部分("interpret markers")があるけど、これはどちらも読むマーカーの種類は同じなのでまとめてしまっても良さそうに見える。Figure B.16と話が違うような気がしないでもないけど一旦まとめてしまおう。階層の定義(DHP)がある時が考慮されてないけど、これも一旦無視する。そうするとコードはこういうことになる。Decoder#DecodeはFigure E.6のDecode_imageDecoder#decodeMiscは"interpret markers"に相当する。

 Decoder#decodeMiscDecoder#decodeFrameは後で実装する。一旦すべて返却値は無いものとする。

func (d *Decoder) decodeMisc() error {
    // TODO
    return nil
}

func (d *Decoder) decodeFrame() error {
    // TODO
    return nil
}

func (d *Decoder) readSOI() error {
    m, err := d.readMarker()
    if err != nil {
        return err 
    }   

    if m != Marker_SOI {
        slog.Error("readSOI()", "marker", m)
        return ErrUnexpectedToken
    }   

    return nil 
}

func (d *Decoder) Decode() error {
    if err := d.readSOI(); err != nil {
        return err 
    }

    if err := d.decodeMisc(); err != nil {
        return err 
    }

    if err := d.decodeFrame(); err != nil {
        return err 
    }

    return nil 
}

Interpret markers

 Miscにあたる部分はTable E.1に記載してある。

 "merkers"というのはこのなかのいずれかのフィールドのリストらしい。ちなみにフローチャートの"Decoder_setup"の部分で読むテーブルもこの中のDRIやDACらしいので、「まとめてしまっても良さそう」と書いた理由はここにある。ここに挙がっているフィールドは全てマーカーの直後の16ビットでフィールドの長さを示してくれているので、取りあえず中身は読み飛ばしておく。これを繰り返し、Table E.1に記載されていないマーカーがきた場合は読むのをやめる。Markerの方にもそのマーカーがSOFxかどうかを判別するメソッドを足しておく。

import "log/slog"

var frameMarkers = []Marker{
    Marker_SOF0,
    Marker_SOF1,
    Marker_SOF2,
    Marker_SOF3,
    Marker_SOF5,
    Marker_SOF6,
    Marker_SOF7,
    Marker_SOF9,
    Marker_SOF10,
    Marker_SOF11,
    Marker_SOF13,
    Marker_SOF14,
    Marker_SOF15,
}

func (m Marker) isFrameMarker() bool {
    for _, fm := range frameMarkers {
        if m == fm {
            return true
        }
    }
    return false
}

func (d *Decoder) decodeMisc() error {
    for {
        m, err := d.readMarker()
        if err != nil {
            return err
        }

        if m.isFrameMarker() {
            d.unread()
            return nil
        }

        switch m {
        case Marker_DQT, Marker_DHT, Marker_DAC, Marker_DRI, Marker_COM, Marker_APP_n:
            l, err := d.readUint16()
            if err != nil {
                return err
            }

            slog.Info("other header",
                "marker", m,
                "length", l,
            )

            // skip field
            if _, err := d.readBytes(int(l - 2)); err != nil {
                slog.Error("skip")
                return err
            }
        default:
            d.unread()
            return nil
        }
    }
}

Decode frame

 フレームをデコードする。フローチャートはこう。

 これはこのとおりに実装する。Decoder#decodeFrameHeaderDecoder#decodeScanの中身は後回し。

func (d *Decoder) decodeFrameHeader() error {
    // TODO
    return nil
}

func (d *Decoder) decodeScan() error {
    // TODO
    return nil
}

func (d *Decoder) readEOI() error {
    m, err := d.readMarker()
    if err != nil {
        return err
    }

    if m != Marker_EOI {
        slog.Error("readEOI()", "marker", m)
        return ErrUnexpectedMarker
    }

    return nil
}

func (d *Decoder) decodeFrame() error {
    if err := d.readFrameHeader(); err != nil {
        return err
    }

    for {
        if err := d.decodeMisc(); err != nil {
            return err
        }

        m, err := d.readMarker()
        if err != nil {
            return err
        }

        if m != Marker_SOS {
            return ErrUnexpectedMarker
        }

        if err := d.decodeScan(); err != nil {
            return err
        }

        if err := d.readEOI(); err == ErrUnexpectedMarker {
            d.unread()
            continue
        } else if err != nil {
            return err
        }

        return nil
    }
}

 ここまでで一旦ストリームの終端まできた。

JPEGを読む(4) ストリームを読む準備

 本格的に復号していく前に、デコーダのインタフェースと補助関数を作る。既にちょっとコードに書いているけど。

外部インタフェース

 外部インタフェースは一旦こういう感じで。出力はまだどういう形式にするか考えていないので一旦無し。

import (
    "bufio"
    "io"
)

type Decoder struct {
    r *bufio.Reader
}

func NewDecoder(r io.Reader) *Decoder {
    return &Decoder{
        r: bufio.NewReader(r),
    }
}

func (d *Decoder) Decode() error {
    // TODO

    return nil 
}

 Decoderには*bufio.Readerを持たせている。おそらく入力ストリームをバイト単位で読むことが多くなると思われるので、bufio.Reader#ReadByteを使いたい。

マーカー

 JPEGファイルのマーカーも定義しておく。これはもうTable B.1を根性で書き写す。

import "fmt"

type Marker byte

const (
    Marker_SOF0   Marker = 0xC0
    Marker_SOF1          = 0xC1
    Marker_SOF2          = 0xC2
    Marker_SOF3          = 0xC3
    Marker_SOF5          = 0xC5
    Marker_SOF6          = 0xC6
    Marker_SOF7          = 0xC7
    Marker_JPG           = 0xC8
    Marker_SOF9          = 0xC9
    Marker_SOF10         = 0xCA
    Marker_SOF11         = 0xCB
    Marker_SOF13         = 0xCD
    Marker_SOF14         = 0xCE
    Marker_SOF15         = 0xCF
    Marker_DHT           = 0xC4
    Marker_DAC           = 0xCC
    Marker_RST_n         = 0xD0
    Marker_SOI           = 0xD8
    Marker_EOI           = 0xD9
    Marker_SOS           = 0xDA
    Marker_DQT           = 0xDB
    Marker_DNL           = 0xDC
    Marker_DRI           = 0xDD
    Marker_DHP           = 0xDE
    Marker_EXP           = 0xDF
    Marker_APP_n         = 0xE0
    Marker_JPG_n         = 0xF0
    Marker_COM           = 0xFE
    Marker_TEM           = 0x01

    Marker_FF            = 0x00
    Marker_Prefix        = 0xFF
)

func (m Marker) String() string {
    return fmt.Sprintf("0xFF%02X", int(m))
}

 ここでもおそらく入力ストリームをバイト単位で読むことを想定して、マーカーの値は先頭の0xFFを取り除いた1バイトだけを記録しておく。最後の2つは厳密にはマーカーではないけど、Prefix部分を表すMarker_Prefix = 0xFFと、データの中に出現する0xFFを表現するためのエスケープ用の疑似マーカー0xFF00のための値Marker_FF = 0x00も用意しておく。デバッグやエラー出力に便利なのでMarker#Stringも一応書いておく。というかこれが書きたくてtype Markerを定義した。なにかと便利なので。

マーカーと1バイトのデータを読む

 次はストリームのデータを読む補助メソッドを作る。マーカーの定義により、1バイトのデータを読んだ時の挙動は以下のようになる。

  1. 1バイトを読む
  2. 読んだ値が0xFFでない場合は、値をそのままデータとして返却する
  3. 1バイトを読む
  4. 読んだ値が0x00の場合は、0xFFをデータとして返却する
  5. それ以外の場合は、読んだ値をマーカーとして返却する

 実装するとこうなる。

func (d *Decoder) readByteMarker() (byte, Marker, error) {
    b, err := d.r.ReadByte()
    if err != nil {      
        return 0, 0, err 
    }
    
    if b != Marker_Prefix {
        return b, 0, nil
    }

    m, err := d.r.ReadByte()
    if err != nil {
        return 0, 0, err
    }

    if m == Marker_FF {
        return b, 0, nil
    }

    return 0, Marker(m), nil
}

 返却値が0, 0, nilになる場合が複数あるように見えるが、0xFF00となるマーカーは存在しないのでこれで成立している。最後のマーカーを返却する時にマーカーとして有効な値になっているかどうかをチェックした方が良いかもしれない。しなくても後でありえない位置にありえないマーカーが来るとエラーになるはずなので、特にここでチェックする必要も無いような予感はする。

 マーカーは突然出現するわけではなく、読むべきタイミングというのは予想できる。つまり基本的には「次はマーカーがくるはず」「次はマーカーは来ないはず」と思ってストリームを読む。なので以下の2つのメソッドを用意しておく。

import "errors"

var (
    ErrUnexpectedByte = errors.New("unexpected byte")
    ErrUnexpectedMarker = errors.New("unexpected marker")
)

func (d *Decoder) readByte() (byte, error) {
    b, m, err := d.readByteMarker()
    if err != nil {
        return 0, err
    }

    if m != 0 {
        return 0, ErrUnexpectedMarker
    }

    return b, nil
}

func (d *Decoder) readMarker() (Marker, error) {
    b, m, err := d.readByteMarker()
    if err != nil {
        return 0, err
    }

    if m == 0 {
        return 0, ErrUnexpectedByte
    }

    return m, nil
}

複数のバイト値や数値

 複数のバイトを一気に読むメソッド。io.Reader#Readとかで一気に読みたいところだけど、マーカーや0xFF00が絡んでくる可能性があるので結局1バイトずつ読むことになる。

func (d *Decoder) readBytes(n int) ([]byte, error) {
    var ret []byte

    for i := 0; i < n; i++ {
        b, err := d.readByte()
        if err != nil {
            return nil, err
        }

        ret = append(ret, b)
    }

    return ret, nil
}

 Annex Fを見たところ、テーブルのほとんどのフィールドは8ビットか16ビットの数値として解釈されるようなので、uint8, uint16の単位で読むメソッドも作っておく。さっそくDecoder#readBytesが役に立った。

func (d *Decoder) readUint8() (uint8, error) {
    b, err := d.readByte()
    if err != nil {
        return 0, err
    }

    return uint8(b), nil
}


func (d *Decoder) readUint16() (uint16, error) {
    bs, err := d.readBytes(2)
    if err != nil {
        return 0, err
    }

    return (uint16(bs[0]) << 8) | uint16(bs[1]), nil
}

読み戻す

 こういうパーサを書く時はトークンを1つ読まなかったことにしたり(unread, unget)、ストリームを消費せずに先頭のトークンを読む(peek)処理があると、最終的に必要かどうかはわからないけどデバッグ中とかでは役に立つ。というか実際に後で役に立ったので作ろう。peekはあとでまた読むのが面倒なので一旦unreadにしておこう。最初に書いたDecoderDecoder#readByteMarkerに手を入れる。

 まずDecoderに状態を追加する。

type Decoder struct {
    r *bufio.Reader

    // unread用のフィールド
    prevByte   byte
    prevMarker Marker
    unreaded   bool
}

 Decoder#readByteMarkerでは、値を返す時に返却値をprevByte,prevMarkerに保存し、unreadedtrueの時は保存した値を返すようにする。

func (d *Decoder) readByteMarker() (byte, Marker, error) {
    if d.unreaded {
        d.unreaded = false
        return d.prevByte, d.prevMarker, nil
    }

    b, err := d.r.ReadByte()
    if err != nil {
        return 0, 0, err
    }

    if b != Marker_Prefix {
        d.prevByte = b
        d.prevMarker = 0
        return b, 0, nil
    }

    m, err := d.r.ReadByte()
    if err != nil {
        return 0, 0, err
    }

    if m == Marker_FF {
        d.prevByte = b
        d.prevMarker = 0
        return b, 0, nil
    }

    d.prevByte = 0
    d.prevMarker = Marker(m)
    return 0, Marker(m), nil
}

 unreadの処理本体はこれでいい。

func (d *Decoder) unread() {
    d.unreaded = true
}

 これで今のところ必要な素材は揃ったはず。

JPEGを読む(3) ハフマン符号の復号準備

JPEGを読む(2) ハフマン表定義(DHT) - yunomuのブログ これのつづき

仕様書(PDF) https://www.w3.org/Graphics/JPEG/itu-t81.pdf

 ハフマン符号表があればscanのペイロードをデコードできる。復号手順はAnnex FのF.2に書いてある。

 JPEGでは8×8のブロックごとにDCTをかけて64個のDCT係数を計算する。DCT係数は本文ではZZ(k) (0 ≦ k ≦ 63)と表現されている。8×8=64のブロックをジグザグ(Zig-zag)に走査しているのでZZらしい。

 ZZのうち最初の係数ZZ(0)をDC、以降の63個(ZZ(k) (1 ≦ k ≦ 63))をACと呼ぶ。DCとACでは符号化/復号化の手順と内容が少し違っている。

 DCはDCT係数のリストの中で直前のブロックのDCとの差分が記録されている。つまりi番目のブロックのDCの値DIFFiは以下のようになる。

  • DIFF0 = DC0
  • DIFFi = DCi - DCi-1

 ビット列からDIFFを取り出す手順はF 2.2に書かれている。

T = DECODE
DIFF = RECEIVE(T)
DIFF = EXTEND(DIFF, T)

 この中のDECODE, RECEIVE, EXTENDは以下の通り。

  • DECODE: ハフマン符号の復号処理
  • RECEIVE: 指定したビット数のビット列を読み取る処理
  • EXTEND: ビット列を正負符号を考慮して差分値に戻す処理

 ここでわかる通り、JPEGでハフマン符号化する値は値そのものではなく、値を表現するために必要なビット数だったということになる。値を直接符号化するとハフマン符号表が大きくなりすぎるので、読むべきビット数を符号化してヘッダみたいに付加した方が全体としての圧縮率は上がるということなんだろう。

 具体的には、前回の表に照らし合わせると、例えば1を表現しようとすると、10b1なのでビット長は1。ビット長1を表すハフマン符号語は0b010なので、10b0100b1を合わせて0b0101と表現されることになる。同様に180b10010で5ビットなので、5ビットを表わす符号語0b110を付加して0b11010010となる。冗長な気がしなくもないけど、差分符号化で0付近へのデータの偏りを高めた状態で適用すると値部分のビット長が短くなるので、最終的に圧縮効率を上げることができるのだろう。

 ACの方はFigure F.13, F14にある通り少しだけ複雑になっているけどおおむねDCと同じ。つまりまずはDECODE, RECEIVE, EXTENDあたりを読み解いていこう。

 まずDECODEに先立ってDecoder_tablesというMAXCODE, MINCODE, VALPTRの3つのテーブルを作る。

 これを前回のHUFFCODEテーブルに適用すると以下の3つの表ができあがる。書くのが面倒なのでひとつにまとめた。

ビット長 MAXCODE MINCODE VALPTR
1 -1
2 0 0 0
3 6 2 1
4 14 14 6
5 30 30 7
6 62 62 8
7 126 126 9
8 254 254 10
9 510 510 11
10 -1
11 -1
12 -1
13 -1
14 -1
15 -1
16 -1

 この表はそれぞれ以下のものを表している。

  • MAXCODE: そのビット長の符号語の中で、符号語を数値として見た時に最大値のもの
  • MINCODE: そのビット長の符号語の中で、符号語を数値として見た時に最小値のもの
  • VALPTR: HUFFCODEテーブルの中で、そのビット長の符号語が始まる位置のインデックス

 言われてみれば、切れ目無く符号語と数値が連続したビット列を解読するにあたり、このような補助テーブルがあると便利かもしれない。

 これらの表を格納するにあたり、前回作ったhufftable構造体を拡張する。

type hufftable struct {
    class  uint8 // Tc
    target uint8 // Th
    huffcodes []*huffcode

    maxcode map[int]int
    mincode map[int]int
    valptr  map[int]int
}

 あとはアルゴリズムを整理して実装。前回のmakeHufftableも拡張して補助テーブルも生成するようにする。

func makeDecoderTables(bits [16]uint8, huffcodes []*huffcode) (map[int]int, map[int]int, map[int]int) {
    maxcodes := make(map[int]int)
    mincodes := make(map[int]int)
    valptr := make(map[int]int)
    var j int 
    for i := 0; i < 16; i++ {
        if bits[i] == 0 { 
            maxcodes[i+1] = -1
            continue
        }

        valptr[i+1] = j 
        mincodes[i+1] = int(hufftable[j].code)
        j += int(bits[i]) - 1
        maxcodes[i+1] = int(hufftable[j].code)
        j++
    }

    return maxcodes, mincodes, valptr
}

func makeHufftable(class, target uint8, bits [16]uint8, huffval []*huffval) *hufftable {
    var huffcodes []*huffcode

    // HUFFSIZE
    for i, l := range bits {
        for j := 0; j < int(l); j++ {
            huffcodes = append(huffcodes, &huffcode{
                size: i + 1,
            })
        }
    }

    // HUFFCODE
    var code uint16
    prev := huffcodes[0]
    for _, huffcode := range huffcodes[1:] {
        code++
        size := huffcode.size
        if prev.size != size {
            code <<= size - prev.size
        }
        huffcode.code = code
        prev = huffcode
    }

    // Order_codes
    for i, v := range huffval {
        huffcodes[i].value = v.v
    }

    maxcode, mincode, valptr := makeDecoderTables(bits, huffcodes)

    return &hufftable{
        class:     class,
        target:    target,
        huffcodes: huffcodes,
        maxcode:   maxcode,
        mincode:   mincode,
        valptr:    valptr,
    }
}

 これでハフマン符号デコードの準備はできた。けどいくつかある符号表のどれをどのscanブロックで使うのかがまだわからないので次からは全体の構造を読まなければならない気がする。順序がめちゃくちゃだけど。

JPEGを読む(2) ハフマン表定義(DHT)

JPEGを読む - yunomuのブログ これの続き。

JPEG仕様(PDF) https://www.w3.org/Graphics/JPEG/itu-t81.pdf

 Figure B.16, B17あたりにフィールドが並ぶ位置のルールが書いてあるけど、これは真面目にパーサーを書くとして。

 まずはDHT(Define huffman table(s): ハフマン表定義)を読みたい。ハフマン符号そのものや符号化のアルゴリズム自体は知っていても、実際に符号表をどう定義するのかは意外と知らなかったりする。単純に考えると値と符号の組を連ねればいいのだけど、データ量削減のための符号化でそんなナイーヴなことをしているはずはない。

 定義を見ると非常に単純だ。1つのDHTにいくつかの表が含まれていて、それぞれは用途を示すTc, Thで識別されている。1つの表は2つの部分で構成される。それぞれLiVi, jで表現される。

  • Li (1 ≦ i ≦ 16): 長さがiビットの符号語の数
  • Vi, j (0 ≦ jLi): 長さがiビットでj番目の符号語が表現する値

 これだけで用は足りるらしい。言われてみればハフマン符号は極めて規則的なので、Liさえあれば出現する符号語のリストを作ることができる(と、わかった風に書いたがこれを理解するのに2〜3日かかった)。文書の中ではBITSと呼ばれている。Vi, jはそのリストに対して順に値を割り振るためのリストで、文書の中ではHUFFVALと呼ばれている。

 ここからHUFFSIZE/HUFFCODEと最終的な値の表を構築していく。それぞれの意味は

  • HUFFSIZE: k(0 ≦ klength(HUFFVAL))番目の符号語の長さを表すリスト
  • HUFFCODE: k(0 ≦ klength(HUFFVAL))番目の符号語のリスト

 なので最終的には「符号長」「符号語」「値」の3つ組の表ができることになる。Go言語で表現すると以下のコードで、最終的な目的であるhufftableを作っていくためにsizecodevalueといったフィールドを順に埋めていく作業をする。Tc, Thも一旦ここに格納しておく。

type huffcode struct {
  size  int
  code  uint16
  value uint8
}

type hufftable struct {
    class uint8 // Tc
    target uint8 // Th
    huffcodes []*huffcode
}

 前提として、JPEGのハフマン符号では符号長の最大は16ビットで、値は8ビットであると定義されている。hufftableの大きさも、valueの数だけエントリがあるわけなので当然length(HUFFVAL)と等しくなる。LVからこの表を作るプロセスはフローチャートになっている。

 まずはHUFFSIZEから。(これもわかったように書いているが理解に1日かかっている)

 これはつまりBITS(符号長iビットの値の数のリスト)が以下のようだった場合、

BITS=[0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0]

FUFFSIZE(k番目の値の符号長(size)のリスト)は以下のようになるはずだ。

[2, 3, 3, 3, 3, 3, 4, 5, 6, 7, 8, 9]

 BITS(1)=0なので1ビットの符号は0個、BITS(2)=1なので2ビットの符号は1個、BITS(3)=5なので3ビットの符号は5個、BITS(4)=1なので4ビットの符号は1個、...、というのを表現したのが上のリストになる。

 これをhuffcodeのリストとして表現すると以下のコードになる。

func makeHufftable(class,  target uint8, bits [16]uint8, huffval []uint8) []*huffcode {
    var huffcodes []*huffcode

    // HUFFSIZE
    for i, l := range bits {
        for j := 0; j < int(l); j++ {
            huffcodes = append(huffcodes, &huffcode{
                size: i + 1,
            })
        }
    }

 BITSの値の合計値が12なので、HUFFSIZEの長さも12になる。フローチャートの通りに実装するなら最初にHUFFSIZEの長さを計算するとよさそう。

 次に、hufftablecodeの値、つまりHUFFCODEを埋めていく。フローチャートはこう。

 これが実装は簡単なんだけど意味の理解が結構難しい。符号語のルールとして「すべて"1"の符号語は他の符号語の接頭辞でなければならない(the codes shall be generated such that the all-1-bits code word of any length is reserved as a prefix for longer code words)」というものがあるので、これのおかげで符号長毎の符号語の数のリストだけで符号語のリストを作ることができる。

 フローチャートを読み解くと以下のようになる。

  • 最初の符号語は0 (ビット長にかかわらず)
  • 符号語Ck-1のビット長とCkのビット長が同じ場合、Ck=Ck-1+1
  • 符号語Ck-1ビット長とCkのビット長が異なる場合、Ck=(Ck-1+1)<<1

 これをコードで表現するとこう。1ビットシフトを繰り返す部分はまとめてnビットシフトするようにした。本当はフローチャートのまんま書いた方がいいんだけど(実際は2種類実装してテストしました)。

    // HUFFCODE
    var code uint16
    prev := hufftable[0]
    for _, huffcode := range huffcodes[1:] {
        code++
        if prev.size != huffcode.size {
            code <<= huffcode.size - prev.size
        }
        huffcode.code = code
        prev = huffcode
    }

 最後に出来上がった符号語リストにHUFFVALの値を割り当てていく。

 アルゴリズムはこうだけど、hufftableの値が既にHUFFVALと同じ順序で並んでいるので簡単でいい。

    // Order_codes
    for i, v := range huffval {
        huffcodes[i].value = v
    }   

    return &hufftable {
        class: class,
        target: target,
        huffcodes: huffcodes,
    }
}

 これを並べると以下のような対応表が出来上がる。今回はたまたまvalueがindex値と同じになっているけど、当然出現頻度によってここの順番はバラバラになる。

値(value) 符号語長(size) 符号語(code) 符号語(10進数表記)
0 2 00 0
1 3 010 2
2 3 011 3
3 3 100 4
4 3 101 5
5 3 110 6
6 4 1110 14
7 5 11110 30
8 6 111110 62
9 7 1111110 126
10 8 11111110 254
11 9 111111110 510

 これで8bitの値のリストをハフマン符号化できそうな気がしてきた。

JPEGを読む

 画像や動画のエンコード/デコードの理屈はわかるけど実際どうなっているかはよくわかってないのでとりあえずJPEGファイルの仕様を調べてみる。

資料 https://www.w3.org/Graphics/JPEG/itu-t81.pdf

 JISの日本語の資料もあったけど(JISX4301)、閲覧が面倒くさすぎて英語で読む方がまだマシであった。

 上から読んでいっても理解できなさそうなのでJPEGファイルのフォーマットから読みつつ、実際にJPEGファイルを読むコードも書いていく。"Annex B: Compressed data formats"から。

 基本的にはデータやパラメータやハフマン符号のテーブルやらのフィールドが並んでいて、それぞれのフィールドは先頭2バイトのMarkerと呼ばれるマジックナンバーで判別できるようになっている。それでフィールドの中にヘッダやペイロードがあったりなかったりして、最後に終端のMarkerがついて終わり。

 このMarkerというのは必ず0xFFから始まる2バイトで、Annex Bの最初の方に全部の定義が書いてある。JPEGファイル内で0xFFから始まる2バイトはMarkerしかなくて、データ内に偶然に出現する0xFF0xFF00エスケープされる。ということらしい。

 それならとりあえずMarkerだけを取り出して並べて見るとなんとなくの構造がわかりそう。こんなかんじで↓

package main

import (
    "bufio"
    "io"
    "log/slog"
    "os"
    "strconv"
    "strings"
)

func marker(i uint64) string {
    return "0xFF" + strings.ToUpper(strconv.FormatUint(uint64(i), 16))
}

func main() {
    r := bufio.NewReader(os.Stdin)

    for {
        b, err := r.ReadByte()
        if err == io.EOF {
            slog.Info("FINISHED")
            return
        } else if err != nil {
            slog.Error("ReadByte", "err", err)
            return
        }

        if b != 0xFF {
            continue
        }

        m, err := r.ReadByte()
        if err == io.EOF {
            slog.Error("unexpected EOF")
            return
        } else if err != nil {
            slog.Error("read marker", "err", err)
            return
        }

        if m == 0x0 {
            continue
        }

        slog.Info("marker", "val", marker(uint64(m)))
    }
}

 これで適当なファイルを読む。

 go run main.go < test.jpg
INFO marker val=0xFFD8
INFO marker val=0xFFDB
INFO marker val=0xFFC0
INFO marker val=0xFFC4
INFO marker val=0xFFDA
INFO marker val=0xFFD9
INFO FINISHED

 意外と少ない。これを表B.1と見比べてみると

  • 0xFFD8 Start of image : ファイルの先頭
  • 0xFFDB Define quantization table(s) : そのまんま、量子化係数のテーブル
  • 0xFFC0 Baseline DCT : SOF(start of frame)。圧縮の方式はbaseline(progressiveやlosslessじゃないやつ)で、non-differentialで離散コサイン変換(DCT)。
  • 0xFFC4 Define Huffman table(s) : これもそのまんま、ハフマン符号表。
  • 0xFFDA Start of scan : Figure B.2からみるとここに符号化データが入ってる。
  • 0xFFD9 End of image : ファイルの終端

 ハフマン符号化は元データのヒストグラムに偏りがある方が圧縮効率が良くなるので、元画像の特性によってアルゴリズムを変更できるようになっている。

 次はこの中のどれかのフィールドの中身を見ていこう。

スケジュール帳に何を書くか

 大抵のものには日付や時刻のデータが付随しているので全部スケジュールとしてカレンダーに載せたくなる。けどスケジュール帳に載せづらいデータというものがたくさんある。

 会議や飲み会の予定は明らかで、カレンダーのその日付に書き込んでもGoogleカレンダーなどのアプリに登録しても何の違和感も無い。

「夏休み」「冬休み」といった予定は、予定ではあるものの範囲があるし、その日になにかをすると決まった予定ではないのでカレンダーに書くと少し面倒くさい。土日祝日のようにあらかじめカレンダーに書かれている通りに休暇があるならいいけど、そうでない休暇も予定として書くには違和感がある。予定の重複を警告してくるタイプのスケジュールアプリだったりするととても面倒くさい。そもそも予定は重複するし運用するものだろう。今は関係ないけど。

 休暇などは、やることではないので会議の予定などとはちょっと性質が違う。

 TODOは、やることのリストであり、日付はあったりなかったりする。このうち日付のあるものはカレンダーに反映したくなるが、TODOにおける日付というものはおおむね締め切りであることが多く、必ずしもその日にやる必要はないことがある。前日にやってもいいし、1週間前でもいい。そういう意味では締め切り付きのTODOは今日の予定でもありうる。締め切りが過ぎたからといって消えないでほしいTODOもある。

 TODOの範囲がある場合もある。何かのチケットや商品の予約開始日と終了日がある場合、予約開始日まではTODOではなく、終了日を過ぎると無効になる。

 映画の公開日のように、公開後にいつかやればいいタイプのTODOもある。日時を決めた段階でスケジュールになるし、たまたまいいタイミングで映画館に行けたらそれはそれで完了になる。

 このようにカレンダーの側からTODOを見ると、範囲を持っているものが多く、カレンダーのビューで表示すると見づらい。逆にTODO側から見ると、今日の予定も締め切りのあるTODOも無いTODOも今やるべきことリストとして並んでいても違和感は無い。ついでに明日からやるべきことも並んでくれていい。

 ここまではビューの切り替えだったり、カレンダーとノートの間の転記でなんとかなる気がする。

 曖昧な予定のようなものというのが結構ある。

 やることといっても厚みがあるものがTODOとして並んでいるとちょっとやりづらい。厚みというか時間で、人によって感覚は違うかもしれないけど半日かかるものはそれはもうTODOではなく予定を立てなければならない。けどいつでもいい。予定を立てるTODOを作りたい。みたいなことが結構ある。歯医者から定期検診のおはがきが来た場合などがこれにあたる。

「だいたいこの日までに機能を作りたい」けど作るのに3日かかる。こういうのはもうプロジェクト管理だ。

「問い合わせしたら『月曜日まで待って』って言われた」という時、これって月曜日開始のTODOとして管理するの? 返答如何でその後に何かをやらなきゃいけないとか言いだすと、これもTODOを作るためのTODOという気がする。

「お祭りあるけど行っても行かなくてもいい」「ゲームの発売日だけどその日以降ならいつ買ってもいい」というような、予定やTODOというほどではないんだけど覚えておきたいようなもの。忘れてもまあいいか。

「毎年この時期に飲み会やってるよな」「隔月くらいで集まってるけど次回を決めるのをよく忘れる」「春になったら種を蒔こう」とかいうぼんやりと定期的なもの。毎年やる飲み会では毎回最初に「去年は死ぬほど暑かったけど今年は涼しいね」などその日の気候の話題で盛り上がるので、ぼんやりとした周期を考えると二十四節気みたいに細かく名前をつけるのは良いアイデアな気がする。冬至のかぼちゃとか。

 思いつくまま書いてみたけど、このあたりの概念を分類したり名前つけたりして、漠然としたスケジュールアプリに対する不満が整理できないものかなと考えていた。

 それでとりあえず毎年送られてくるけど使っていなかった紙の手帳を試しに久しぶりに今年は使ってみようかなと思って使いはじめたところ。どこに何を書いたら便利なのかまだよくわからない。