Go 语言图片处理简明教程
虽然 Go 语言主要用于 Web 后端以及各类中间件和基础设施开发,也难免遇到一些图像处理的需求。Go 语言提供的 image 标准库提供了基本的图片加载、裁剪、绘制等能力,可以帮助我们实现一些绘图需求。
加载图片
image.Decode(io.Reader) 会从 reader 获取数据,并根据文件开头的 Magic Number 来选择合适的解码器:
import (
"fmt"
"image"
_ "image/jpeg" // 通过 jpeg 包中的 init 函数注册解码器
_ "image/png"
"os"
)
func main() {
input, _ := os.Open("avatar.jpeg")
defer input.Close()
img, _, err := image.Decode(input)
if err != nil {
panic(err)
}
fmt.Println(img.Bounds())
}
在知道图片类型的情况下也可以直接使用相应的解码器:
import (
"fmt"
"image/jpeg"
"os"
)
func main() {
input, _ := os.Open("avatar.jpeg")
defer input.Close()
img, err := jpeg.Decode(input)
if err != nil {
panic(err)
}
fmt.Println(img.Bounds())
}
Decode 返回值类型为 image.Image, 它是 image 库定义的一个接口:
type Image interface {
ColorModel() color.Model // 返回图片的颜色模型, 如 RGB、YUV
Bounds() Rectangle // 返回图片的长宽
At(x, y int) color.Color // 返回(x,y)像素点的颜色
}
如果 image 标准库中缺少需要的格式支持,我们可以通过 image.RegisterFormat
来注册自己的解码器。
保存图片
保存图片与导入图片类似, 将图像和 io.Writer 传给 png 或 jpeg 编码器即可:
func saveImage(img image.Image, filename string) error {
outFile, err := os.Create(filename)
if err != nil {
return err
}
defer outFile.Close()
b := bufio.NewWriter(outFile)
err = jpeg.Encode(b, img, nil)
if err != nil {
return err
}
err = b.Flush()
if err != nil {
return err
}
return nil
}
裁剪图片
图片的裁剪主要使用 SubImage() 方法:
width := 540
height := 960
window := image.Rect(
(img.Bounds().Dx()-width)/2, 0,
(img.Bounds().Dx()+width)/2, height,
)
return img.SubImage(window)
裁剪横屏图片中央 540*960 的范围,注意 SubImage 左上角的坐标不是 (0,0) 而是在原图片中的坐标(即 window.Min)。
subImage 使用 image.Rectangle 结构体表示的矩形范围, 它通过左上角和右下角坐标来描述矩形。坐标 0 点再原图左上角,X 轴向右 Y 轴向下。
type Rectangle struct {
Min, Max Point
}
type Point struct {
X, Y int
}
缩放图片
截止 go1.19 image 标准库中仍然没有缩放图片的功能,我们可以使用 golang.org/x/image
包:
dst := image.NewRGBA(image.Rect(0, 0, src.Bounds().Max.X/2, src.Bounds().Max.Y/2)) // 缩放后的目标图片
draw.NearestNeighbor.Scale(dst, dst.Rect, src, src.Bounds(), draw.Over, nil) // 使用 NearestNeighbor 算法进行伸缩
x/image 包中有四种缩放算法:
- NearestNeighbor
- ApproxBiLinear
- BiLinear
- CatmullRom
也可以使用 github.com/nfnt/resize
包:
resize.Resize(targetWidth, targetHeight, img, resize.NearestNeighbor)
绘制纯色图片
image.Uniform 是 image 库中纯色图片类型。下面的代码生成了一张纯蓝色的图片:
m := image.NewRGBA(image.Rect(0, 0, 640, 480))
blue := color.RGBA{0, 0, 255, 255}
draw.Draw(m, m.Bounds(), &image.Uniform{blue}, image.ZP, draw.Src)
go 标准库并不能解析我们常用的 16 进制的色号(示例:#D34899),可以使用 https://github.com/icza/gox 提供的解析函数:
func ParseHexColor(s string) (c color.RGBA, err error) {
c.A = 0xff
if s[0] != '#' {
return c, errInvalidFormat
}
hexToByte := func(b byte) byte {
switch {
case b >= '0' && b <= '9':
return b - '0'
case b >= 'a' && b <= 'f':
return b - 'a' + 10
case b >= 'A' && b <= 'F':
return b - 'A' + 10
}
err = errInvalidFormat
return 0
}
switch len(s) {
case 7:
c.R = hexToByte(s[1])<<4 + hexToByte(s[2])
c.G = hexToByte(s[3])<<4 + hexToByte(s[4])
c.B = hexToByte(s[5])<<4 + hexToByte(s[6])
case 4:
c.R = hexToByte(s[1]) * 17
c.G = hexToByte(s[2]) * 17
c.B = hexToByte(s[3]) * 17
default:
err = errInvalidFormat
}
return
}
自由绘制
jpeg 解码器返回的 image 对象(姑且称为对象)是只读的并不能在上面自由绘制,我们需要创建一个画布:
width := 1080
height := 1920
dst := image.NewRGBA(image.Rect(0, 0, width, height)) // 创建一块画布
draw.Draw(dst, image.Rect(0, height/4, width/2, 3*height/4), images[0], image.Pt(0, 0), draw.Over) // 绘制第一幅图
draw.Draw(dst, image.Rect(width/2, height/4, width, 3*height/4), images[1], image.Pt(0, 0), draw.Over) // 绘制第二幅图
上述代码实现了将两张图片拼接成在一起的效果:
其核心是 image/draw 中的 Draw 函数,它的定义如下:
func Draw(dst Image, r image.Rectangle, src image.Image, sp image.Point, op Op) {
DrawMask(dst, r, src, sp, nil, image.Point{}, op)
}
func DrawMask(dst Image, r image.Rectangle, src image.Image, sp image.Point, mask image.Image, mp image.Point, op Op)
Draw 函数的参数如下:
- dst: 绘图的画布,只能是 *image.RGBA 或 *image.Paletted 类型
- r: dst 上绘图范围
- src: 要在画布上绘制的图像
- sp: src 的起始点,实际绘制的图像是
img.SubImage(image.Rectangle{Min: sp, Max: src.Bounds().Max})
。注意,src 为 SubImage 时,sp 应为 src.Bounds().Min - op: porter-duff 混合方式, 具体介绍可以看下面的微软的参考资料。image/draw 库提供了 draw.Src 和 draw.Over 两种混合方式
- draw.Src: 将 src 覆盖在 dst 上
- draw.Over: src 在上,dst 在下按照 alpha 值进行混合。在图片完全不透明时,draw.Over 与 draw.Src 没有区别
Draw 不支持设置背景图片或者背景色,其实只要在画布最下层绘制一张和画布一样大的图片或纯色图片即可。
遮罩
DrawMask 函数可以在 src 上面一个遮罩,可以实现圆形图片、圆角等效果。
圆形图片
首先定义一个中心圆形不透明、边缘部分透明的 circle 类型,实现 image.Image 接口:
// 圆形遮罩
type circle struct {
p image.Point // 圆心位置
r int // 半径
}
func (c *circle) ColorModel() color.Model {
return color.AlphaModel
}
func (c *circle) Bounds() image.Rectangle {
return image.Rect(c.p.X-c.r, c.p.Y-c.r, c.p.X+c.r, c.p.Y+c.r)
}
func (c *circle) At(x, y int) color.Color {
xx, yy, rr := float64(x-c.p.X)+0.5, float64(y-c.p.Y)+0.5, float64(c.r)
if xx*xx+yy*yy < rr*rr {
return color.Alpha{A: 255} // 半径以内的图案设成完全不透明
}
return color.Alpha{}
}
使用 DrawMask 方法将其绘制出来:
c := circle{p: image.Point{X: avatarRad, Y: avatarRad}, r: avatarRad}
circleAvatar := image.NewRGBA(image.Rect(0, 0, avatarRad*2, avatarRad*2)) // 准备画布
draw.DrawMask(circleAvatar, circleAvatar.Bounds(), avatar, image.Point{}, &c, image.Point{}, draw.Over) // 使用 Over 模式进行混合
顺便把头像画在图片上:
圆角
圆角的实现原理和圆形一样,改一下 At 的函数公式即可:
type radius struct {
p image.Point // 矩形右下角位置
r int
}
func (c *radius) ColorModel() color.Model {
return color.AlphaModel
}
func (c *radius) Bounds() image.Rectangle {
return image.Rect(0, 0, c.p.X, c.p.Y)
}
// 对每个像素点进行色值设置,分别处理矩形的四个角,在四个角的内切圆的外侧,色值设置为全透明,其他区域不透明
func (c *radius) At(x, y int) color.Color {
var xx, yy, rr float64
var inArea bool
// left up
if x <= c.r && y <= c.r {
xx, yy, rr = float64(c.r-x)+0.5, float64(y-c.r)+0.5, float64(c.r)
inArea = true
}
// right up
if x >= (c.p.X-c.r) && y <= c.r {
xx, yy, rr = float64(x-(c.p.X-c.r))+0.5, float64(y-c.r)+0.5, float64(c.r)
inArea = true
}
// left bottom
if x <= c.r && y >= (c.p.Y-c.r) {
xx, yy, rr = float64(c.r-x)+0.5, float64(y-(c.p.Y-c.r))+0.5, float64(c.r)
inArea = true
}
// right bottom
if x >= (c.p.X-c.r) && y >= (c.p.Y-c.r) {
xx, yy, rr = float64(x-(c.p.X-c.r))+0.5, float64(y-(c.p.Y-c.r))+0.5, float64(c.r)
inArea = true
}
if inArea && xx*xx+yy*yy >= rr*rr {
return color.Alpha{}
}
return color.Alpha{A: 255}
}
添加文字
github.com/golang/freetype
库可以用来在图片上绘制文字:
img := image.NewRGBA(image.Rect(0, 0, width, height))
ttfBytes, err := ioutil.ReadFile(fontSource) // 读取 ttf 文件
if err != nil {
return err
}
font, err := freetype.ParseFont(ttfBytes)
if err != nil {
return err
}
fc := freetype.NewContext()
fc.SetDPI(72) // 每英寸的分辨率
fc.SetFont(font)
fc.SetFontSize(size)
fc.SetClip(img.Bounds())
fc.SetDst(img)
fc.SetSrc(image.Black) // 设置绘制操作的源图像,通常使用纯色图片 image.Uniform
_, err = fc.DrawString("hello world", freetype.Pt(0, 0))
if err != nil {
return err
}
Go 语言图片处理简明教程的更多相关文章
- [深度应用]·实战掌握PyTorch图片分类简明教程
[深度应用]·实战掌握PyTorch图片分类简明教程 个人网站--> http://www.yansongsong.cn/ 项目GitHub地址--> https://github.com ...
- 基于R语言的结构方程:lavaan简明教程 [中文翻译版]
lavaan简明教程 [中文翻译版] 译者注:此文档原作者为比利时Ghent大学的Yves Rosseel博士,lavaan亦为其开发,完全开源.免费.我在学习的时候顺手翻译了一下,向Yves的开源精 ...
- HTML简明教程(一)
HTML简明教程(一) 内容主体来自:W3School 一.HTML 简介 二.HTML 基础 三.HTML 元素 四.HTML 属性 五.HTML 标题 六.HTML 段落 七.HTML 文本格式化 ...
- CSDN Markdown简明教程5-高速上手
0.文件夹 文件夹 前言 CSDN Markdown特点 CSDN Markdown高速上手 1 使用快捷键 粗体斜体 引用 链接 高亮代码块 图片 标题 列表 切割线 撤销反复 2 使用离线写作 3 ...
- CSDN Markdown简明教程3-表格和公式
0. 文件夹 文件夹 前言 表格 1 表格 2 表格对齐方式 公式 1 行内公式 2 陈列公式displayed formulas 3 MathJax语法 深入 声明 1. 前言 Markdown是一 ...
- CSDN Markdown简明教程4-UML画画
0.文件夹 文件夹 前言 序列图 1 序列图演示样例 2 序列图语法 流程图 1 流程图演示样例 2 流程图语法 节点定义 节点连接 Gravizo 声明 1. 前言 Markdown是一种轻量级的标 ...
- Vbs 脚本编程简明教程之一
—为什么要使用 Vbs ? 在 Windows 中,学习计算机操作也许很简单,但是很多计算机工作是重复性劳动,例如你每周也许需要对一些计算机文件进行复制.粘贴.改名.删除,也许你每天启动 计算机第一件 ...
- glsl shader简明教程系列1
glsl shader简明教程系列1 底层的东西我就不说了(自己去百度翻基础教程) 我直接说上层了(片段着色器) web编辑器还在开发中 有了编辑器 到时候可以把代码复制上去可以看到效果了 1 实 ...
- Python 简明教程 --- 9,Python 编码
微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 当你选择了一种语言,意味着你还选择了一组技术.一个社区. -- Joshua Bloch 目录 1, ...
随机推荐
- 《Mybatis 手撸专栏》第10章:使用策略模式,调用参数处理器
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 你这代码写的,咋这么轴呢! 说到轴,让我想起初中上学时老师说的话:"你那脑 ...
- 解锁!玩转 HelloGitHub 的新姿势
本文不会涉及太多技术细节和源码,请放心食用 大家好,我是 HelloGitHub 的老荀,好久不见啊! 我在完成 HelloZooKeeper 系列之后,就很少"露面了".但是我对 ...
- vue运行npm run dev时候,自动打开页面
在config/index.js找到dev:{}里面的autoOpenBrowser: 设置为true,重新npm run dev一次就自动弹出浏览器页面啦!
- ElasticSearch7.3学习(三十)----ES7.X SQL新特性解析及使用Java api实现sql功能
一.ES7 sql新特性 1.1 数据准备 创建索引及映射 建立价格.颜色.品牌.售卖日期 四个字段 PUT /tvs PUT /tvs/_mapping { "properties&quo ...
- anaconda遇到:Solving environment: failed with initial frozen solve. Retrying with flexible solve.问题
Solving environment: failed with initial frozen solve. Retrying with flexible solve. 遇到上述问题: 解决方案: # ...
- 如何利用 RPA 实现自动化获客?
大家好,我是二哥.前高级技术专家 & 增长黑客,现一枚爱折腾的小小创业者,专注于 RPA & SaaS 软件这块.这次给大家带来如何利用 RPA 实现自动化获客 一.RPA 是什么?难 ...
- JS:自增和自减
自增自减是一元操作符运算 1.++: 前置++:先把取值,再把变量的值加1 后置++:先把变量的值加1,再取值 2.--: 前置--:先把取值,再把变量的值加1 后置--:先把变量的值加1,再取值 v ...
- BUUCTF-菜刀666
菜刀666 这题和之前做过的流量题不同,对我还是有些难度.看了看大佬的wp才做出来的 wireshark打开流量包,一开始只是单纯过滤http,包很多,看花了眼,看了好多也没觉得有啥异常. 后面才知道 ...
- node zlib压缩模块了解一下
压缩: 从index.html压缩成index.html.gz const zlib = require('zlib'); const gzip = zlib.createGzip();const f ...
- 【python基础】第11回 数据类型内置方法 02
本章内容概要 列表内置方法 字典内置方法 元组内置方法 集合内置方法 可变类型与不可变类型 本章内容详细 1.列表内置方法 list 列表在调用内置方法之后不会产生新的值 1.1 统计列表中的数据值的 ...