Go语言之图像处理
golang的image库提供了图像处理基本功能
一、语法:
1、image库的Rect()函数可用于创建一个矩形对象,语法如下:
func Rect(x0, y0, x1, y1 int) Rectangle
其中,x0, y0 为左上角坐标,x1, y1 为右下角坐标。这个函数返回一个 Rectangle 类型的对象,表示以指定坐标为左上角和右下角所构成的矩形,例如:r := image.Rect(0, 0, 100, 50) 表示创建一个从左上角坐标 (0, 0) 到右下角坐标 (100, 50) 的矩形,将其保存在 r 变量中,Rectangle结构体实际是Point结构体嵌套而来,因此image.Rect(0,0,100,50)还可以写为image.Rectangle{image.Point{0,0},image.Point{100,50}},如图:

注:Golang的图像坐标系中,Y轴向下是正方向,X轴向右是正方向
2、image库的NewRGBA() 函数用于创建一个 RGBA 类型的图像对象,语法如下:
func NewRGBA(r Rectangle) *RGBA
其中,r 表示图像的大小,是一个 Rectangle 类型的对象。这个函数返回一个指向 RGBA 类型的图像对象的指针
例如:创建一个宽为100、高为50的 RGBA 类型的图像对象,结合第一步的Rect()函数,如下:
r := image.Rect(0, 0, 100, 50)
img := image.NewRGBA(r)
上面代码会创建一个由 100×50 个像素点组成,每个像素点有红、绿、蓝和透明度四个通道的 RGBA 类型的图像对象,并将其保存在 img 变量中,创建完图像对象后,可以通过设置每个像素点的颜色值来绘制图像
3、image/color库的RGBA结构体是一个代表 RGB 颜色模型中的一种颜色的结构体。它由四个 8 位无符号整数组成,分别表示红、绿、蓝和 alpha(透明度)通道的值。具体定义如下:
type RGBA struct {
R, G, B, A uint8
}
其中R、G、B分别表示红、绿、蓝三原色的颜色值,其中 0 表示完全透明,255 表示完全不透明,color.RGBA 结构体实现了 color.Color 接口,可以在图像绘制等场景中使用
4、draw库Draw()函数是一个用于在目标图像上绘制源图像的函数。它可以实现将一个图像粘贴到另一个图像上的功能,支持多种不同的操作模式和混合模式。使用方式如下:
import "image"
import "image/draw"
// 创建目标图像和源图像
dst := image.NewRGBA(image.Rect(0, 0, 100, 100))
src := image.NewUniform(color.RGBA{255, 0, 0, 255})
// 在 dst 上绘制 src
draw.Draw(dst, dst.Bounds(), src, image.Point{}, draw.Src)
这将在 dst 图像上绘制一个红色图形,大小为 dst 的边界框(本例中为 [0,0,100,100])。draw.Draw() 函数的第一个参数是目标图像对象,第二个参数是绘制区域的矩形边界框,第三个参数是源图像对象,第四个参数是开始绘制的位置,第五个参数draw.Src表示绘制模式,有多个参数,如下:
- Over: 默认操作,直接将源图像绘制到目标图像
- Src: 只绘制源图像的颜色,忽略目标图像的颜色
- Dst: 只保留目标图像的颜色,忽略源图像的颜色
- Clear: 用透明色填充目标图像
5、image库的RGBA结构体的Bounds()方法用于返回该图像对象的边界框。具体定义如下:
func (p *RGBA) Bounds() Rectangle
Bounds() 方法返回的是一个 image.Rectangle 类型的对象,表示该图像对象的边界框。这个边界框的左上角坐标为 (0, 0),右下角坐标为 (w, h),其中 w 和 h 分别为图像的宽度和高度(以像素为单位)。例如,如果我们有一个名为 img 的 image.RGBA 对象,可以通过以下方式获取其边界框:
bounds := img.Bounds()
这将返回一个 image.Rectangle 类型的对象 bounds,可以通过调用其 Min 和 Max 方法获取其左上角和右下角的坐标。例如,要获取图像的宽度和高度,可以使用以下代码:
w, h := bounds.Max.X, bounds.Max.Y
6、image库的NewUniform()函数用于创建一个包含单一颜色的图像对象。它接受一个 color.Color 类型的参数作为输入,返回一个实现了 image.Image 接口的新图像对象,用法如下:
import "image"
import "image/color"
// 创建一个 100x100 的红色图像
c := color.RGBA{255, 0, 0, 255}
img := image.NewUniform(c)
#注意:image.Rectangle{image.Point{0, 0}, image.Point{100, 100}}等价于image.Rect(0,0,100,100)
dst := image.NewRGBA(image.Rectangle{image.Point{0, 0}, image.Point{100, 100}})
draw.Draw(dst, dst.Bounds(), img, image.Point{}, draw.Src)
上述代码将创建一个大小为 1×1,颜色为 {255, 0, 0, 255}(红色)的 color.RGBA 对象,并将其传递给 image.NewUniform() 函数,以创建一个包含该颜色的新图像对象。在这个例子中,我们使用了 draw.Draw() 函数将该图像绘制到另一个图像上
7、image库的 Point 是一个表示二维平面上的点的结构体。它由两个整数类型的 X 和 Y 坐标组成,分别表示点在水平和垂直方向的位置。具体定义如下:
type Point struct {
X, Y int
}
image.Point 可以用于表示图像的像素坐标、矩形的左上角和右下角坐标等位置信息
例如,我们可以使用 image.Point 来指定一个矩形的左上角和右下角坐标,然后使用该矩形作为参数调用 image.NewRGBA() 函数创建一个 RGBA 图像对象:
import "image"
// 创建一个 100x100 的 RGBA 图像
rect := image.Rectangle{image.Point{0, 0}, image.Point{100, 100}}
img := image.NewRGBA(rect)
上述代码将创建一个大小为 100×100 的 RGBA 图像对象,并且左上角坐标为 (0,0),右下角坐标为 (100,100)
二、实战:
1、下面例子表示创建一个背景为白色的矩形图像,如图:

