57

Given image.RGBA, coordinates, and a line of text, how do I add a simple label with any plain fixed font? E.g. Face7x13 from font/basicfont.

package main

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

func main() {
    img := image.NewRGBA(image.Rect(0, 0, 320, 240))
    x, y := 100, 100
    addLabel(img, x, y, "Test123")
    png.Encode(os.Stdout, img)
}

func addLabel(img *image.RGBA, x, y int, label string) {
     col := color.Black
     // now what?
}

Alignment doesn't really matter, but best if I could write the label above a line which starts at the coordinates.

And I would like to avoid external loadable dependencies like fonts.

icza
  • 389,944
  • 63
  • 907
  • 827
sanmai
  • 29,083
  • 12
  • 64
  • 76
  • This package explicitly states that it only provides an interface for font faces. `"Other packages provide font face implementations. For example, a truetype package would provide one based on .ttf font files."` – Endre Simo Jul 11 '16 at 06:47

2 Answers2

91

The golang.org/x/image/font package just defines interfaces for font faces and drawing text on images.

You may use the Go implementation of Freetype font rasterizer: github.com/golang/freetype.

The key type is freetype.Context, it has all the methods you need.

For a complete example, check out this file: example/freetype/main.go. This example loads a font file, creates and configures freetype.Context, draws text on image and saves the result image to file.

Let's assume you already have the font file loaded, and a c context configured (see the example how to do that). Then your addLabel() function could look like this:

func addLabel(img *image.RGBA, x, y int, label string) {
    c.SetDst(img)
    size := 12.0 // font size in pixels
    pt := freetype.Pt(x, y+int(c.PointToFixed(size)>>6))

    if _, err := c.DrawString(label, pt); err != nil {
        // handle error
    }
}

If you don't want to hassle with the freetype package and external font files, the font/basicfont package contains a basic font named Face7x13 whose graphical data is entirely self-contained. This is how you could use that:

import (
    "golang.org/x/image/font"
    "golang.org/x/image/font/basicfont"
    "golang.org/x/image/math/fixed"
    "image"
    "image/color"
)

func addLabel(img *image.RGBA, x, y int, label string) {
    col := color.RGBA{200, 100, 0, 255}
    point := fixed.Point26_6{fixed.I(x), fixed.I(y)}

    d := &font.Drawer{
        Dst:  img,
        Src:  image.NewUniform(col),
        Face: basicfont.Face7x13,
        Dot:  point,
    }
    d.DrawString(label)
}

This is how this addLabel() function can be used: the code below creates a new image, draws the "Hello Go" text on it and saves it in a file named hello-go.png:

func main() {
    img := image.NewRGBA(image.Rect(0, 0, 300, 100))
    addLabel(img, 20, 30, "Hello Go")

    f, err := os.Create("hello-go.png")
    if err != nil {
        panic(err)
    }
    defer f.Close()
    if err := png.Encode(f, img); err != nil {
        panic(err)
    }
}

Note the above code also requires the "image/png" package import.

Also note that the y coordinate given will be the bottom line of the text. So if you want to draw a line to the top left corner, you have to use x = 0 and y = 13 (13 is the height of this Face7x13 font). If you wish, you could build this into the addLabel() function by subtracting 13 from the y coordinate, so that the passed y coordinate would be the top coordinate at which the text will be drawn.

There is also an additional self-contained font in the golang.org/x/image/font/inconsolata package with regular and bold style, to use them, you only need to specify a different Face in addLabel():

import "golang.org/x/image/font/inconsolata"

        // To use regular Inconsolata font family:
        Face: inconsolata.Regular8x16,

        // To use bold Inconsolata font family:
        Face: inconsolata.Bold8x16,
Nathan Baulch
  • 20,233
  • 5
  • 52
  • 56
icza
  • 389,944
  • 63
  • 907
  • 827
  • @sanmai See edited answer, I included an example how to use `basicfont.Face7x13`. – icza Jul 11 '16 at 07:33
  • thanks, just about it! Only it prints the lines [half cut](http://i.imgur.com/sPoxcAb.png) – sanmai Jul 11 '16 at 07:46
  • @sanmai Is your `img` you pass big enough? Maybe it's cut because you position the text below the bottom of the image? – icza Jul 11 '16 at 07:54
  • Well, that was all my fault because I overdraw the label later in the program. Thanks! – sanmai Jul 11 '16 at 08:00
  • @sanmai Good to hear (that issue wasn't in my code). Added a usage example which creates an images, draws text on it and saves result in a file. – icza Jul 11 '16 at 09:59
  • 1
    `One or more of the answers is exemplary and worthy of an additional bounty.` indeed! – ellisbben Mar 28 '17 at 19:24
  • 3
    There's a newer [example/drawer/main.go](https://github.com/golang/freetype/blob/master/example/drawer/main.go) which I'd urge anyone looking into using this to check out - it features some newer functions like MeasureText which make things a whole lot easier. – fstanis Jun 03 '17 at 23:57
  • by using `Face7x13` there is noway to change the font size ?! – TomSawyer Sep 30 '22 at 09:40
20

here is the sample code using gg library where we already have src.jpg or any image and we write text over it.. you can adjust canvas size accordingly .. it's just example. let me know if it doesn't work.

package main

import (
    "github.com/fogleman/gg"
    "log"
)

func main() {
    const S = 1024
    im, err := gg.LoadImage("src.jpg")
    if err != nil {
        log.Fatal(err)
    }

    dc := gg.NewContext(S, S)
    dc.SetRGB(1, 1, 1)
    dc.Clear()
    dc.SetRGB(0, 0, 0)
    if err := dc.LoadFontFace("/Library/Fonts/Arial.ttf", 96); err != nil {
        panic(err)
    }
    dc.DrawStringAnchored("Hello, world!", S/2, S/2, 0.5, 0.5)

    dc.DrawRoundedRectangle(0, 0, 512, 512, 0)
    dc.DrawImage(im, 0, 0)
    dc.DrawStringAnchored("Hello, world!", S/2, S/2, 0.5, 0.5)
    dc.Clip()
    dc.SavePNG("out.png")
}
Yatender Singh
  • 3,098
  • 3
  • 22
  • 31
  • 1
    Doing `dc.DrawImage(im, 0, 0)` was slow in raspberrypi and my functions were timing out. Was able to speed things up by using `gg.NewContextForImage("src.jpg")` – Ramesh Jan 01 '22 at 13:45