はじめに

image」および「image/color」パッケージは、いくつかの型を定義しています:color.Colorおよびcolor.Modelは色を説明し、image.Pointおよびimage.Rectangleは基本的な2次元ジオメトリを説明し、image.Imageはこれら2つの概念を組み合わせて色の長方形グリッドを表現します。「別の記事」では、「image/draw」パッケージを使用した画像合成について説明しています。

色とカラーモデル

Colorは、色と見なされる任意の型の最小メソッドセットを定義するインターフェースです:赤、緑、青、アルファ値に変換できるものです。変換は、CMYKやYCbCrカラースペースからの変換のように、損失がある場合があります。

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

戻り値に関する3つの重要な微妙な点があります。まず、赤、緑、青はアルファプリマルチプライされたもので、完全に飽和した赤が25%透明である場合、RGBAは75%のrを返します。第二に、チャンネルは16ビットの有効範囲を持ち、100%の赤はRGBAが65535のrを返し、255ではないため、CMYKやYCbCrからの変換はそれほど損失がありません。第三に、返される型はuint32であり、最大値が65535であっても、2つの値を掛け合わせてもオーバーフローしないことを保証します。このような掛け算は、3番目の色からのアルファマスクに従って2つの色をブレンドする際に発生します。これはPorter and Duffの古典的な代数のスタイルです:

  1. dstr, dstg, dstb, dsta := dst.RGBA()
  2. srcr, srcg, srcb, srca := src.RGBA()
  3. _, _, _, m := mask.RGBA()
  4. const M = 1<<16 - 1
  5. // The resultant red value is a blend of dstr and srcr, and ranges in [0, M].
  6. // The calculation for green, blue and alpha is similar.
  7. dstr = (dstr*(M-m) + srcr*m) / M

そのコードスニペットの最後の行は、非アルファプリマルチプライされた色で作業していた場合、より複雑になっていたでしょう。これがColorがアルファプリマルチプライされた値を使用する理由です。

image/colorパッケージは、Colorインターフェースを実装するいくつかの具体的な型も定義しています。たとえば、RGBAは、古典的な「チャンネルあたり8ビット」色を表す構造体です。

  1. type RGBA struct {
  2. R, G, B, A uint8
  3. }

RGBARフィールドは、範囲[0, 255]の8ビットアルファプリマルチプライされた色です。RGBAは、その値を0x101で掛けて、範囲[0, 65535]の16ビットアルファプリマルチプライされた色を生成することによってColorインターフェースを満たします。同様に、NRGBA構造体型は、PNG画像形式で使用される8ビット非アルファプリマルチプライされた色を表します。NRGBAのフィールドを直接操作する場合、値は非アルファプリマルチプライされていますが、RGBAメソッドを呼び出すと、戻り値はアルファプリマルチプライされます。

Modelは、Colorを他のColorに変換できるものです。損失がある可能性があります。たとえば、GrayModelは任意のColorを脱飽和Grayに変換できます。Paletteは、任意のColorを限られたパレットからのものに変換できます。

  1. type Model interface {
  2. Convert(c Color) Color
  3. }
  4. type Palette []Color

点と長方形

Pointは、整数グリッド上の(x, y)座標で、軸は右と下に増加します。これはピクセルでもグリッドの正方形でもありません。Pointは固有の幅、高さ、色を持たず、以下の視覚化では小さな色付きの正方形を使用しています。

  1. type Point struct {
  2. X, Y int
  3. }

Go画像パッケージ(The Go image package) - img1

  1. p := image.Point{2, 1}

Rectangleは、整数グリッド上の軸に整列した長方形で、左上と右下のPointによって定義されます。Rectangleも固有の色を持たず、以下の視覚化では薄い色付きの線で長方形をアウトラインし、そのMinおよびMaxPointを呼び出します。

  1. type Rectangle struct {
  2. Min, Max Point
  3. }

便利なことに、image.Rect(x0, y0, x1, y1)image.Rectangle{image.Point{x0, y0}, image.Point{x1, y1}}と同等ですが、入力がはるかに簡単です。

Rectangleは、左上で包含し、右下で排他的です。Point pRectangle rの場合、p.In(r)r.Min.X <= p.X && p.X < r.Max.Xの場合に限り、同様にYに対しても同様です。これは、スライスs[i0:i1]が低い端で包含し、高い端で排他的であるのと類似しています。(配列やスライスとは異なり、Rectangleはしばしばゼロ以外の原点を持ちます。)

