32

I want to draw a mailing label with some rectangles, barcodes, and then finally generate a PNG/PDF file.

Is there is a better way to draw a shape in Go other than to do it with primitives - pixel by pixel?

Fakeer
  • 985
  • 1
  • 13
  • 29
  • 1
    I haven't tried these, but https://code.google.com/p/rog-go/source/browse/canvas/ and https://code.google.com/p/draw2d/ might help. – twotwotwo Mar 11 '15 at 17:53
  • thanks looked at draw2d. downloaded but ran into plenty of compile issues (i'm on appengine stack). lot of work to just get started. worst case will try to resolve them. – Fakeer Mar 12 '15 at 19:29
  • lol who how offensive was the requirement to draw a line that the question was downvoted! – Fakeer Mar 12 '15 at 19:30
  • 1
    I wasn't one of the folks that downvoted, so I'm not sure, but I think it might've been that the initial phrasing sounded a little exasperated ("simple rectangle", "Is it just me or...", "I just want to..."). Usually does better on SO to state the question as objectively as you can, even if you are frustrated (which happens!). – twotwotwo Mar 12 '15 at 21:24
  • 1
    You may want to use the new http://github.com/llgcode/draw2d repos. The library works well, a new branch of the library shows that a clean up will be done to enhance readibility and usability of the library: see [Draw a line](https://github.com/llgcode/draw2d.samples/blob/master/line/line.go) sample code and [Rect](http://godoc.org/github.com/llgcode/draw2d#Rect) function – llgcode May 11 '15 at 09:04
  • For posterity I would like to add that I abandoned Go completely in 2016 due to struggle with libs. It has been JS and occasional python since then. – Fakeer Apr 14 '21 at 20:31

5 Answers5

54

The standard Go library does not provide primitive drawing or painting capabilities.

What it provides is models for colors (image/color package) and an Image interface with several implementations (image package). The blog post The Go Image package is a good introduction to this.

It also provides a capability to combine images (e.g. draw them on each other) with different operations in the image/draw package. This can be used to a lot more than it sounds at first. There is a nice blog article about the image/draw package which showcases some of its potential: The Go image/draw package

Another example is the open-source game Gopher's Labyrinth (disclosure: I'm the author) which has a graphical interface and it uses nothing else just the standard Go library to assemble its view.

Gopher's Labyrinth Screenshot

It's open source, check out its sources how it is done. It has a scrollable game view with moving images/animations in it.

The standard library also supports reading and writing common image formats like GIF, JPEG, PNG, and support for other formats are available out of the box: BMP, RIFF, TIFF and even WEBP (only a reader/decoder).

Although support is not given by the standard library, it is fairly easy to draw lines and rectangles on an image. Given an img image which supports changing a pixel with a method: Set(x, y int, c color.Color) (for example image.RGBA is perfect for us) and a col of type color.Color:

// HLine draws a horizontal line
func HLine(x1, y, x2 int) {
    for ; x1 <= x2; x1++ {
        img.Set(x1, y, col)
    }
}

// VLine draws a veritcal line
func VLine(x, y1, y2 int) {
    for ; y1 <= y2; y1++ {
        img.Set(x, y1, col)
    }
}

// Rect draws a rectangle utilizing HLine() and VLine()
func Rect(x1, y1, x2, y2 int) {
    HLine(x1, y1, x2)
    HLine(x1, y2, x2)
    VLine(x1, y1, y2)
    VLine(x2, y1, y2)
}

Using these simple functions here is a runnable example program which draws a line and a rectangle and saves the image into a .png file:

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

var img = image.NewRGBA(image.Rect(0, 0, 100, 100))
var col color.Color