- 13行:首先通过image.Rect()函数创建长300高300的矩形对象,并通过NewRGBA创建图像对象
- 14行:创建一个空图片,返回一个File指针和error
- 18行:通过bufio.NewWriter()初始化一个Writer结构体对象,返回bufio.Writer结构体指针
- 19行:通过png.Encode()将图像对象newimage编码为png格式数据,如果读取文件就要解码(Decode)
- 23行:调用bufio的Writer结构体的Flush()将数据写入磁盘中
- 27行:通过os打开或者操作的文件需要关闭
运行之后在项目目录下生成test.png图片,打开图片可以看到背景为透明,像素为300*300,如图:

2、下面例子表示绘制一个两个圆形,并设置两个圆之间边框颜色为红色,如图:
package main
import (
"fmt"
"image"
"image/color"
"image/draw"
"image/png"
"math"
"os"
)
func main() {
//backgroundColor := color.RGBA{0, 0, 0, 0} //透明
img := image.NewRGBA(image.Rect(0, 0, 600, 600)) //定义图像对象
//在图像对象img上绘制新透明色
//draw.Draw(img, img.Bounds(), image.NewUniform(backgroundColor), image.Point{}, draw.Src)
//圆的中心坐标,因为我们希望画的圆形可以位于图像对象的中心,因为设置为300,可根据需要自定义
centerX := 300
centerY := 300
//设置外层圆的半径,如果希望外层圆与图像对象相接,可设置半径为300
radius := 300
//设置内层圆与外层圆之间的边框宽度
borderWidth := 10
//定义外层圆的颜色,本例子设置为红色
borderColor := color.RGBA{255, 0, 0, 255}
//定义外层圆 2*math.Pi相当于圆的一周,遍历圆的一周上的点与水平方向的角度
for angle := 0.0; angle < 2*math.Pi; angle += 0.001 {
x := int(float64(radius)*math.Cos(angle)) + centerX
y := int(float64(radius)*math.Sin(angle)) + centerY
img.Set(x, y, borderColor)
}
//定义内层圆
for angle := 0.0; angle < 2*math.Pi; angle += 0.001 {
x := int(float64(radius-borderWidth)*math.Cos(angle)) + centerX
y := int(float64(radius-borderWidth)*math.Sin(angle)) + centerY
img.Set(x, y, borderColor)
}
//填充内圆与外侧圆之间的区域为红色
for angle := 0.0; angle < 2*math.Pi; angle += 0.001 {
//右上四分之一圆
for x := int(float64(radius-borderWidth)*math.Cos(angle)) + centerX; x <= int(float64(radius)*math.Cos(angle))+centerX; x++ {
for y := int(float64(radius)*math.Sin(angle)) + centerY; y <= int(float64(radius-borderWidth)*math.Sin(angle))+centerY; y++ {
img.Set(x, y, borderColor)
}
}
//左上四分之一圆
for x := int(float64(radius)*math.Cos(angle)) + centerX; x <= int(float64(radius-borderWidth)*math.Cos(angle))+centerX; x++ {
for y := int(float64(radius)*math.Sin(angle)) + centerY; y <= int(float64(radius-borderWidth)*math.Sin(angle))+centerY; y++ {
img.Set(x, y, borderColor)
}
}
//左下四分之一圆
for x := int(float64(radius)*math.Cos(angle)) + centerX; x <= int(float64(radius-borderWidth)*math.Cos(angle))+centerX; x++ {
for y := int(float64(radius-borderWidth)*math.Sin(angle)) + centerY; y <= int(float64(radius)*math.Sin(angle))+centerY; y++ {
img.Set(x, y, borderColor)
}
}
//右下四分之一圆
for x := int(float64(radius-borderWidth)*math.Cos(angle)) + centerX; x <= int(float64(radius)*math.Cos(angle))+centerX; x++ {
for y := int(float64(radius-borderWidth)*math.Sin(angle)) + centerY; y <= int(float64(radius)*math.Sin(angle))+centerY; y++ {
img.Set(x, y, borderColor)
}
}
}
// 将图片输出到文件
outputFile, err := os.Create("circle.png")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
defer outputFile.Close()
png.Encode(outputFile, img)
}
说明:
上面的外层圆的 for 循环,遍历从 0 到 2π(即一周)之间的弧度值,并且每次递增0.001,具体来说,radius 表示点到圆心的距离,也就是我们设置的外层圆的半径,angle 表示圆上的点与圆心连线后与X轴的夹角(弧度),centerX 表示圆心在水平方向上的坐标。这段代码先将 radius 转换成浮点数类型并乘以 math.Cos(angle) 计算出点在水平方向上的距离(即 x 坐标),此坐标是相对于圆心的坐标,因此需要加上 centerX才能得到点在水平方向上的实际位置坐标,y坐标一样,最后通过调用 img.Set(x, y, borderColor),将该点的颜色设置为 borderColor,本例子中为红色,因此就画出了一个红色的圆形
内层圆的原理一样的,需要先用外层圆的半径减去内外层圆之间的边框距离,然后画圆即可,画好的图如下:

再来说一下如何给内圆和外圆之间的边框区域添加颜色为红色
原理:还是通过for循环遍历内外圆的各个点上的弧度,根据弧度值计算出每个点的X,Y坐标,然后通过image库的RGBA结构体的Set()方法设置坐标区间的颜色
首先我们看上面代码的第三个for循环,还是遍历一个圆周(2*math.Pi)上的点,获取这个点的弧度制,然后通过math.Cos()和math.Sin()来获取每个点对应的实际坐标,在设置颜色的时,其实是通过(600,300)这个点然后顺时针画的,也就是先画的右下四分之一圆,然后左下四分之一圆,以此类推,因此在for循环中又包含了四个for循环,说明如下:
右下四分之一圆:从遍历后x,y的坐标来看,右下四分之一圆的外层圆起始坐标为(600,300),内层圆的起始坐标为(590,300),外层圆终止坐标(300,600),内层圆终止坐标为(300,590),并且整个外层圆的x坐标始终大于等于内层圆的x坐标,外层圆的y坐标始终大于内层圆的y坐标,因此通过for循环从全部的结果中遍历符合的坐标为:
for x := int(float64(radius-borderWidth)*math.Cos(angle)) + centerX; x <= int(float64(radius)*math.Cos(angle))+centerX; x++ {
for y := int(float64(radius-borderWidth)*math.Sin(angle)) + centerY; y <= int(float64(radius)*math.Sin(angle))+centerY; y++ {
img.Set(x, y, borderColor)
}
}
左下四分之一圆:左下四分之一圆的外层圆起始坐标为(300,600),内层圆为(300,590),外层圆终止坐标为(0,300),内层圆终止坐标为(10,300),因为边框厚度为10,因此内层圆的X坐标为10,从遍历的坐标结果看,外层圆的x坐标始终小于等于内层圆的坐标,外层圆的y坐标使用大于等于内层圆的Y坐标,因此通过for循环从全部的结果中遍历符合的坐标为:
for x := int(float64(radius)*math.Cos(angle)) + centerX; x <= int(float64(radius-borderWidth)*math.Cos(angle))+centerX; x++ {
for y := int(float64(radius-borderWidth)*math.Sin(angle)) + centerY; y <= int(float64(radius)*math.Sin(angle))+centerY; y++ {
img.Set(x, y, borderColor)
}
}
左上四分之一圆:左上四分之一圆的外层圆起始坐标为(0,300),内层圆为(300,10),外层圆终止坐标为(300,0),内层圆为(300,10),从遍历的坐标结果看,外层圆的x坐标始终小于等于内层圆的x坐标,外层圆的y坐标始终小于等于内层圆的y坐标,因此通过for循环从全部的结果中遍历符合的坐标为:
for x := int(float64(radius)*math.Cos(angle)) + centerX; x <= int(float64(radius-borderWidth)*math.Cos(angle))+centerX; x++ {
for y := int(float64(radius)*math.Sin(angle)) + centerY; y <= int(float64(radius-borderWidth)*math.Sin(angle))+centerY; y++ {
img.Set(x, y, borderColor)
}
}
右上四分之一圆:右上四分之一圆的外层圆起始坐标为(300,0),内层圆的起始坐标为(300,10),外层圆的终止坐标为(600,300),内层圆的终止坐标为(590,300),从遍历的x,y坐标结果看,外层圆的x坐标始终大于等于内层圆的x坐标,外层圆的Y坐标始终小于等于内层圆的Y坐标,因此通过for循环从全部的结果中遍历符合的坐标为:
for x := int(float64(radius-borderWidth)*math.Cos(angle)) + centerX; x <= int(float64(radius)*math.Cos(angle))+centerX; x++ {
for y := int(float64(radius)*math.Sin(angle)) + centerY; y <= int(float64(radius-borderWidth)*math.Sin(angle))+centerY; y++ {
img.Set(x, y, borderColor)
}
}

注意:如果只是画一个内外圆并设置边框宽度为红色,第1、2个的for循环2*math.Pi其实可以不写的,也可以达到效果,但是如果只是画两个空心圆,那么第一个第二个就必须要写
其他:上面我们计算点的坐标是根据的弧度值,通过for循环遍历2*math.Pi获取的直接就是弧度值,如果给定角度,来获取坐标值,可以先将角度值转换为弧度值,然后在计算坐标,如下:
angle := 45 //给定角度
radian := angle *math.Pi /180 //根据角度求弧度值
res := int(float64(radius)*math.Cos(radian)) + centerX //根据弧度获取X坐标


