Golang 生成默认头像

704 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第17天,点击查看活动详情

用户默认头像的几种做法

用户注册之后,如果还没有上传自己的头像之前,系统一般会给这个用户一个默认头像,有下面几种做法:

  • 固定一个头像,全系统都用这个默认头像
  • 搞几个头像,然后随机一个给这个用户
  • 每个用户注册的时候,都生成一个随机图片当做头像

现在就说第三种,这个生成头像是怎么做的。

图片内容的存储方式

图片在硬盘上存储时,一般都用一些压缩编码的方式,也就是并不是像一个二维数组那样,一个像素一个像素的存储。

但是读取到内存中之后,一般就是一个二维数组,或者类似于一个二维数组,这个看具体的实现。

如果最后要生成png格式的图片,一般一个像素是四个数据,就是RGBA,分别代表:

  • R 红色数值
  • G 绿色数值
  • B 蓝色数值
  • A 透明度数值

这几个数值根据不同的系统,不同的编程语言,取值范围可能不一样,在Golang中,范围是[0,255][0, 255]

程序化生成图片

假设系统用户头像的像素是 646464 * 64

那么其实,我们的任务就是填充这个 646464 * 64 的数组。

而数组的每一个元素包含了四个数据:RGBA。那么算下来就是 6464464 * 64 * 4个数据,在Golang里,这些数据的类型都是 uint8

那么任务就是,填充这 6464464 * 64 * 4 uint8

Golang导出png

这个大概分成两个部分:

  • 图片内容部分,Golang提供的 *image.RGBA的类型可以用来存储图片
  • 图片导出部分,Golang提供的 image/png 自带一个编码函数,可以用来将上面的RGBA类型导出到文件

示例代码:

	img := image.NewRGBA(image.Rect(0, 0, 64, 64)) // 图片宽高 64 * 64

        // 在 (2,3) 这个像素填充 RGBA:255,0,0,255 这四个数。就是纯红色,不透明。
	img.Set(2, 3, color.RGBA{255, 0, 0, 255}) 

	// 打开一个文件
	f, _ := os.OpenFile("out.png", os.O_WRONLY|os.O_CREATE, 0600)
	defer f.Close()
        // 将img用png编码的方式导出
	png.Encode(f, img)

任意随机法

我们可以随机6464464 * 64 * 4个数据,往里面填充,然后导出成一个png格式的图片。

这里注意,随机之前,一定要设置随机种子,不能一样,否则所有用户生成的图片就都一样了。

这里给出代码:

package main

import (
	"image"
	"image/color"
	"image/png"
	"math/rand"
	"os"
	"time"
)

func main() {

	width, height := 64, 64
	img := image.NewRGBA(image.Rect(0, 0, width, height))
	arr := genRandom(width, height)

	for x := 0; x < 64; x++ {
		for y := 0; y < 64; y++ {
			r := arr[64*4*x+4*y+0]
			g := arr[64*4*x+4*y+1]
			b := arr[64*4*x+4*y+2]
			a := arr[64*4*x+4*y+3]
			img.Set(x, y, color.RGBA{r, g, b, a})
		}
	}

	// Save to out.png
	f, _ := os.OpenFile("out.png", os.O_WRONLY|os.O_CREATE, 0600)
	defer f.Close()
	png.Encode(f, img)
}

func genRandom(width, height int) []uint8 {
	rand.Seed(time.Now().Unix())
	arr := make([]uint8, width*height*4)
	//
	for idx := range arr {
		arr[idx] = uint8(rand.Uint32())
	}
	return arr
}


运行一下,看看生成的 out.png:

out.png

还行,就是有点太乱了,完全不像一个头像。

再运行一次看看(由于随机数种子是根据系统时间戳来的,所以每次运行的结果不一样):

out.png

这一张图和上面的肯定不一样,但是由于太乱,我们看见的效果差不多。

周期随机法

既然不能完全随机, 那么我们就周期性的填充数组即可,比如说只生成前6464个像素点,后面的就以这个周期延展:

func genRandom(width, height int) []uint8 {
	rand.Seed(time.Now().Unix())
	arr := make([]uint8, width*height*4)
	//
	for idx := range arr {
		if idx < 64*4 {
			arr[idx] = uint8(rand.Uint32())
		} else {
			arr[idx] = arr[idx-64*4]
		}
	}
	return arr
}

运行两次,效果如下:

out.png

out.png

这个比上面的就要好多了。

局部固定法

所谓局部固定就是,将646464 * 64变成小块,每一小块都是一样的数据,比如说,分成 888 * 8的小块。 代码如下:

package main

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

func main() {

	width, height := 64, 64
	img := image.NewRGBA(image.Rect(0, 0, width, height))

	for x := 0; x < 64; x++ {
		for y := 0; y < 64; y++ {
			if x%8 == 0 && y%8 == 0 {
				r := uint8(rand.Uint32())
				g := uint8(rand.Uint32())
				b := uint8(rand.Uint32())
				a := uint8(rand.Uint32())
				img.Set(x, y, color.RGBA{r, g, b, a})
			} else {
				c := img.RGBAAt(x-x%8, y-y%8)
				img.Set(x, y, c)
			}
		}
	}

	// Save to out.png
	f, _ := os.OpenFile("out.png", os.O_WRONLY|os.O_CREATE, 0600)
	defer f.Close()
	png.Encode(f, img)
}

看一下运行两次的效果:

out.png

out.png

越来越看着顺眼了。

更多的模式,可以发散自己的想象力,比如说左右对称,让图形看起来更加的真实。