在Golang中如何画一个矩形?

32
我希望绘制一张带有矩形、条形码的邮标签,并最终生成PNG/PDF文件。
在Go语言中,除了逐像素使用原始图形绘制,是否存在更好的绘制形状的方式?

1
我没有尝试过这些,但是https://code.google.com/p/rog-go/source/browse/canvas/和https://code.google.com/p/draw2d/可能会有所帮助。 - twotwotwo
谢谢,我看了一下draw2d。下载后遇到了很多编译问题(我在appengine堆栈上)。要开始工作需要做很多工作。最坏的情况是尝试解决它们。 - Fakeer
哈哈,谁知道这个问题被踩得多么冒犯,需要画一条线来标记! - Fakeer
1
我不是其中一个点踩的人,所以我不确定,但我认为可能是最初的措辞听起来有点恼怒(“简单矩形”,“只有我还是...”,“我只想要...”)。通常在SO上尽可能客观地陈述问题会更好,即使你感到沮丧(这种情况经常发生!)。 - twotwotwo
1
你可能想使用新的http://github.com/llgcode/draw2d存储库。该库运行良好,该库的一个新分支显示将进行清理以增强库的可读性和可用性:请参见[Draw a line](https://github.com/llgcode/draw2d.samples/blob/master/line/line.go)示例代码和[Rect](http://godoc.org/github.com/llgcode/draw2d#Rect)功能。 - llgcode
为了后人纪念,我想补充一下,由于在使用Go的库时遇到了困难,我在2016年彻底放弃了它。从那以后我的主要语言是JavaScript,偶尔用Python。 - Fakeer
5个回答

55

标准的Go库没有提供原始的绘图或绘画功能。

它提供了颜色模型(image/color包)和一个带有多个实现的Image接口(image包)。博客文章The Go Image package是对此的很好介绍。

它还提供了一种组合图像(例如,在彼此上绘制它们)并使用image/draw包中的不同操作的能力。这可以用于比它听起来更多的东西。有一篇关于image/draw包的不错的博客文章,展示了它的一些潜力:The Go image/draw package

另一个例子是开源游戏Gopher's Labyrinth声明:我是作者),它具有图形界面,并且仅使用标准Go库来组装其视图。

Gopher's Labyrinth Screenshot

这是开源的,可以查看它的源代码了解它是如何完成的。它具有可滚动的游戏视图,并在其中移动图像/动画。
标准库还支持读写常见的图像格式,如 GIFJPEGPNG,并且支持其他格式,例如 BMPRIFFTIFF,甚至 WEBP(仅限读取器/解码器)。
尽管标准库不提供支持,但在图像上绘制线条和矩形相当容易。假设有一个支持使用方法Set(x, y int, c color.Color)(例如image.RGBA非常适合我们)更改像素的img图像和类型为color.Colorcol
// 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)
}

使用这些简单的函数,下面是一个可运行的示例程序,它绘制了一条线和一个矩形,并将图像保存为 .png 文件:
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)
}

如果您想绘制文本,可以使用FreeType的Go实现。此外,请参阅这个问题,了解在图像上绘制字符串的简单介绍:如何在Go中向图像添加简单的文本标签?

如果您需要高级和更复杂的绘图功能,还有许多外部库可供选择,例如:

https://github.com/llgcode/draw2d

https://github.com/fogleman/gg


2
icza非常感谢。我确实看到了image pkg structs的内容,只是无法“连接点”并找到像您在hline和vline中所做的内置功能。这是我不想做的事情,因为线算法与光栅化抗锯齿、bresenham等并不简单。我现在会尝试您的示例并学习。还要感谢您建议freetype-将文本放在标签上也是我问题中忘记询问的一部分。 - Fakeer
1
icza的游戏很不错。为什么不在AppEngine上免费托管呢? - Fakeer
@Fakeer,因为它还不是AppEngine-和生产就绪的。游戏是在不到48小时内创建的(Gopher Gala规则)。因此,这更像是演示,而不是如果花更多时间的话会变得更好。 - icza
1
提供一个简单的、可工作的示例,加1分。这是我找到的第一个向我展示如何逐像素绘制并保存为PNG的方法。现在我要开始学习这个课程了https://github.com/ssloy/tinyrenderer/wiki。 - vastlysuperiorman

15

这里我们使用标准的golang库绘制了两个矩形

// 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
}

上述代码将生成一个png文件/tmp/two_rectangles.png,其中包含我们的两个矩形:

以下代码将从矩形创建一个棋盘图像。

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
}


5
你可能正在寻找 draw2d 库。以下是它们在 GitHub自述文件中的描述:

Draw2d 中包含的操作包括多边形、弧线、贝塞尔曲线的描边和填充,绘制图片以及使用 TrueType 字体渲染文本。所有绘图操作都可以通过仿射变换(缩放、旋转、平移)进行转换。

以下代码绘制一个黑色矩形并将其写入一个 .png 文件。它是使用 v1 版本(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)
}

请查阅github页面获取最新版本的信息。

我不明白为什么每个人都发布这个错误的示例。Package draw2d中不存在NewGraphicContext和SaveToPngFile。如果您尝试运行此代码,则会得到类似于“.\generate.go:13: undefined: draw2d.NewGraphicContext”的内容。这两个功能都在github.com/llgcode/draw2d/draw2dimg内部。 - Aleksandr Zonov
Github页面上的示例已经更改。我已经更新了我的代码以使用新版本。 - Rick Smith
我认为NewGraphicContext需要draw.Image对吧?image.Image没有“set()”函数。 - Robert Ross
@RobertRoss 正确,image.NewRGBA是实现两个接口的结构体。NewGraphicContext看到image.RGBA并知道它实现了draw.Image(和image.Image)。我希望这回答了你的问题。 - Rick Smith
1
这个例子仍然有问题:./main.go:16: undefined: draw2d in draw2d.Rect。相反,github页面上的示例是可行的。 - iacopo
@iacopo 上面的示例是针对v1的,而v1已不再是主分支。我已经更新了我的答案,说明并引用了github页面上较新版本的示例。 - Rick Smith

3
我来这里是因为我想在一张现有的图片上绘制一个矩形(仅边框)。它补充了@Fakeer的响应,涉及到磁盘的读写。
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()
}

这将得到以下结果: 输入图像描述

1
这很棒,也是唯一一个对我有效的示例。我只希望它能够使用“描边”或边框,因为这样计算会变得更加复杂。 - Jimbo

0

我尝试绘制给定线宽度的矩形,但是仍很原始。

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)
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接