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, ...
随机推荐
- django框架7
内容概要 聚合查询 分组查询 F查询 Q查询 ORM查询优化 ORM常见字段类型 ORM重要参数 ORM事务操作 ORM执行原生SQL 多对多三种创建方式 内容详情 聚合查询 MySQL聚合函数:ma ...
- junit 5 - Display Name 展示名称
本文地址:https://www.cnblogs.com/hchengmx/p/14883563.html @DisplayName可以给 测试类 或者 测试方法来自定义显示的名称.可以支持 空格.特 ...
- 【clickhouse专栏】基础数据类型说明
本文是clickhouse专栏第五篇,更多内容请关注本号历史文章! 一.数据类型表 clickhouse内置了很多的column数据类型,可以通过查询system.data_type_families ...
- 物联网微消息队列MQTT介绍-EMQX集群搭建以及与SpringBoot整合
项目全部代码地址:https://github.com/Tom-shushu/work-study.git (mqtt-emqt 项目) 先看我们最后实现的一个效果 1.手机端向主题 topic111 ...
- mysql调优学习笔记
性能监控 使用show profile查询剖析工具,可以指定具体的type 此工具默认是禁用的,可以通过服务器变量在绘画级别动态的修改 set profiling=1; 当设置完成之后,在服务器上执行 ...
- Tomcat部署界面使用burp爆破
打开界面显示私密连接,正常抓包. 抓包查看Authorization的数据 Basic 后面的数据是经过base64加密后的.格式为admin:123456 勾选对应参数,payload设置为Cust ...
- $.fn解析
$.fn是指jquery的命名空间,加上fn上的方法及属性,会对jquery实例每一个有效. 如扩展$.fn.abc(),即$.fn.abc()是对jquery扩展了一个abc方法,那么后面你的每一个 ...
- SpringBoot 整合文件上传 elment Ui 上传组件
SpringBoot 整合文件上传 elment Ui 上传组件 本文章记录 自己学习使用 侵权必删! 前端代码 博主最近在学 elment Ui 所以 前端使用 elmentUi 的 upload ...
- mysql备份数据库linux
备份数据库 问题描述: 我们用的是mysql,以今天遇到的情况为例,我们是在两台服务器上要搭相同的平台,部署完成后页面报错,发现是数据库的问题,我们打开数据库查看,确实数据库中少建一个wind数据 ...
- java面向对象编程---方法
二.方法 1.方法的重载 1.1 方法的签名 方法的唯一标识就是方法的签名:方法的名字和参数列表: 一个类中不能出现两个方法的签名完全一样的方法 1.2 方法的重载 方法名相同但参数列表不同称之为方法 ...