一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第17天,点击查看活动详情。
用户默认头像的几种做法
用户注册之后,如果还没有上传自己的头像之前,系统一般会给这个用户一个默认头像,有下面几种做法:
- 固定一个头像,全系统都用这个默认头像
- 搞几个头像,然后随机一个给这个用户
- 每个用户注册的时候,都生成一个随机图片当做头像
现在就说第三种,这个生成头像是怎么做的。
图片内容的存储方式
图片在硬盘上存储时,一般都用一些压缩编码的方式,也就是并不是像一个二维数组那样,一个像素一个像素的存储。
但是读取到内存中之后,一般就是一个二维数组,或者类似于一个二维数组,这个看具体的实现。
如果最后要生成png格式的图片,一般一个像素是四个数据,就是RGBA,分别代表:
- R 红色数值
- G 绿色数值
- B 蓝色数值
- A 透明度数值
这几个数值根据不同的系统,不同的编程语言,取值范围可能不一样,在Golang中,范围是。
程序化生成图片
假设系统用户头像的像素是 。
那么其实,我们的任务就是填充这个 的数组。
而数组的每一个元素包含了四个数据:RGBA。那么算下来就是 个数据,在Golang里,这些数据的类型都是 uint8。
那么任务就是,填充这 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)
任意随机法
我们可以随机个数据,往里面填充,然后导出成一个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:
还行,就是有点太乱了,完全不像一个头像。
再运行一次看看(由于随机数种子是根据系统时间戳来的,所以每次运行的结果不一样):
这一张图和上面的肯定不一样,但是由于太乱,我们看见的效果差不多。
周期随机法
既然不能完全随机, 那么我们就周期性的填充数组即可,比如说只生成前个像素点,后面的就以这个周期延展:
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
}
运行两次,效果如下:
这个比上面的就要好多了。
局部固定法
所谓局部固定就是,将变成小块,每一小块都是一样的数据,比如说,分成 的小块。 代码如下:
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)
}
看一下运行两次的效果:
越来越看着顺眼了。
更多的模式,可以发散自己的想象力,比如说左右对称,让图形看起来更加的真实。