はじめに
「image」および「image/color」パッケージは、いくつかの型を定義しています:color.Color
およびcolor.Model
は色を説明し、image.Point
およびimage.Rectangle
は基本的な2次元ジオメトリを説明し、image.Image
はこれら2つの概念を組み合わせて色の長方形グリッドを表現します。「別の記事」では、「image/draw」パッケージを使用した画像合成について説明しています。
色とカラーモデル
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つの重要な微妙な点があります。まず、赤、緑、青はアルファプリマルチプライされたもので、完全に飽和した赤が25%透明である場合、RGBAは75%のrを返します。第二に、チャンネルは16ビットの有効範囲を持ち、100%の赤はRGBAが65535のrを返し、255ではないため、CMYKやYCbCrからの変換はそれほど損失がありません。第三に、返される型はuint32
であり、最大値が65535であっても、2つの値を掛け合わせてもオーバーフローしないことを保証します。このような掛け算は、3番目の色からのアルファマスクに従って2つの色をブレンドする際に発生します。これはPorter and Duffの古典的な代数のスタイルです:
dstr, dstg, dstb, dsta := dst.RGBA()
srcr, srcg, srcb, srca := src.RGBA()
_, _, _, m := mask.RGBA()
const M = 1<<16 - 1
// The resultant red value is a blend of dstr and srcr, and ranges in [0, M].
// The calculation for green, blue and alpha is similar.
dstr = (dstr*(M-m) + srcr*m) / M
そのコードスニペットの最後の行は、非アルファプリマルチプライされた色で作業していた場合、より複雑になっていたでしょう。これがColor
がアルファプリマルチプライされた値を使用する理由です。
image/colorパッケージは、Color
インターフェースを実装するいくつかの具体的な型も定義しています。たとえば、RGBA
は、古典的な「チャンネルあたり8ビット」色を表す構造体です。
type RGBA struct {
R, G, B, A uint8
}
RGBA
のR
フィールドは、範囲[0, 255]の8ビットアルファプリマルチプライされた色です。RGBA
は、その値を0x101で掛けて、範囲[0, 65535]の16ビットアルファプリマルチプライされた色を生成することによってColor
インターフェースを満たします。同様に、NRGBA
構造体型は、PNG画像形式で使用される8ビット非アルファプリマルチプライされた色を表します。NRGBA
のフィールドを直接操作する場合、値は非アルファプリマルチプライされていますが、RGBA
メソッドを呼び出すと、戻り値はアルファプリマルチプライされます。
Model
は、Color
を他のColor
に変換できるものです。損失がある可能性があります。たとえば、GrayModel
は任意のColor
を脱飽和Gray
に変換できます。Palette
は、任意のColor
を限られたパレットからのものに変換できます。
type Model interface {
Convert(c Color) Color
}
type Palette []Color
点と長方形
Point
は、整数グリッド上の(x, y)座標で、軸は右と下に増加します。これはピクセルでもグリッドの正方形でもありません。Point
は固有の幅、高さ、色を持たず、以下の視覚化では小さな色付きの正方形を使用しています。
type Point struct {
X, Y int
}
p := image.Point{2, 1}
Rectangle
は、整数グリッド上の軸に整列した長方形で、左上と右下のPoint
によって定義されます。Rectangle
も固有の色を持たず、以下の視覚化では薄い色付きの線で長方形をアウトラインし、そのMin
およびMax
のPoint
を呼び出します。
type Rectangle struct {
Min, Max Point
}
便利なことに、image.Rect(x0, y0, x1, y1)
はimage.Rectangle{image.Point{x0, y0}, image.Point{x1, y1}}
と同等ですが、入力がはるかに簡単です。
Rectangle
は、左上で包含し、右下で排他的です。Point p
とRectangle r
の場合、p.In(r)
はr.Min.X <= p.X && p.X < r.Max.X
の場合に限り、同様にY
に対しても同様です。これは、スライスs[i0:i1]
が低い端で包含し、高い端で排他的であるのと類似しています。(配列やスライスとは異なり、Rectangle
はしばしばゼロ以外の原点を持ちます。)
r := image.Rect(2, 1, 5, 5)
// Dx and Dy return a rectangle's width and height.
fmt.Println(r.Dx(), r.Dy(), image.Pt(0, 0).In(r)) // prints 3 4 false
![](https://cdn.hedaai.com/projects/go-latest/6999ad232314fbc45f59d75b2ccb053f.png_big1500.jpeg)
``````bash
r := image.Rect(2, 1, 5, 5).Add(image.Pt(-4, -2))
fmt.Println(r.Dx(), r.Dy(), image.Pt(0, 0).In(r)) // prints 3 4 true
`
2つの長方形が交差すると、別の長方形が得られ、空である可能性があります。
r := image.Rect(0, 0, 4, 3).Intersect(image.Rect(2, 2, 5, 5))
// Size returns a rectangle's width and height, as a Point.
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)によって定義されるグリッド正方形の色を指します。
type Image interface {
// ColorModel returns the Image's color model.
ColorModel() color.Model
// Bounds returns the domain for which At can return non-zero color.
// The bounds do not necessarily contain the point (0, 0).
Bounds() Rectangle
// At returns the color of the pixel at (x, y).
// At(Bounds().Min.X, Bounds().Min.Y) returns the upper-left pixel of the grid.
// At(Bounds().Max.X-1, Bounds().Max.Y-1) returns the lower-right one.
At(x, y int) color.Color
}
一般的な間違いは、Image
の境界が(0, 0)から始まると仮定することです。たとえば、アニメーションGIFは画像のシーケンスを含み、最初の後の各Image
は通常、変更された領域のピクセルデータのみを保持し、その領域は必ずしも(0, 0)から始まるわけではありません。Image
mのピクセルを反復処理する正しい方法は次のようになります:
b := m.Bounds()
for y := b.Min.Y; y < b.Max.Y; y++ {
for x := b.Min.X; x < b.Max.X; x++ {
doStuffWith(m.At(x, y))
}
}
``````bash
type Uniform struct {
C color.Color
}
`
ただし、通常、プログラムはスライスに基づく画像を望みます。RGBA
やGray
のような構造体型(他のパッケージではimage.RGBA
やimage.Gray
と呼ばれます)は、ピクセルデータのスライスを保持し、Image
インターフェースを実装します。
type RGBA struct {
// Pix holds the image's pixels, in R, G, B, A order. The pixel at
// (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*4].
Pix []uint8
// Stride is the Pix stride (in bytes) between vertically adjacent pixels.
Stride int
// Rect is the image's bounds.
Rect Rectangle
}
これらの型は、画像を1ピクセルずつ変更することを可能にするSet(x, y int, c color.Color)
メソッドも提供します。
m := image.NewRGBA(image.Rect(0, 0, 640, 480))
m.Set(5, 5, color.RGBA{255, 0, 0, 255})
多くのピクセルデータを読み書きしている場合、これらの構造体型のPix
フィールドに直接アクセスする方が効率的ですが、より複雑です。
スライスベースのImage
実装は、同じ配列に基づくSubImage
メソッドも提供し、サブ画像のピクセルを変更すると、元の画像のピクセルに影響を与えます。これは、サブスライスs[i0:i1]
の内容を変更すると、元のスライスs
の内容に影響を与えるのと類似しています。
m0 := image.NewRGBA(image.Rect(0, 0, 8, 5))
m1 := m0.SubImage(image.Rect(1, 2, 5, 5)).(*image.RGBA)
fmt.Println(m0.Bounds().Dx(), m1.Bounds().Dx()) // prints 8, 4
fmt.Println(m0.Stride == m1.Stride) // prints true
画像のPix
フィールドで動作する低レベルのコードでは、Pix
を範囲指定すると、画像の境界外のピクセルに影響を与える可能性があることに注意してください。上記の例では、m1.Pix
でカバーされるピクセルが青でシェーディングされています。At
やSet
メソッド、またはimage/drawパッケージなどの高レベルのコードは、操作を画像の境界にクリップします。
画像フォーマット
標準パッケージライブラリは、GIF、JPEG、PNGなどの一般的な画像フォーマットをサポートしています。ソース画像ファイルのフォーマットがわかっている場合、io.Reader
から直接デコードできます。
import (
"image/jpeg"
"image/png"
"io"
)
// convertJPEGToPNG converts from JPEG to PNG.
func convertJPEGToPNG(w io.Writer, r io.Reader) error {
img, err := jpeg.Decode(r)
if err != nil {
return err
}
return png.Encode(w, img)
}
フォーマットが不明な画像データがある場合、image.Decode
関数がフォーマットを検出できます。認識されたフォーマットのセットは実行時に構築され、標準パッケージライブラリにあるものに限定されません。画像フォーマットパッケージは通常、init関数でそのフォーマットを登録し、メインパッケージはフォーマット登録の副作用のためだけにそのようなパッケージを「アンダースコアインポート」します。
import (
"image"
"image/png"
"io"
_ "code.google.com/p/vp8-go/webp"
_ "image/jpeg"
)
// convertToPNG converts from any recognized format to PNG.
func convertToPNG(w io.Writer, r io.Reader) error {
img, _, err := image.Decode(r)
if err != nil {
return err
}
return png.Encode(w, img)
}