Technical

Go image パッケージ(The Go image package)

Go image パッケージ #

The Go image package By Nigel Tao

はじめに #

imageimage/color パッケージはいくつかの型を定義しています。color.Colorcolor.Model は色を、image.Pointimage.Rectangle は基本的な2次元幾何学をそれぞれ記述しており、image.Image は色の長方形格子を表現するためにそれら2つの概念を1つにまとめます。別記事 では image/draw を用いた画像の構成について取り上げています。

Color と Color Model #

Color は色として考えられる任意の型をまとめた最小限のメソッドの組を定義したインターフェースです。つまり、赤、緑、青、アルファ値に変換できるものです。CMYK や YCbCr 色空間から変換するといったある種の変換は不可逆かもしれません。

type Color interface {
    // RGBA returns the alpha-premultiplied red, green, blue and alpha values
    // for the color. Each value ranges within [0, 0xFFFF], but is represented
    // by a uint32 so that multiplying by a blend factor up to 0xFFFF will not
    // overflow.
    RGBA() (r, g, b, a uint32)
}

戻り値には3つの重要な巧妙さがあります。1つ目は、赤、緑、青はプリマルチプライド・アルファであることです。25% 透明な完全飽和赤は 75% の r を返す RGBA によって表現されます。2つ目に、チャネルの有効範囲は 16 ビットです。100% の赤は 255 ではなく 65535 の r を返す RGBA によって表現されているので、CMYK や YCbCr からの変換はさほど情報量を失いません。3つ目に、最大値は 65535 ですが、戻り値の型は uint32 となっていることです。これは2つの値の積がオーバーフローしないことを保証するためです。Porter-Duff の古典代数学のスタイルでアルファマスクに従う2つの色を混ぜ合わせて第3の色を生成する乗算の際に起ります。

...

GIFデコーダ: Goインターフェースの練習(A GIF decoder: an exercise in Go interfaces)

GIFデコーダ: Goインターフェースの練習 #

A GIF decoder: an exercise in Go interfaces by Rob Pike

はじめに #

2011年5月10日にサンフランシスコで行われたGoogle I/Oのカンファレンスで、私たちはGo言語がGoogle App Engineで利用可能になったことを発表しました。Goは機械語に直接コンパイルするApp Engine上で利用可能となった最初の言語で、画像処理のようなCPUに負荷をかけるタスクにとってそれは良い選択でした。

その流れで、私たちは以下のような画像を手軽により良くする Moustachio と呼ばれるプログラムを実演しました:

gif-decoder-exercise-in-go-interfaces_image00

髭を加えて、その結果を共有しましょう:

gif-decoder-exercise-in-go-interfaces_image02

アンチエイリアスが施された髭の描画を含む全てのグラフィック処理は、App Engine上で動作しているGoプログラムで完結します。(そのソースコードは appengine-goプロジェクト で利用可能です。)

Web上のほとんどの画像 - 少なくとも髭加工される可能性のある - がJPEGであるにも関わらず、ほかにも数えきれないほど広まっている画像形式があり、そしてアップロードされた数種類の画像形式を受け入れるのでそれは髭にとってもの分かりが良いように見えます。JPEGとPNGのデコーダはすでにGoの画像ライブラリの中にありましたが、昔からあるGIF形式のデコーダはなかったので、私たちは発表に間に合うようにGIFデコーダを書くとこにしました。そのデコーダは、問題解決のためにGoのインターフェースがどのようにしてその問題をより扱いやすくしているのかを示すいくつかのピースを含んでいます。このブログ記事の残りの部分ではその2、3個の例を述べています。

GIFのフォーマット #

まず初めに、GIFの形式について簡単に見ていきましょう。GIFの画像ファイルはパレット化されており、つまりそれぞれのピクセル値はファイルに含まれているある決まったカラーマップにインデックス付けされています。ディスプレイの1ピクセルがたった8ビットで表されていた頃からGIF形式はあり、カラーマップは値の制限された組をスクリーンを明るくするために必要なRGB(赤、緑、青)の3値に変換するために使われていました。(これはJPEGとは対称的で、例えば、JPEGはエンコーダが直接カラー信号の分離を表現するためJPEGにはカラーマップはありません。)

GIF画像は1ピクセル当たり1から8ビットの値をとることができ、包括的ですが、1ピクセルあたり8ビットが最も使われています。

少し単純化すると、GIFファイルはピクセル深度、画像の次元、カラーマップ(1枚の8ビット画像あたり256色のRGB値)をそれぞれ定義するヘッダーと、次いでピクセルデータを含んでいます。ピクセルデータは1次元のビットストリームとして格納され、写真には向きませんがコンピュータが生成するグラフィックスにとってはかなり効率的なLZWアルゴリズムを使って圧縮されます。そのとき圧縮データはある長さで区切られ、1バイトのカウント(0-255)とそれに続くバイト列という構成のブロックに分割されます:

