The Go Blog
Пакет image в Go
Введение
Пакеты image и image/color
определяют несколько типов:
color.Color и color.Model описывают цвета,
image.Point и image.Rectangle описывают базовую 2D-геометрию,
а image.Image объединяет эти два понятия, чтобы представить прямоугольную сетку цветов.
Отдельная статья
рассматривает композицию изображений с использованием пакета image/draw.
Цвета и цветовые модели
Color — это интерфейс, который определяет минимальный набор методов любого типа, который может рассматриваться как цвет: тип, который может быть преобразован в значения красного, зеленого, синего и альфа. Преобразование может быть потенциально потери, например, при преобразовании из цветовых пространств CMYK или YCbCr.
<code>type Color interface {
// RGBA возвращает альфа-предварительно умноженные значения красного, зеленого, синего и альфа
// Для каждого значения диапазон [0, 0xFFFF], но представлено как uint32,
// чтобы при умножении на коэффициент смешивания до 0xFFFF не происходило переполнения.
RGBA() (r, g, b, a uint32)
}
</code>
Существует три важных нюанса в возвращаемых значениях.
Во-первых, красный, зеленый и синий каналы предварительно умножены на альфа:
полностью насыщенный красный с прозрачностью 25% представлен значением RGBA, возвращающим 75% r.
Во-вторых, каналы имеют эффективный 16-битный диапазон:
100% красный представлен значением RGBA, возвращающим r равным 65535,
а не 255, чтобы преобразование из CMYK или YCbCr было менее потери.
В-третьих, возвращаемый тип — uint32, даже если максимальное значение 65535,
чтобы гарантировать, что умножение двух значений не приведет к переполнению.
Такие умножения происходят при смешивании двух цветов согласно альфа-маске третьего цвета,
в стиле классической алгебры Портера и Дэфф:
<code>dstr, dstg, dstb, dsta := dst.RGBA() srcr, srcg, srcb, srca := src.RGBA() _, _, _, m := mask.RGBA() const M = 1<<16 - 1 // Результирующее значение красного — это смешивание dstr и srcr, и находится в диапазоне [0, M]. // Вычисления для зеленого, синего и альфа аналогичны. dstr = (dstr*(M-m) + srcr*m) / M </code>
Последняя строка этого фрагмента кода была бы сложнее, если бы мы
работали с непредварительно умноженными альфа-цветами,
поэтому Color использует альфа-предварительно умноженные значения.
Пакет image/color также определяет несколько конкретных типов, реализующих
интерфейс Color.
Например, RGBA — это структура,
представляющая классический цвет с "8 битами на канал".
<code>type RGBA struct {
R, G, B, A uint8
}
</code>
Обратите внимание, что поле R структуры RGBA представляет собой 8-битное alpha-премноженное значение цвета в диапазоне [0, 255].
RGBA удовлетворяет интерфейсу Color, умножая это значение на 0x101 для получения 16-битного alpha-премноженного цвета в диапазоне [0, 65535].
Аналогично, тип структуры NRGBA представляет собой 8-битный не-alpha-премноженный цвет,
как используется в формате изображений PNG.
При прямом обращении к полям NRGBA, значения являются не-alpha-премноженными, но при вызове метода RGBA,
возвращаемые значения будут alpha-премноженными.
Model — это просто что-то,
что может преобразовывать Color в другие Color, возможно с потерей информации.
Например, GrayModel может преобразовать любой Color в оттенок серого Gray.
Palette может преобразовать любой Color в один из ограниченного набора цветов.
<code>type Model interface {
Convert(c Color) Color
}
type Palette []Color
</code>
Точки и прямоугольники
Point — это координата (x, y) на целочисленной сетке, где оси увеличиваются вправо и вниз.
Это не пиксель и не ячейка сетки. У Point нет встроенной ширины, высоты или цвета, но визуализации ниже используют небольшие цветные квадраты.
<code>type Point struct {
X, Y int
}
</code>
<code>p := image.Point{2, 1}
</code>
Rectangle — это осевая прямая прямоугольная область на целочисленной сетке,
определяемая своей верхней левой и нижней правой Point.
У Rectangle также нет встроенного цвета,
но визуализации ниже выделяют прямоугольники тонкой цветной линией,
и указывают их Min и Max Point.
<code>type Rectangle struct {
Min, Max Point
}
</code>
Для удобства, 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 часто имеет ненулевую точку начала.)
<code>r := image.Rect(2, 1, 5, 5) // Dx и Dy возвращают ширину и высоту прямоугольника. fmt.Println(r.Dx(), r.Dy(), image.Pt(0, 0).In(r)) // выводит 3 4 false </code>
Сложение Point и Rectangle переводит Rectangle.
Точки и прямоугольники не ограничены областью в нижнем правом квадранте.
<code>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 </code>
Пересечение двух прямоугольников даёт другой прямоугольник, который может быть пустым.
<code>r := image.Rect(0, 0, 4, 3).Intersect(image.Rect(2, 2, 5, 5))
// Size возвращает ширину и высоту прямоугольника, как Point.
fmt.Printf("%#v\n", r.Size()) // prints image.Point{X:2, Y:1}
</code>
Точки и прямоугольники передаются и возвращаются по значению.
Функция, принимающая аргумент типа Rectangle, будет такой же эффективной, как и функция,
принимающая два аргумента типа Point,
или четыре аргумента типа int.
Изображения
Image отображает каждую клетку сетки
в Rectangle в Color из Model.
«Пиксель в (x, y)» означает цвет клетки, определённой точками (x, y), (x+1, y), (x+1, y+1) и (x, y+1).
<code>type Image interface {
// ColorModel возвращает цветовую модель Image.
ColorModel() color.Model
// Bounds возвращает область, для которой At может вернуть ненулевой цвет.
// Границы не обязательно содержат точку (0, 0).
Bounds() Rectangle
// At возвращает цвет пикселя в (x, y).
// At(Bounds().Min.X, Bounds().Min.Y) возвращает верхний левый пиксель сетки.
// At(Bounds().Max.X-1, Bounds().Max.Y-1) возвращает нижний правый.
At(x, y int) color.Color
}
</code>
Частая ошибка — предположение, что границы Image начинаются с (0, 0).
Например, анимированный GIF содержит последовательность изображений,
и каждое Image после первого обычно содержит данные только для области, которая изменилась,
и эта область не обязательно начинается с (0, 0).
Правильный способ перебора пикселей Image m выглядит так:
<code>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))
}
}
</code>
Реализации Image не обязательно основаны на внутреннем срезе данных пикселей.
Например, Uniform — это
Image с огромными границами и однородным цветом,
внутреннее представление которого — просто этот цвет.
<code>type Uniform struct {
C color.Color
}
</code>
Обычно же программы хотят иметь изображение, основанное на срезе.
Типы структур, такие как RGBA и Gray
(другие пакеты называют их image.RGBA и image.Gray), содержат срезы
данных пикселей и реализуют интерфейс Image.
<code>type RGBA struct {
// Pix содержит пиксели изображения в порядке R, G, B, A. Пиксель по
// координатам (x, y) начинается с Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*4].
Pix []uint8
// Stride — это шаг (в байтах) между вертикально соседними пикселями в Pix.
Stride int
// Rect — это границы изображения.
Rect Rectangle
}
</code>
Эти типы также предоставляют метод Set(x, y int, c color.Color), который позволяет изменять изображение по одному пикселю за раз.
<code>m := image.NewRGBA(image.Rect(0, 0, 640, 480))
m.Set(5, 5, color.RGBA{255, 0, 0, 255})
</code>
Если вы читаете или записываете большое количество данных пикселей,
то может быть более эффективно, но и более сложным,
работать напрямую с полем Pix этих структур.
Срезовые реализации типа Image также предоставляют метод SubImage,
который возвращает Image, основанный на том же массиве.
Изменение пикселей подизображения повлияет на пиксели исходного изображения,
аналогично тому, как изменение содержимого подсреза s[i0:i1] влияет
на содержимое исходного среза s.
<code>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()) // выводит 8, 4 fmt.Println(m0.Stride == m1.Stride) // выводит true </code>
Для низкоуровневого кода, работающего с полем Pix изображения,
учтите, что итерация по Pix может повлиять на пиксели за пределами границ изображения.
В приведённом выше примере пиксели, покрываемые m1.Pix, выделены синим цветом.
Более высокоуровневый код, такой как методы At и Set или пакет image/draw,
будет обрезать свои операции до границ изображения.
Форматы изображений
Стандартная библиотека пакетов поддерживает множество распространённых форматов изображений,
таких как GIF, JPEG и PNG.
Если вы знаете формат исходного файла изображения,
то можно напрямую декодировать данные из io.Reader.
<code>import (
"image/jpeg"
"image/png"
"io"
)
// convertJPEGToPNG конвертирует из JPEG в 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)
}
</code>
Если у вас есть данные изображения неизвестного формата,
то функция image.Decode может определить формат.
Набор распознаваемых форматов создаётся во время выполнения и не ограничен
форматами, доступными в стандартной библиотеке пакетов.
Пакет формата изображения обычно регистрирует свой формат в функции init,
а основной пакет делает «подчёркивающий импорт» такого пакета исключительно ради
эффекта регистрации формата.
<code>import (
"image"
"image/png"
"io"
_ "code.google.com/p/vp8-go/webp"
_ "image/jpeg"
)
// convertToPNG конвертирует из любого распознанного формата в 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)
}
</code>
Следующая статья: Пакет Go image/draw
Предыдущая статья: Законы отражения
Индекс блога