Работа с изображениями

12 minute read

В стандартной библиотеки Go много всего интересного. Например, пакет image для работы с изображениями. Сейчас попробуем нарисовать что нить простенькое для понимания основных принципов.

Начало

Сообществом уже написана целая куча библиотек для работы с графикой. Но, чтобы продуктивно работать с этими инструментами, нужно разбираться, как это все устроено.

Начинаем с самого простого. Создадим простую png картинку абсолютно без ничего.

package main

import (
    "fmt"
    "image"
    "image/png"
    "os"
)

func main() {
    file, err := os.Create("someimage.png")

    if err != nil {
        fmt.Errorf("%s", err)
    }
    img := image.NewRGBA(image.Rect(0, 0, 500, 500))
    png.Encode(file, img)   
}

img := image.NewRGBA(image.Rect(0, 0, 500, 500)) - создаем наше изображение, которое состоит из одной области размером 500x500 пикселей.

png.Encode(file, m) - сохраняем изображение в файл. Стандартная библиотека поддерживает два формата изображений: png и jpeg.

Теперь заполним картинку цветом. Для этого нам нужны пакеты image/draw и image/color

package main

import (
    "fmt"
    "image"
    "image/color"
    "image/draw"
    "image/png"
    "os"
)

var teal color.Color = color.RGBA{0, 200, 200, 255}

func main() {
    file, err := os.Create("someimage.png")

    if err != nil {
        fmt.Errorf("%s", err)
    }

    img := image.NewRGBA(image.Rect(0, 0, 500, 500))

    draw.Draw(img, img.Bounds(), &image.Uniform{teal}, image.ZP, draw.Src)
    // или draw.Draw(img, img.Bounds(), image.Transparent, image.ZP, draw.Src)

    png.Encode(file, img)
}

var teal color.Color = color.RGBA{0, 200, 200, 255} - цвет, которым будет заполнена наша картинка. Первое значение - это прозрачность.

draw.Draw(img, img.Bounds(), &image.Uniform{rectaleColor}, image.ZP, draw.Src) - заполняем картинку цветом. Функция draw.Draw() в параметрах принимает:

  • Создаваемое изображение типа draw.Image,
  • Прямоугольную область рисования. У нас это все изображение img.Bounds())
  • Исходное изображение &image.Uniform{rectaleColor}. Это изображение с бесконечными размерами и залитое цветом rectaleColor
  • Координаты прямоугольника для рисования image.ZP - это точка с координатами (0, 0)
  • draw.Src - метод рисования, который указывает функции просто скопировать исходное изображение в целевое не применяя никаких преобразований. В нашем случае, просто окрасит пиксели изображения в нужный цвет.

Линии

Пришло время нарисовать пару линий.

// ...

var red  color.Color = color.RGBA{200, 30, 30, 255}

// ...

draw.Draw(img, img.Bounds(), &image.Uniform{teal}, image.ZP, draw.Src)

for x := 20; x < 380; x++ {
    y := x/3 + 15
    img.Set(x, y, red)
}

// ...

Линия начинается в точке с координатами x = 20 y = 21 и заканчивается в точке x = 379 y = 141.

y := x/3 + 15 - простейшая математическая функция рисования линии на координатной плоскости. Меняя тройку - меняем наклон линии.

img.Set(x, y, red) - функция, которая заполняет определенную точку на изображении указанным цветом.

Фигуры

Линии это просто. Нам нужно больше математики! Будем рисовать круги. Создаем новый тип Circle

type Circle struct {
    p   image.Point
    r   int
}

func (c *Circle) ColorModel() color.Model {
    return color.AlphaModel
}

func (c *Circle) Bounds() image.Rectangle {
    return image.Rect(c.p.X-c.r, c.p.Y-c.r, c.p.X+c.r, c.p.Y+c.r)
}

func (c *Circle) At(x, y int) color.Color {
    xx, yy, rr := float64(x-c.p.X)+0.5, float64(y-c.p.Y)+0.5, float64(c.r)
    if xx*xx+yy*yy < rr*rr {
        return color.Alpha{255}
    }
    return color.Alpha{0}
}

Тип Circle определяет интерфейс image.Image

type Image interface {
    ColorModel() color.Model
    Bounds() Rectangle
    At(x, y int) color.Color
}

ColorModel() color.Model - определяет какая цветовая модель будет использоваться для нашего изображения.

Bounds() Rectangle - определяем границы, в переделах которых будет размещена наша фигура.

At(x, y int) color.Color - возвращет цвет для определенных координат.

Теперь нам нужно изображение, которое будет исходником для нашей маски:

mask := image.NewRGBA(image.Rect(0, 0, 500, 500))
draw.Draw(mask, mask.Bounds(), &image.Uniform{red}, image.ZP, draw.Src)

Это изображение заполнено красным цветом.

Рисуем три круга используя функцию draw.DrawMask(), которая принимает аж 7 параметров.

draw.DrawMask(img, img.Bounds(), mask, image.ZP, &Circle{image.Point{20, 21}, 20}, image.ZP, draw.Over)
draw.DrawMask(img, img.Bounds(), mask, image.ZP, &Circle{image.Point{379, 141}, 20}, image.ZP, draw.Over)
draw.DrawMask(img, img.Bounds(), mask, image.ZP, &Circle{image.Point{239, 321}, 70}, image.ZP, draw.Over)

Сигнатура функции draw.DrawMask выглядит так:

func DrawMask(dst Image, r image.Rectangle, src image.Image, sp image.Point, mask image.Image, mp image.Point, op Op)
  • dst Image - это изображение, на которое мы будем накладывать маску
  • r image.Rectangle - границы изображения, в котором сохранятся результаты преобразования
  • src image.Image - это исходное изображение, которое будет использоваться для создания маски.
  • sp image.Point - позиция исходного изображения
  • mask image.Image - изображение, которое будет играть роль маски. У нас это &Circle{image.Point{x, y}, r}
  • mp image.Point - позиция для создаваемой маски
  • op Op - оператор композиции изображения. В нашем случае draw.Over.

Собираем все это вместе и запускаем. В результате у нас получится вот такая картинка:

Конечный вариант этой программы можно найти на гисте.

Почитать по теме: