之前做项目有一个图片裁剪需求,没想到Golang标准库对图片处理的支持还挺强大的,在此记录一下,以备后用。

在Golang里面图片处理主要依赖一个image库,支持常见的png、jpeg、gif等格式,使用这个库我们可以做生成图片、读取图片、裁剪图片等操作。

这篇文章只介绍某几个常用的操作,详细API和使用的技巧大家可以参考Go官方文档和博客,比如:https://blog.golang.org/image-draw

func genImage() {    m := image.NewRGBA(image.Rect(0, 0, 640, 480))    draw.Draw(m, m.Bounds(), &image.Uniform{C: color.White}, image.Point{}, draw.Src)    f, err := os.Create("demo.jpeg")    if err != nil {        panic(err)    }    err = jpeg.Encode(f, m, nil)    if err != nil {        panic(err)    }}

以上代码可以生成一个长宽640x480,内容白色的图片,虽然没什么用,但是咱可以通过这些代码简单了解一下这个库的一些API。

图片也是一个文件,读取图片文件和一般文件是一样的,但是如果我们把这个文件当成图片去处理,必须经过一个decode操作,decode操作就是去校验这个文件是否是图片,是否是支持的格式。

不同的图片格式的编码存储方式都不一样,这些信息存放在文件头部分,而当我们需要把处理完的图片保存到文件里面的时候,还需经过一个encode操作。

本文以这个文件为例,展示一些操作:

func readImage()  {    f, err := os.Open("ubuntu.png")    if err != nil {        panic(err)    }    // decode图片    m, err := png.Decode(f)    if err != nil {        panic(err)    }    fmt.Printf("%v", m.Bounds())     // 图片长宽    fmt.Printf("%v", m.ColorModel()) // 图片颜色模型    fmt.Printf("%v", m.At(100,100))  // 该像素点的颜色}

通过decode读取的只是一个image.Image类型,其实现的方法并不多,只有3个,如果我们要做一些操作,则需把其转换成相应的颜色模型,比如RGBA、CMYK等。

// Image is a finite rectangular grid of color.Color values taken from a color// model.type Image interface {    // ColorModel returns the Image's color model.    ColorModel() color.Model    // Bounds returns the domain for which At can return non-zero color.    // The bounds do not necessarily contain the point (0, 0).    Bounds() Rectangle    // At returns the color of the pixel at (x, y).    // At(Bounds().Min.X, Bounds().Min.Y) returns the upper-left pixel of the grid.    // At(Bounds().Max.X-1, Bounds().Max.Y-1) returns the lower-right one.    At(x, y int) color.Color}

在源码里面大家可以看到很多下面类似代码,有很多种不同的颜色模型,他们都实现了这个3个方法。

// RGBA is an in-memory image whose At method returns color.RGBA values.type RGBA struct {    // Pix holds the image's pixels, in R, G, B, A order. The pixel at    // (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*4].    Pix []uint8    // Stride is the Pix stride (in bytes) between vertically adjacent pixels.    Stride int    // Rect is the image's bounds.    Rect Rectangle}func (p *RGBA) ColorModel() color.Model { return color.RGBAModel }func (p *RGBA) Bounds() Rectangle { return p.Rect }func (p *RGBA) At(x, y int) color.Color {    return p.RGBAAt(x, y)}

下面这个操作就可以把整个图片截取上半部分,而且是无损的裁剪,其主要依靠SubImage方法:

func readImage() {    f, err := os.Open("ubuntu.png")    if err != nil {        panic(err)    }    // decode图片    m, err := png.Decode(f)    if err != nil {        panic(err)    }    rgba := m.(*image.RGBA)    subImage := rgba.SubImage(image.Rect(0, 0, 266, 133)).(*image.RGBA)    // 保存图片    create, _ := os.Create("new.png")    err = png.Encode(create, subImage)    if err != nil {        panic(err)    }}

base64适合一些小图片的存储,经常也会用到,操作很简单:

func b64() {    f, err := os.Open("ubuntu.png")    if err != nil {        panic(err)    }    all, _ := ioutil.ReadAll(f)    str := base64.StdEncoding.EncodeToString(all)        fmt.Printf("%s", str)}

实际上,这块和image库没关系,但是需要注意一点,上述的结果并不包含头部分,所以得自己加一个,比如: data:image/png;base64,

为了节省流量和存储空间,一般都会对图片做有损压缩,比如手机拍一张照片大概有7-8MB,但是网页上一般100-200k大小就可以了,太大的话影响用户体验不说,服务器带宽压力也大。

Go的话标准库支持还不够,如果自己去实现也比较麻烦,这里推荐一个第三方库:https://github.com/nfnt/resize

这个库支持多种采样算法,以满足各种需求,支持等比或者固定比缩放,有需要的人可以尝试一下。

package mainimport (    "github.com/nfnt/resize"    "image/jpeg"    "log"    "os")func main() {    // open "test.jpg"    file, err := os.Open("test.jpg")    if err != nil {        log.Fatal(err)    }    // decode jpeg into image.Image    img, err := jpeg.Decode(file)    if err != nil {        log.Fatal(err)    }    file.Close()    // resize to width 1000 using Lanczos resampling    // and preserve aspect ratio    m := resize.Resize(1000, 0, img, resize.Lanczos3)    out, err := os.Create("test_resized.jpg")    if err != nil {        log.Fatal(err)    }    defer out.Close()    // write new image to file    jpeg.Encode(out, m, nil)}

总体来说,这个库的功能还是比较简单的,能够满足一些基本需求,再深入的话可能就得自己去实现了,毕竟Go只是通用语言,不可能把一些图片处理算法都给你实现好,但是框架已经给你搭好了。