func main() {
    col = color.RGBA{255, 0, 0, 255} // Red
    HLine(10, 20, 80)
    col = color.RGBA{0, 255, 0, 255} // Green
    Rect(10, 10, 80, 50)

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

If you want to draw texts, you can use the Go implementation of FreeType. Also check out this question for a simple introduction to drawing strings on images: How to add a simple text label to an image in Go?

If you want advanced and more complex drawing capabilities, there are also many external libraries available, for example:

https://github.com/llgcode/draw2d

https://github.com/fogleman/gg

icza
  • 389,944
  • 63
  • 907
  • 827
  • 2
    icza many thanks. i did see the image pkg structs. just could not 'connect the dots' and find something builtin like what you are doing in hline and vline. this is something i did not want to do because line algorithms are not simple with rasterization anti-aliasing, bresenham etc. i will try your example now and learn. and thanks also for suggesting freetype - putting text on the label was also part of my req which i forgot to ask in the question. – Fakeer Mar 12 '15 at 19:46
  • 1
    icza neat game btw. why not freehost it on appengine? – Fakeer Mar 12 '15 at 19:56
  • @Fakeer Because it's not AppEngine- and production ready. The game was created in less than 48 hours (Gopher Gala rules). So this is more like a demo and not how good it could be if more time would be spent on it. – icza Mar 12 '15 at 20:32
  • 1
    +1 for providing a simple, working example. This is the first I've found that shows me how I can draw pixel by pixel and save to PNG. Now I'm off to start through this learning course https://github.com/ssloy/tinyrenderer/wiki – vastlysuperiorman Oct 09 '16 at 04:21
15

Here we draw two rectangles using standard golang libraries

// https://blog.golang.org/go-imagedraw-package

package main

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

func main() {

    new_png_file := "/tmp/two_rectangles.png" // output image will live here

    myimage := image.NewRGBA(image.Rect(0, 0, 220, 220)) // x1,y1,  x2,y2 of background rectangle
    mygreen := color.RGBA{0, 100, 0, 255}  //  R, G, B, Alpha

    // backfill entire background surface with color mygreen
    draw.Draw(myimage, myimage.Bounds(), &image.Uniform{mygreen}, image.ZP, draw.Src)

    red_rect := image.Rect(60, 80, 120, 160) //  geometry of 2nd rectangle which we draw atop above rectangle
    myred := color.RGBA{200, 0, 0, 255}

    // create a red rectangle atop the green surface
    draw.Draw(myimage, red_rect, &image.Uniform{myred}, image.ZP, draw.Src)

    myfile, err := os.Create(new_png_file)     // ... now lets save output image
    if err != nil {
        panic(err)
    }
    defer myfile.Close()
    png.Encode(myfile, myimage)   // output file /tmp/two_rectangles.png
}

above will generate a png file /tmp/two_rectangles.png with our two rectangles :

following code will create a chessboard image from rectangles

package main

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

func main() {

    new_png_file := "/tmp/chessboard.png" // output image lives here
    board_num_pixels := 240

    myimage := image.NewRGBA(image.Rect(0, 0, board_num_pixels, board_num_pixels))
    colors := make(map[int]color.RGBA, 2)

    colors[0] = color.RGBA{0, 100, 0, 255}   // green
    colors[1] = color.RGBA{50, 205, 50, 255} // limegreen

    index_color := 0
    size_board := 8
    size_block := int(board_num_pixels / size_board)
    loc_x := 0

    for curr_x := 0; curr_x < size_board; curr_x++ {

        loc_y := 0
        for curr_y := 0; curr_y < size_board; curr_y++ {

            draw.Draw(myimage, image.Rect(loc_x, loc_y, loc_x+size_block, loc_y+size_block),
                &image.Uniform{colors[index_color]}, image.ZP, draw.Src)

            loc_y += size_block
            index_color = 1 - index_color // toggle from 0 to 1 to 0 to 1 to ...
        }
        loc_x += size_block
        index_color = 1 - index_color // toggle from 0 to 1 to 0 to 1 to ...
    }
    myfile, err := os.Create(new_png_file) 
    if err != nil {
        panic(err.Error())
    }
    defer myfile.Close()
    png.Encode(myfile, myimage) // ... save image
    fmt.Println("firefox ", new_png_file) // view image issue : firefox  /tmp/chessboard.png
}

Scott Stensland
  • 26,870
  • 12
  • 93
  • 104
5

You are probably looking for the draw2d package. From their github readme:

Operations in draw2d include stroking and filling polygons, arcs, Bézier curves, drawing images and text rendering with truetype fonts. All drawing operations can be transformed by affine transformations (scale, rotation, translation).

The following code draws a black rectangle and writes it to a .png file. It is using the v1 release (go get -u github.com/llgcode/draw2d).

package main

import (
        "github.com/llgcode/draw2d/draw2dimg"

        "image"
        "image/color"
)

func main() {
        i := image.NewRGBA(image.Rect(0, 0, 200, 200))
        gc := draw2dimg.NewGraphicContext(i)
        gc.Save()
        gc.SetStrokeColor(color.Black)
        gc.SetFillColor(color.Black)
        draw2d.Rect(gc, 10, 10, 100, 100)
        gc.FillStroke()
        gc.Restore()

        draw2dimg.SaveToPngFile("yay-rectangle.png", i)
}

Please consult the github page for the newest version.

Rick Smith
  • 9,031
  • 15
  • 81
  • 85
  • I can't understand why everyone posts this broken example. Package draw2d doesn't contain NewGraphicContext and SaveToPngFile. If you try to run this code you get something like ".\generate.go:13: undefined: draw2d.NewGraphicContext" Both that functions are inside github.com/llgcode/draw2d/draw2dimg. – Aleksandr Zonov Sep 07 '15 at 20:16
  • The example on the github page has changed. I've updated my code to use the new version – Rick Smith Sep 10 '15 at 17:07
  • I think NewGraphicContext takes draw.Image right? image.Image doesn't have the "set()" func. – Robert Ross Sep 18 '15 at 18:48
  • @RobertRoss Correct, image.NewRGBA is a struct that implements both interfaces. NewGraphicContext sees image.RGBA and knows it implements draw.Image (and image.Image). I hope that answered your question. – Rick Smith Sep 18 '15 at 19:52
  • 1
    This example is still broken: `./main.go:16: undefined: draw2d in draw2d.Rect`. Instead, the example in the github page it's working. – iacopo May 15 '17 at 08:56
  • @iacopo The example above was for v1, which is no longer the the master branch. I've updated my answer to state and and refer to the example on the github page for newer versions. – Rick Smith May 15 '17 at 15:28
3

I got here because I wanted to draw a rectangle (just the border) on from an existing picture. It adds to @Fakeer response the reading and writing on disk.

import (
    "os"
    "image"
    "image/png"
    _ "image/jpeg"
    "image/color"
    "image/draw"
)

func drawRectangle(img draw.Image, color color.Color, x1, y1, x2, y2 int) {
    for i:= x1; i<x2; i++ {
        img.Set(i, y1, color)
        img.Set(i, y2, color)
    }

    for i:= y1; i<=y2; i++ {
        img.Set(x1, i, color)
        img.Set(x2, i, color)
    }
}

func addRectangleToFace(img draw.Image, rect image.Rectangle) (draw.Image) {
    myColor := color.RGBA{255, 0, 255, 255}

    min := rect.Min
    max := rect.Max

    drawRectangle(img, myColor, min.X, min.Y, max.X, max.Y)

    return img
}

func getImageFromFilePath(filePath string) (draw.Image, error) {

    // read file
    f, err := os.Open(filePath)
    if err != nil {
        return nil, err
    }
    defer f.Close()

    // convert as image.Image
    orig, _, err := image.Decode(f)

    // convert as usable image
    b := orig.Bounds()
    img := image.NewRGBA(image.Rect(0, 0, b.Dx(), b.Dy()))
    draw.Draw(img, img.Bounds(), orig, b.Min, draw.Src)

    return img, err
}

func main() {
    // read file and convert it
    src, err := getImageFromFilePath("src.png")
    if err != nil {
        panic(err.Error())
    }

    myRectangle := image.Rect(10, 20, 30, 40)

    dst := addRectangleToFace(src, myRectangle)

    outputFile, err := os.Create("dst.png")
    if err != nil {
        panic(err.Error())
    }

    png.Encode(outputFile, dst)

    outputFile.Close()
}

Which gives the following result: enter image description here

henyxia
  • 89
  • 1
  • 5
  • 1
    This is great, and the only example that worked well for me. I just wish it worked with a 'stroke' or border with, because the calculations become a bit more complex with this. – Jimbo Jun 10 '22 at 11:10
0

My noob shot at drawing a rectangle of given line thickness. Still primitive

func Rect(x1, y1, x2, y2, thickness int, img *image.RGBA) {
    col := color.RGBA{0, 0, 0, 255}

    for t:=0; t<thickness; t++ {
        // draw horizontal lines
        for x := x1; x<= x2; x++ {
            img.Set(x, y1+t, col)
            img.Set(x, y2-t, col)
        }
        // draw vertical lines
        for y := y1; y <= y2; y++ {
            img.Set(x1+t, y, col)
            img.Set(x2-t, y, col)
        }
    }
}


// handler to test
func draw(w http.ResponseWriter, r *http.Request) {
    img := image.NewRGBA(image.Rect(0, 0, 1200, 1800))
    Rect(5, 5, 1195, 1795, 2, img)
    png.Encode(w, img)
}
Fakeer
  • 985
  • 1
  • 13
  • 29