gif-decoder-exercise-in-go-interfaces_image03

ピクセルデータのデブロッキング #

GIFのピクセルデータをGoでデコードするために、compress/lzw パッケージからLZWデコンプレッサを使うことができます。そのパッケージには、ドキュメント曰く、「rから読み出したデータを解凍することによって読み出し可能となる」オブジェクトを返すNewReader関数があります。

func NewReader(r io.Reader, order Order, litWidth int) io.ReadCloser

ここで、orderはビットデータをパックする順番を定義し、litWidthはGIFファイルとピクセル深度(典型的には8)を対応させる際に使用するビット単位のワードサイズを意味しています。

しかし、NewReader の最初の引数として入力ファイルを与えることはできません。それはデコンプレッサがバイトストリームを要求しているにも関わらずGIFデータはアンパックが必要なブロックストリームになっているからです。この問題を扱うために、それをデブロッキングするためのちょっとしたコードにより入力 io.Reader をラップすることができます。さらにそのコードを再び Reader として実装することもできます。つまり、デブロッキングするコードを blockReader と呼ばれる新しい型の Read メソッドの中で実装します。

以下は blockReader のデータ構造です。

type blockReader struct {
    r     reader    // Input source; implements io.Reader and io.ByteReader.
    slice []byte    // Buffer of unread data.
    tmp   [256]byte // Storage for slice.
}

リーダー r は画像データのソースであり、そのソースは恐らくファイルかHTTP接続でしょう。slicetmp フィールドはデブロッキングを管理するために使われます。以下は Read メソッドの全体像です。

...

Goの並行パターン:タイムアウトと進行(Go Concurrency Patterns: Timing out, moving on)

Goの並行パターン:タイムアウトと進行 #

Go Concurrency Patterns: Timing out, moving on by Andrew Gerrand

並行プログラミングにはイディオムがあります。良い例はタイムアウトです。 Goのチャンネルではタイムアウトを直接はサポートしていませんが、その実装は容易です。 たとえば、ch チャンネルから値を受信したいけれど、1秒以上は待ちたくないという状況を考えてみましょう。 まずシグナル用のチャンネルを作り、そのチャンネルに送信する前に1秒待つゴルーチンを起動します。

timeout := make(chan bool, 1)
go func() {
    time.Sleep(1 * time.Second)
    timeout <- true
}()

その後、select構文を使って chtimeout を待つようにします。 もし1秒待っても ch から何も来なければ、 timeout のケースが選択され、chからの読み込みは破棄されます。

select {
case <-ch:
    // chから読み込む
case <-timeout:
    // chからの読み込みはタイムアウト
}

timeout チャンネルは1つの値をバッファし、タイムアウトのゴルーチンがそのチャンネルに値を送り、終了できるようになっています。 このゴルーチンは、chから値が受け取られたかを知りません。(あるいは気にしていません) つまりこのゴルーチンはchからの読み込みがタイムアウトより前に起こったとしても、永遠には存在しえません。 timeout チャンネルは最終的にガベージコレクタによって回収されます。

(この例ではゴルーチンとチャンネルの機構をデモするために time.Sleep を使いました。 実際のプログラムでは time.After という、チャンネルを返し、決まった時間のあとにそのチャンネルに値を送る関数を使うべきでしょう。)

このパターンの他の例を見てみましょう。この例では複数のレプリケーションされたデータベースから同時に読み込むプログラムを扱っています。 このプログラムでは、値は1つだけ必要で最初に来た値だけを取得すべきです。

Query 関数はデータベース接続のスライスと問い合わせの文字列を引数に取ります。 この関数は各データベースに並列に問い合わせ、最初に受信した結果を返します。

func Query(conns []Conn, query string) Result {
    ch := make(chan Result, 1)
    for _, conn := range conns {
        go func(c Conn) {
            select {
            case ch <- c.DoQuery(query):
            default:
            }
        }(conn)
    }
    return <-ch
}

この例では、クロージャーがノンブロッキングに送信します。これは、 default ケース付きの select 構文内の送信操作を使うことで実現しています。 もし送信が出来なければ、直ちに default ケースが選択されます。 送信をノンブロッキングにすることで、ループ内で立ち上げられたゴルーチンが1つも無駄に生存しないことが保証されます。 しかしながら、親の関数が値を受信する前に結果が来れば、チャンネルのバッファの準備ができていないため送信は失敗する可能性があります。

...