Go画像パッケージ(The Go image package) - img2

  1. r := image.Rect(2, 1, 5, 5)
  2. // Dx and Dy return a rectangle's width and height.
  3. fmt.Println(r.Dx(), r.Dy(), image.Pt(0, 0).In(r)) // prints 3 4 false
  1. ![](https://cdn.hedaai.com/projects/go-latest/6999ad232314fbc45f59d75b2ccb053f.png_big1500.jpeg)
  2. ``````bash
  3. r := image.Rect(2, 1, 5, 5).Add(image.Pt(-4, -2))
  4. fmt.Println(r.Dx(), r.Dy(), image.Pt(0, 0).In(r)) // prints 3 4 true
  5. `

2つの長方形が交差すると、別の長方形が得られ、空である可能性があります。

Go画像パッケージ(The Go image package) - img3

  1. r := image.Rect(0, 0, 4, 3).Intersect(image.Rect(2, 2, 5, 5))
  2. // Size returns a rectangle's width and height, as a Point.
  3. fmt.Printf("%#v\n", r.Size()) // prints image.Point{X:2, Y:1}

点と長方形は値によって渡され、返されます。Rectangle引数を取る関数は、2つのPoint引数を取る関数や4つのint引数を取る関数と同じくらい効率的です。

画像

https://golang.org/pkg/image/#Imageは、Rectangleの各グリッド正方形をColorからModelにマッピングします。「(x, y)のピクセル」は、点(x, y)、(x+1, y)、(x+1, y+1)、(x, y+1)によって定義されるグリッド正方形の色を指します。

  1. type Image interface {
  2. // ColorModel returns the Image's color model.
  3. ColorModel() color.Model
  4. // Bounds returns the domain for which At can return non-zero color.
  5. // The bounds do not necessarily contain the point (0, 0).
  6. Bounds() Rectangle
  7. // At returns the color of the pixel at (x, y).
  8. // At(Bounds().Min.X, Bounds().Min.Y) returns the upper-left pixel of the grid.
  9. // At(Bounds().Max.X-1, Bounds().Max.Y-1) returns the lower-right one.
  10. At(x, y int) color.Color
  11. }

一般的な間違いは、Imageの境界が(0, 0)から始まると仮定することです。たとえば、アニメーションGIFは画像のシーケンスを含み、最初の後の各Imageは通常、変更された領域のピクセルデータのみを保持し、その領域は必ずしも(0, 0)から始まるわけではありません。Imagemのピクセルを反復処理する正しい方法は次のようになります:

  1. b := m.Bounds()
  2. for y := b.Min.Y; y < b.Max.Y; y++ {
  3. for x := b.Min.X; x < b.Max.X; x++ {
  4. doStuffWith(m.At(x, y))
  5. }
  6. }
  1. ``````bash
  2. type Uniform struct {
  3. C color.Color
  4. }
  5. `

ただし、通常、プログラムはスライスに基づく画像を望みます。RGBAGrayのような構造体型(他のパッケージではimage.RGBAimage.Grayと呼ばれます)は、ピクセルデータのスライスを保持し、Imageインターフェースを実装します。

  1. type RGBA struct {
  2. // Pix holds the image's pixels, in R, G, B, A order. The pixel at
  3. // (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*4].
  4. Pix []uint8
  5. // Stride is the Pix stride (in bytes) between vertically adjacent pixels.
  6. Stride int
  7. // Rect is the image's bounds.
  8. Rect Rectangle
  9. }

これらの型は、画像を1ピクセルずつ変更することを可能にするSet(x, y int, c color.Color)メソッドも提供します。

  1. m := image.NewRGBA(image.Rect(0, 0, 640, 480))
  2. m.Set(5, 5, color.RGBA{255, 0, 0, 255})

多くのピクセルデータを読み書きしている場合、これらの構造体型のPixフィールドに直接アクセスする方が効率的ですが、より複雑です。

スライスベースのImage実装は、同じ配列に基づくSubImageメソッドも提供し、サブ画像のピクセルを変更すると、元の画像のピクセルに影響を与えます。これは、サブスライスs[i0:i1]の内容を変更すると、元のスライスsの内容に影響を与えるのと類似しています。

Go画像パッケージ(The Go image package) - img4

  1. m0 := image.NewRGBA(image.Rect(0, 0, 8, 5))
  2. m1 := m0.SubImage(image.Rect(1, 2, 5, 5)).(*image.RGBA)
  3. fmt.Println(m0.Bounds().Dx(), m1.Bounds().Dx()) // prints 8, 4
  4. fmt.Println(m0.Stride == m1.Stride) // prints true

画像のPixフィールドで動作する低レベルのコードでは、Pixを範囲指定すると、画像の境界外のピクセルに影響を与える可能性があることに注意してください。上記の例では、m1.Pixでカバーされるピクセルが青でシェーディングされています。AtSetメソッド、またはimage/drawパッケージなどの高レベルのコードは、操作を画像の境界にクリップします。

画像フォーマット

標準パッケージライブラリは、GIF、JPEG、PNGなどの一般的な画像フォーマットをサポートしています。ソース画像ファイルのフォーマットがわかっている場合、io.Readerから直接デコードできます。

  1. import (
  2. "image/jpeg"
  3. "image/png"
  4. "io"
  5. )
  6. // convertJPEGToPNG converts from JPEG to PNG.
  7. func convertJPEGToPNG(w io.Writer, r io.Reader) error {
  8. img, err := jpeg.Decode(r)
  9. if err != nil {
  10. return err
  11. }
  12. return png.Encode(w, img)
  13. }

フォーマットが不明な画像データがある場合、image.Decode関数がフォーマットを検出できます。認識されたフォーマットのセットは実行時に構築され、標準パッケージライブラリにあるものに限定されません。画像フォーマットパッケージは通常、init関数でそのフォーマットを登録し、メインパッケージはフォーマット登録の副作用のためだけにそのようなパッケージを「アンダースコアインポート」します。

  1. import (
  2. "image"
  3. "image/png"
  4. "io"
  5. _ "code.google.com/p/vp8-go/webp"
  6. _ "image/jpeg"
  7. )
  8. // convertToPNG converts from any recognized format to PNG.
  9. func convertToPNG(w io.Writer, r io.Reader) error {
  10. img, _, err := image.Decode(r)
  11. if err != nil {
  12. return err
  13. }
  14. return png.Encode(w, img)
  15. }