понеділок, 21 листопада 2016 р.

Программное формирования изображения на Go

Понадобилось мне при разработке мода на 7 Days to Die программно формировать изображение круга в зависимости от количества дней в игре. Так как я давно порывался посмотреть на Go, решил я использовать его для данной задачи. Да и подходит он для такого микро-сервиса в самый раз.
Сделать веб сервер на Go просто :) для этого используем стандартную библиотеку "net/http"
func main() {
  flag.Parse()
  log.Println("Starting tiny webserver for 7DTD moon texture...")
  log.Println("Document root path: ", *root)
  http.HandleFunc("/moon", moonHandler)
  log.Println("Listening on port: ", *port)
  if err := http.ListenAndServe(":"+strconv.Itoa(*port), nil); err != nil {
    log.Fatal("ERROR Listen and Serve: ", err)
  }
}
Дальше, формируем изображение круга с использование библиотеки "image/draw",
func moonHandler(w http.ResponseWriter, r *http.Request) {
  d := getDay(r)
  /*** Create colored image ***/
  dst := image.NewRGBA(image.Rect(0, 0, cXY.X+cR+1, cXY.Y+cR+1))
  draw.Draw(dst, dst.Bounds(), &circle{cXY, cR}, image.ZP, draw.Src)
  writeImg(w, dst)
}
и отдаем ее как картинку "image/png"
func writeImg(w http.ResponseWriter, img image.Image) {
  buffer := new(bytes.Buffer)
  if err := png.Encode(buffer, img); err != nil {
    log.Println("Unable to encode image: ", err)
    http.Error(w, err.Error(), http.StatusNotFound)
    return
  }

  w.Header().Set("Content-Type", "image/png")
  w.Header().Set("Content-Length", strconv.Itoa(len(buffer.Bytes())))

  if _, err := w.Write(buffer.Bytes()); err != nil {
    log.Println("Unable to write image: ", err)
    http.Error(w, err.Error(), http.StatusNotFound)
    return
  }
}
А теперь самое интересное, как же сделать эту картинку :)
Для этого описываем структуру:
type circle struct {
  P image.Point
  R int
}
И определяем методы которые описываю данную структуру в разрезе "image.Image"
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), float64(y-c.P.Y), float64(c.R)
  if xx*xx+yy*yy < rr*rr {
    return color.Alpha{255}
  }
  return color.Alpha{0}
}
Все это прекрасно работает, но мне нужно было получить картинку с определенной прозрачностью, по этому пришлось немного изменить код функции "At" и структуры "circle". Добавляем поля для цвета и типа изображения (сплошная заливка или градиентная)
type circle struct {
  P image.Point
  R int
  C color.NRGBA
  S bool
}
... и теперь сама процедура отрисовки круга:
func (c *circle) At(x, y int) color.Color {
  xx, yy, rr := float64(x-c.P.X), float64(y-c.P.Y), float64(c.R)
  if xx*xx+yy*yy < rr*rr {
    xr := math.Abs(float64(c.P.X - x))
    yr := math.Abs(float64(c.P.Y - y))
    ar := uint8((1 - math.Sqrt(xr*xr+yr*yr)/float64(c.R)) * float64(c.C.A))
    if c.S {
      ar = c.C.A
    }
    return color.NRGBA{c.C.R, c.C.G, c.C.B, ar}
  }
  return color.Alpha{0}
}
В итоге, мы можем получить два вида кружкок :) задав параметры при обращении к веб серверу.
Градиентная заливка для 7 дня Сплошная заливка для 7 дня Градиентная заливка для 5-6 дня Сплошная заливка для 5-6 дня


Конечный результат как это все выглядит в игре :)

Немає коментарів:

Дописати коментар