golang中的图像image处理详解

常用的图像格式有png,jpeg,gif,对应的文件后缀为png,jpg,gif,当然文件的具体内容编码才能证明存放的是哪种文件,图像文件的头部都存储了具体标志,比如jpeg前缀\xffd8,png前缀\x89PNG\r\n\x1a\n,gif前缀GIF8?a,使用文本编辑器打开图像文件就能明显看到。

实践代码:https://github.com/phprao/go-image

读取jpg文件头部

f, err := os.Open("./image1.jpg")
if err != nil {
    panic(err)
}
defer f.Close()

r := bufio.NewReader(f)
b, _ := r.Peek(2)
fmt.Println(b) // [255 216] 就是 \xffd8

因此,如果需要Decode图像的话,需要先设置前缀信息和解析方式,生成图像Encode的时候则不需要,因为你已经指明了类型。

import _ "image/jpeg"
import _ "image/png"

查看其init方法

func init() {
	image.RegisterFormat("jpeg", "\xff\xd8", Decode, DecodeConfig)
}

通过对比这个magic前缀来决定使用哪个编码解码方式。而name参数只是名称。

var pic = "./image1.jpg"

func main() {
	f, err := os.Open(pic)
	if err != nil {
		panic(err)
	}
	defer f.Close()

	img, fmtName, err := image.Decode(f)
	if err != nil {
		panic(err)
	}
	fmt.Printf("Name: %v, Bounds: %v, Color: %+v", fmtName, img.Bounds(), img.ColorModel())
}

在解析图片的时候,如果使用image.Decode(),那么需要引入图片的类型对应的包名;或者改成png.Decode()jpeg.Decode()来代替。

关于Image接口

type Image interface {
  ColorModel() color.Model // 返回图片的颜色模型
  Bounds() Rectangle       // 返回图片外框边界
  At(x, y int) color.Color // 返回(x,y)像素点的颜色
}

也就是图像的宽高,以及各个像素点的颜色值。

// 透明度
func NewAlpha(r Rectangle) *Alpha
func NewAlpha16(r Rectangle) *Alpha16

func NewRGBA(r Rectangle) *RGBA
func NewRGBA64(r Rectangle) *RGBA64

func NewNRGBA(r Rectangle) *NRGBA
func NewNRGBA64(r Rectangle) *NRGBA64

func NewGray(r Rectangle) *Gray
func NewGray16(r Rectangle) *Gray16

func NewCMYK(r Rectangle) *CMYK

func NewYCbCr(r Rectangle, subsampleRatio YCbCrSubsampleRatio) *YCbCr

func NewNYCbCrA(r Rectangle, subsampleRatio YCbCrSubsampleRatio) *NYCbCrA

func NewPaletted(r Rectangle, p color.Palette) *Paletted

func NewUniform(c color.Color) *Uniform
RGBA
RGBA represents a traditional 32-bit alpha-premultiplied color, having 8 bits for each of red, green, 
blue and alpha.
// 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
}

// NewRGBA returns a new RGBA image with the given bounds.
func NewRGBA(r Rectangle) *RGBA {
	return &RGBA{
		Pix:    make([]uint8, pixelBufferLength(4, r, "RGBA")),
		Stride: 4 * r.Dx(),
		Rect:   r,
	}
}

type Rectangle struct {
	Min, Max Point
}

RGBA对象实现了 Image 接口,用来操作带有RGB颜色以及透明度A的图像,因此每一个像素点有四个值,都是uint8类型,存储在切片 Pix 中,像素点是矩阵排列,因此需要规则转换。

其中r.Dx()为图像的宽度,即X轴最大值。

那么对于像素点(x,y),我们要怎么找到其在 Pix 切片中的起始位置呢,算法在注释中已经给明了,为了便于理解就假设Rect.Min在(0,0)位置,一个像素点占四个位置,然后就很容易得出结果 Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*4]

Stride 的注释是像素点在垂直方向上的间距,在这里它是4倍的图像宽度,方便在y轴上计算用的。

提供的方法

func (p *RGBA) ColorModel() color.Model // 返回颜色模型
func (p *RGBA) Bounds() Rectangle // 返回图像边界
func (p *RGBA) At(x, y int) color.Color // 返回制定像素点的颜色
func (p *RGBA) RGBAAt(x, y int) color.RGBA // 返回制定像素点的颜色,返回color.RGBA
func (p *RGBA) RGBA64At(x, y int) color.RGBA64 // 返回制定像素点的颜色,返回color.RGBA64
func (p *RGBA) Set(x, y int, c color.Color) // 设置制定像素点的颜色
func (p *RGBA) SetRGBA64(x, y int, c color.RGBA64) // 设置制定像素点的颜色
func (p *RGBA) SetRGBA(x, y int, c color.RGBA) // 设置制定像素点的颜色
func (p *RGBA) SubImage(r Rectangle) Image // 返回制定区域的Image,注意他们会共享Pix切片
func (p *RGBA) Opaque() bool // 判断是否每个像素点的A值都是255,即是否完全不透明
func NewRGBA(r Rectangle) *RGBA
RGBA64
RGBA64 represents a 64-bit alpha-premultiplied color, having 16 bits for each of red, green, blue and 
alpha.

前面的 RGBA 的一个像素点通过四个 uint8 来存储,而 RGBA64 的一个像素点则需要八个 uint8 来存储,也就是64位,R,G,B,A都是两个字节存储。这使得颜色表现更细腻一些。

NRGBA
NRGBA represents a non-alpha-premultiplied 32-bit color.

颜色编码没有alpha-premultiplied

NRGBA64
NRGBA64 represents a non-alpha-premultiplied 64-bit color, having 16 bits for each of red, green, blue 
and alpha.
Alpha
Alpha represents an 8-bit alpha color.

透明度 0-255,0完全透明,255完全不透明,也可以说白表示完全不透明,黑表示完全透明,灰则是半透明。

其实对于alpha,换一种说法会更好理解,它代表当前像素点对图片的贡献度,0代表没有贡献,也就是透明的,最大值代表全部贡献,也就是完全不透明,因此最终的结果就是原本的颜色与alpha分量的乘法。

支持alpha通道的格式有png, tag, tif。

dx := 500
dy := 500
img := image.NewAlpha(image.Rect(0, 0, dx, dy))
for i := 0; i < dx; i++ {
   for j := 0; j < dy; j++ {
      img.Set(i, j, color.Alpha{A: uint8(i % 256)})
   }
}

f, _ := os.Create("./image3.png")
defer f.Close()

b := bufio.NewWriter(f)
png.Encode(b, img)
b.Flush()

在这里插入图片描述

如果使用jpg来存储的话

dx := 500
dy := 500
img := image.NewAlpha(image.Rect(0, 0, dx, dy))
for i := 0; i < dx; i++ {
   for j := 0; j < dy; j++ {
      img.Set(i, j, color.Alpha{A: uint8(i % 256)})
   }
}

f, _ := os.Create("./image3.jpg")
defer f.Close()

b := bufio.NewWriter(f)
jpeg.Encode(b, img, nil)
b.Flush()

在这里插入图片描述

那是因为jpg, bmp, gif等图像格式不支持alpha通道,于是需要将 Alpha 颜色转换成 RGBA 颜色,同时会过滤掉A这个值。

func (c Alpha) RGBA() (r, g, b, a uint32) {
   a = uint32(c.A)
   a |= a << 8 // 被放大了 2^8 倍
   return a, a, a, a
}

于是 R=G=B=A,那么,为什么RGB三值相等的时候得到的颜色是黑白灰呢?

那是因为(0,0,0)表示纯黑,(255,255,255)表示纯白,三值相等说明在三个颜色方向上达到了平衡,看到的效果就是黑白灰渐变色。

Options 参数为编码质量,它的取值范围是1-100,值越高质量越好,默认是 75

Alpha16
Alpha16 represents a 16-bit alpha color.
Gray
Gray represents an 8-bit grayscale color.

灰度,指RGB三个颜色值相等的一种情况,正如上面所说,表现出来的就是黑白灰,灰度是完全不透明的。

type Gray struct {
   Y uint8
}

func (c Gray) RGBA() (r, g, b, a uint32) {
   y := uint32(c.Y)
   y |= y << 8
   return y, y, y, 0xffff
}
dx := 500
dy := 500
img := image.NewGray(image.Rect(0, 0, dx, dy))
for i := 0; i < dx; i++ {
   for j := 0; j < dy; j++ {
      img.Set(i, j, color.Gray{Y: uint8(i % 256)})
   }
}

f, _ := os.Create("./image4.jpg")
defer f.Close()

b := bufio.NewWriter(f)
jpeg.Encode(b, img, nil)
b.Flush()

在这里插入图片描述

Gray16
Gray16 represents a 16-bit grayscale color.
CMYK
// CMYK represents a fully opaque CMYK color, having 8 bits for each of cyan,
// magenta, yellow and black.
//
// It is not associated with any particular color profile.
type CMYK struct {
   C, M, Y, K uint8
}

印刷四色模式是彩色印刷时采用的一种套色模式,利用色料的三原色混色原理,加上黑色油墨,共计四种颜色混合叠加,形成所谓"全彩印刷"。四种标准颜色是:C:Cyan = 青色,又称为’天蓝色’或是’湛蓝’;M:Magenta = 品红色,又称为’洋红色’;Y:Yellow = 黄色;K:blacK=黑色,虽然有文献解释说这里的K应该是Key Color(定位套版色),但其实是和制版时所用的定位套版观念混淆而有此一说。此处缩写使用最后一个字母K而非开头的B,是为了避免与Blue混淆。CMYK模式是减色模式,相对应的RGB模式是加色模式。

减色模式指的是光线照射到颜料上之后有的光色被吸收了,剩余的反射到人眼形成感觉。
加色模式指的是通过三原色的叠加形成新的颜色感觉。

它和RGB相比有一个很大的不同:RGB模式是一种屏幕显示发光的色彩模式,你在一间黑暗的房间内仍然可以看见屏幕上的内容;

CMYK是一种用于印刷品依靠反光的色彩模式,我们是怎样阅读报纸的内容呢?是由阳光或灯光照射到报纸上,再反射到我们的眼中,才看到内容。它需要有外界光源,如果你在黑暗房间内是无法阅读报纸的。

只要在屏幕上显示的图像,就是RGB模式表现的。只要是在印刷品上看到的图像,就是CMYK模式表现的。比如期刊、杂志、报纸、宣传画等,都是印刷出来的,那么就是CMYK模式的了。

RGB以黑色为底色加,即RGB均为0是黑色,均为255是白色。

CMY以白色为底色减,即CMY均为0是白色,均为100%是黑色(但在实际中,由于油墨的纯度等问题这样得不到纯正的黑色,因此引入K)。

dx := 500
dy := 500
img := image.NewCMYK(image.Rect(0, 0, dx, dy))
for i := 0; i < dx; i++ {
   for j := 0; j < dy; j++ {
      img.Set(i, j, color.CMYK{C: uint8(i % 256), M: uint8(i % 256), Y: uint8(i % 256), K: uint8(i % 256)})
   }
}

f, _ := os.Create("./image5.jpg")
defer f.Close()

b := bufio.NewWriter(f)
jpeg.Encode(b, img, nil)
b.Flush()

在这里插入图片描述

可以看到与RGB颜色正好相反。

所以,如果图像是要印刷出来的话,那么应该采用CMYK颜色值,因为用RGB颜色打印出来的效果会有较大差异。

YCbCr
// YCbCr represents a fully opaque 24-bit Y'CbCr color, having 8 bits each for
// one luma and two chroma components.
//
// JPEG, VP8, the MPEG family and other codecs use this color model. Such
// codecs often use the terms YUV and Y'CbCr interchangeably, but strictly
// speaking, the term YUV applies only to analog video signals, and Y' (luma)
// is Y (luminance) after applying gamma correction.
//
// Conversion between RGB and Y'CbCr is lossy and there are multiple, slightly
// different formulae for converting between the two. This package follows
// the JFIF specification at https://www.w3.org/Graphics/JPEG/jfif3.pdf.
type YCbCr struct {
   Y, Cb, Cr uint8
}

这是另外一种颜色模式。使用亮度(黑白灰),蓝色、红色的偏移量描述图像信号的色彩空间,应用在视频领域。jpeg图像使用的就是YCbCr颜色。

使用YCbCr是因为,人眼对于亮度对比的感知能力比色彩的感知能力要强,把亮度分量分离出来后,可以有针对性地使用不同的量化表、采样因子来达到不同的压缩率,且人眼感知不强。如果只有Y信号分量,那么表示的就是黑白灰度图像。

jpeg图像使用的就是 YCbCr 颜色。

关于RGB, YUV, YCbCr三种颜色空间

NYCbCrA
// NYCbCrA represents a non-alpha-premultiplied Y'CbCr-with-alpha color, having
// 8 bits each for one luma, two chroma and one alpha component.
type NYCbCrA struct {
   YCbCr
   A uint8
}
Paletted

调色板

前面提到的类型,都是直接将像素点的各个分量存储在 Pix 中,而调色板类型,则是先有一个调色板p存储了一些color值,而 Pix 中存储的值是p中的索引,这样像素点的颜色也就对应上了,但是Pix中byteePerPixel为1,也就是索引值最大255,所以调色板里最多有256个color。调色板里的color要先预制好,Set方法只能去里面匹配,如果没有找到则索引为0。

func NewPaletted(r Rectangle, p color.Palette) *Paletted {
   return &Paletted{
      Pix:     make([]uint8, pixelBufferLength(1, r, "Paletted")),
      Stride:  1 * r.Dx(),
      Rect:    r,
      Palette: p,
   }
}

NewPaletted 函数得到的是一个调色板,还没有设置 Pix,因此得到的图像的表现就是由调色板第一个颜色平铺而成的背景。

默认提供的两个调色板 palette.Plan9,palette.WebSafe

如果你的图像的颜色区间不大,那么使用调色板模式就可以大大减少存储空间。

Uniform
// Uniform is an infinite-sized Image of uniform color.
// It implements the color.Color, color.Model, and Image interfaces.
type Uniform struct {
   C color.Color
}

func (c *Uniform) Bounds() Rectangle { 
    return Rectangle{Point{-1e9, -1e9}, Point{1e9, 1e9}} 
}

// NewUniform returns a new Uniform image of the given color.
func NewUniform(c color.Color) *Uniform {
	return &Uniform{c}
}

用同一个color来填充一个无限大的幕布,我们可以从幕布上截取一块,当然,这个功能也好实现,只是官方提供了而已,

dx := 500
dy := 500
r := image.Rect(0, 0, dx, dy)
img := image.NewRGBA(r)
for i := 0; i < dx; i++ {
    for j := 0; j < dy; j++ {
        img.Set(i, j, color.Black)
    }
}

或者

dx := 500
dy := 500
r := image.Rect(0, 0, dx, dy)
img := image.NewRGBA(r)
imgBack := image.NewUniform(image.Black)
draw.Draw(img, r, imgBack, image.Point{}, draw.Src)
gif图像
type GIF struct {
   // 连续的图片
   Image []*image.Paletted 
   // 连续的延迟时间,单位是百分之一秒,delay中数值表示展示的时间,10就表示0.1秒
   Delay []int             
   // LoopCount 控制动画的重复播放规则
   // 0 表示无限循环
   // -1 表示只播放一次
   // 其它的播放 LoopCount+1 次
   LoopCount int
   // Disposal is the successive disposal methods, one per frame. For
   // backwards compatibility, a nil Disposal is valid to pass to EncodeAll,
   // and implies that each frame's disposal method is 0 (no disposal
   // specified).
   Disposal []byte
   // Config is the global color table (palette), width and height. A nil or
   // empty-color.Palette Config.ColorModel means that each frame has its own
   // color table and there is no global color table. Each frame's bounds must
   // be within the rectangle defined by the two points (0, 0) and
   // (Config.Width, Config.Height).
   //
   // For backwards compatibility, a zero-valued Config is valid to pass to
   // EncodeAll, and implies that the overall GIF's width and height equals
   // the first frame's bounds' Rectangle.Max point.
   Config image.Config
   // BackgroundIndex is the background index in the global color table, for
   // use with the DisposalBackground disposal method.
   BackgroundIndex byte
}

gif图像本身可以存储静态图和动态图,但如今该图像主要被用来存储动态图,且在大部分系统上都支持。但是相对于webp这些新式的动态图格式,其在颜色质量和压缩率上的表现相对不如人意。gif图像和一般的图像直接存储图像内容不同,而是通过一个颜色表(调色板)映射来表达对应的图像的内容。也就是说图像中存在一张颜色表存储图像中出现的颜色,然后每一帧的图像通过颜色索引来表示颜色。GIF支持的最大颜色表数量为8bit,即256色,所以一般的动态图能够看到图像中存在明显的颜色梯度变化的效应,也就是不够清晰。

func image14() {
   p1 := image.NewPaletted(image.Rect(0, 0, 100, 100), palette.Plan9)
   p2 := image.NewPaletted(image.Rect(0, 0, 100, 100), palette.Plan9)

   for y := 0; y < 100; y++ {
      p1.Set(50, y, color.RGBA{R: 0, G: 0, B: 255, A: 255})
   }

   for y := 0; y < 100; y++ {
      p2.Set(y, 50, color.RGBA{R: 255, G: 0, B: 0, A: 255})
   }

   g := &gif.GIF{
      Image:     []*image.Paletted{p1, p2},
      Delay:     []int{10, 100}, // p1展示0.1秒,p2展示1秒
      LoopCount: 0,
   }
   f1, _ := os.Create("./image14.gif")
   defer f1.Close()

   gif.EncodeAll(f1, g)
}

在这里插入图片描述

func image15() {
   p1 := image.NewPaletted(image.Rect(0, 0, 100, 100), palette.Plan9)
   p2 := image.NewPaletted(image.Rect(0, 0, 100, 100), palette.Plan9)
   p3 := image.NewPaletted(image.Rect(0, 0, 100, 100), palette.Plan9)

   m1, _ := os.Open("./1111.jpg")
   defer m1.Close()
   img1, _, _ := image.Decode(m1)

   m2, _ := os.Open("./2222.jpg")
   defer m2.Close()
   img2, _, _ := image.Decode(m2)

   m3, _ := os.Open("./3333.jpg")
   defer m3.Close()
   img3, _, _ := image.Decode(m3)

   draw.Draw(p1, p1.Bounds(), img1, img1.Bounds().Min, draw.Src)
   draw.Draw(p2, p2.Bounds(), img2, img2.Bounds().Min, draw.Src)
   draw.Draw(p3, p3.Bounds(), img3, img3.Bounds().Min, draw.Src)

   g := &gif.GIF{
      Image:     []*image.Paletted{p1, p2, p3},
      Delay:     []int{100, 100, 100},
      LoopCount: 0,
   }
   f1, _ := os.Create("./image15.gif")
   defer f1.Close()

   gif.EncodeAll(f1, g)
}

在这里插入图片描述

图片操作
import "image/draw"

func Draw(dst Image, r image.Rectangle, src image.Image, sp image.Point, op Op)

func DrawMask(dst Image, r image.Rectangle, src image.Image, sp image.Point, mask image.Image, 
mp image.Point, op Op)

参数说明

dst  要绘制的目标图像
r    在目标图像的哪个区域绘制
src  源图像
sp   start point,从源图像的哪个点开始,范围是 r 的长宽
mask 是绘图时用的蒙版,控制替换图片的方式
op   两个图像采用不同的混合方式得出的结果不同,此处有 Porter-Duff 12 等式,即12种混合方式,参考 
https://blog.csdn.net/ison81/article/details/5468763 和 
http://www.blogjava.net/onedaylover/archive/2008/01/16/175675.html

这里的参数有点多,咋一看有些乱,目的就是从 src 中截取一块放在 dst 中的指定位置,首先 dst 的目标区域起点为 r.Min,src 的目标区域起点为 sp,mask 的目标区域起点为 mp,其次 dst, src, mask 截取宽高都是 r 的宽高。

再将截取的部分放到 dst 上之前,还可以套一层蒙版来改变一些效果,蒙版一般使用 alpha 颜色模型,这样 alpha(255)的地方完全不透明,于是 src 上的图像就能显示出来,相反,alpha(0)的地方完全透明,于是 src 上的图像就看不到, 也可以理解为涂白色的部分显示,涂黑色的部分隐藏,而灰色部分为半透明。默认的蒙版是 nil,也就是纯白色蒙版的效果。

蒙版的作用有很多

抠图
参数中只能截图长方形的图像,如果想要截图圆形的图像出来,此时使用mask,打底的 alpha(0),然后使用 alpha(255)画一个圆形,即可实现。
在这里插入图片描述

无缝拼接图片
在这里插入图片描述
我们可以直接把水母放在海洋上,如下图,但两张图之间有明显的界线
在这里插入图片描述
我们给图2添加蒙版,在蒙版上使用白到黑的渐变,结果如下:
在这里插入图片描述
制作水中的倒影
复制图片后,翻转,为倒影添加白到黑的渐变,即可产生渐渐消失的倒影效果
在这里插入图片描述

1、图像合并

func image8() {
   // 蓝色 500*500
   img1 := image.NewRGBA(image.Rect(0, 0, 500, 500))
   for i := 0; i < img1.Bounds().Max.X; i++ {
      for j := 0; j < img1.Bounds().Max.Y; j++ {
         img1.Set(i, j, color.RGBA{B: 255, A: 255})
      }
   }

   // 红色 200*200
   img2 := image.NewRGBA(image.Rect(0, 0, 200, 200))
   for i := 0; i < img2.Bounds().Max.X; i++ {
      for j := 0; j < img2.Bounds().Max.Y; j++ {
         img2.Set(i, j, color.RGBA{R: 255, A: 255})
      }
   }

   draw.Draw(img1, img2.Bounds(), img2, img2.Bounds().Min, draw.Src)

   f, _ := os.Create("./image8.jpg")
   defer f.Close()

   b := bufio.NewWriter(f)
   jpeg.Encode(b, img1, nil)
   b.Flush()
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BVbqp6Qr-1675236120865)(D:\dev\php\magook\trunk\server\md\img\image-20230131164352370.png)]

2、图片置灰

使用灰度Gray作为目标即可

func image9() {
   f, err := os.Open("./image1.jpg")
   if err != nil {
      panic(err)
   }
   defer f.Close()

   src, _, _ := image.Decode(f)

   dst := image.NewGray(src.Bounds())

   draw.Draw(dst, src.Bounds(), src, src.Bounds().Min, draw.Src)

   f1, _ := os.Create("./image9.jpg")
   defer f1.Close()

   b := bufio.NewWriter(f1)
   jpeg.Encode(b, dst, nil)
   b.Flush()
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bpzXjqnC-1675236120866)(D:\dev\php\magook\trunk\server\md\img\image-20230131170943418.png)]

3、图片裁剪

func image10() {
   f, err := os.Open("./image1.jpg")
   if err != nil {
      panic(err)
   }
   defer f.Close()

   src, _, _ := image.Decode(f)

   // jpeg 使用的 YCbCr 颜色
   dst := src.(*image.YCbCr).SubImage(image.Rect(0, 0, 500, 500))

   f1, _ := os.Create("./image10.jpg")
   defer f1.Close()

   b := bufio.NewWriter(f1)
   jpeg.Encode(b, dst, nil)
   b.Flush()
}

4、图片缩放

图片的缩小可以理解为只抽取部分像素点,比如等比例缩小为原来的一半,那么就是每间隔一个点抽取一个像素,然后将这些像素点排列在新的画布上,原来的1200*800变成600*400,没有原来清晰了。

func image11() {
   f, err := os.Open("./image1.jpg")
   if err != nil {
      panic(err)
   }
   defer f.Close()

   scale := 2 // 等比例缩小一半

   src, _, _ := image.Decode(f)
   width := src.Bounds().Max.X
   height := src.Bounds().Max.Y

   dstWidth := width / scale
   dstHeight := height / scale

   dst := image.NewRGBA(image.Rect(0, 0, dstWidth, dstHeight))

   for i := 0; i < dstWidth; i++ {
      for j := 0; j < dstHeight; j++ {
         dst.Set(i, j, src.At(i*scale, j*scale))
      }
   }

   f1, _ := os.Create("./image11.jpg")
   defer f1.Close()

   b := bufio.NewWriter(f1)
   jpeg.Encode(b, dst, nil)
   b.Flush()
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DqRJZrJR-1675236120866)(D:\dev\php\magook\trunk\server\md\img\image-20230201094227274.png)]

等比例缩放和非等比例缩放是一样的道理。次方法成为邻域插值法。

缩小操作效果还可以,但是放大操作由于算法的原因会导致一些像素点不太合理,有时候会得到不存在的像素点,因此效果不是太好。

传统差值原理和评价

在传统图像插值算法中,邻插值较简单,容易实现,早期的时候应用比较普遍。但是,该方法会在新图像中产生明显的锯齿边缘和马赛克现象。双线性插值法具有平滑功能,能有效地克服邻法的不足,但会退化图像的高频部分,使图像细节变模糊。在放大倍数比较高时,高阶插值,如双三次和三次样条插值等比低阶插值效果好。这些插值算法可以使插值生成的像素灰度值延续原图像灰度变化的连续性,从而使放大图像浓淡变化自然平滑。但是在图像中,有些像素与相邻像素间灰度值存在突变,即存在灰度不连续性。这些具有灰度值突变的像素就是图像中描述对象的轮廓或纹理图像的边缘像素。在图像放大中,对这些具有不连续灰度特性的像素,如果采用常规的插值算法生成新增加的像素,势必会使放大图像的轮廓和纹理模糊,降低图像质量。

基于边缘的图像插值算法

为了克服传统方法的不足, 提出了许多边缘保护的插值方法,对插值图像的边缘有一定的增强, 使得图像的视觉效果更好, 边缘保护的插值方法可以分为两类: 基于原始低分辨图像边缘的方法和基于插值后高分辨率图像边缘的方法。基于原始低分辨率图像边缘的方法:(1)首先检测低分辨率图像的边缘, 然后根据检测的边缘将像素分类处理, 对于平坦区域的像素,采用传统方法插值;对于边缘区域的像素, 设计特殊插值方法, 以达到保持边缘细节的目的。(2)基于插值后高分辨率图像边缘的方法这类插值方法:首先采用传统方法插值低分辨率图像,然后检测高分辨率图像的边缘,最后对边缘及附近像素进行特殊处理, 以去除模糊, 增强图像的边缘。

基于区域的图像插值算法

首先将原始低分辨率图像分割成不同区域,然后将插值点映射到低分辨率图像, 判断其所属区域, 最后根据插值点的邻域像素设计不同的插值公式, 计算插值点的值。

官方包golang.org/x/image/draw

提供了对图像高级操作。

采用双线性插值算法(bilinear)来放大图像。

import draw2 "golang.org/x/image/draw"
func image13() {
   f, err := os.Open("./image1.jpg")
   if err != nil {
      panic(err)
   }
   defer f.Close()
   src, _, _ := image.Decode(f)

   scale := 2
   dstWidth := src.Bounds().Max.X * scale
   dstHeight := src.Bounds().Max.Y * scale

   dr := image.Rect(0, 0, dstWidth, dstHeight)
   dst := image.NewRGBA(dr)

   draw2.BiLinear.Scale(dst, dr, src, src.Bounds(), draw2.Src, nil)

   f1, _ := os.Create("./image13.jpg")
   defer f1.Close()

   b := bufio.NewWriter(f1)
   jpeg.Encode(b, dst, nil)
   b.Flush()
}

golang.org/x/image/draw 提供了四种插值算法,他们的特点在注释中已经说明了。

var (
   // NearestNeighbor is the nearest neighbor interpolator. It is very fast,
   // but usually gives very low quality results. When scaling up, the result
   // will look 'blocky'.
   NearestNeighbor = Interpolator(nnInterpolator{})

   // ApproxBiLinear is a mixture of the nearest neighbor and bi-linear
   // interpolators. It is fast, but usually gives medium quality results.
   //
   // It implements bi-linear interpolation when upscaling and a bi-linear
   // blend of the 4 nearest neighbor pixels when downscaling. This yields
   // nicer quality than nearest neighbor interpolation when upscaling, but
   // the time taken is independent of the number of source pixels, unlike the
   // bi-linear interpolator. When downscaling a large image, the performance
   // difference can be significant.
   ApproxBiLinear = Interpolator(ablInterpolator{})

   // BiLinear is the tent kernel. It is slow, but usually gives high quality
   // results.
   BiLinear = &Kernel{1, func(t float64) float64 {
      return 1 - t
   }}

   // CatmullRom is the Catmull-Rom kernel. It is very slow, but usually gives
   // very high quality results.
   //
   // It is an instance of the more general cubic BC-spline kernel with parameters
   // B=0 and C=0.5. See Mitchell and Netravali, "Reconstruction Filters in
   // Computer Graphics", Computer Graphics, Vol. 22, No. 4, pp. 221-228.
   CatmullRom = &Kernel{2, func(t float64) float64 {
      if t < 1 {
         return (1.5*t-2.5)*t*t + 1
      }
      return ((-0.5*t+2.5)*t-4)*t + 2
   }}

   // TODO: a Kaiser-Bessel kernel?
)

其他功能,比如将文字写入图像,数字验证码,水印等等都是基于此包。

参考 https://blog.csdn.net/picone/article/details/123788128

golang绘制2D图形库
github.com/fogleman/gg

https://pkg.go.dev/github.com/fogleman/gg#section-readme

https://blog.csdn.net/qq_26037391/article/details/121457209

imgo 包
go get -u github.com/fishtailstudio/imgo

ImGo 是一个开源的 Golang 图片处理和操作的库。它为创建、编辑和合成图像提供了一种更简单、更具表现力的方法。

官方文档 :English Documentation | 简体中文文档

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
在Go,接口是一个使用非常广泛的概念,可以让您以一种非常灵活的方式编写代码,同时保持代码的可读性和可维护性。接口是一种类型,它定义了一组方法,这些方法可以被不同的类型实现。下面是一些关于Go接口的详细信息: 1. 接口是一种类型。 在Go,接口是一种类型,它定义了一组方法。一个类型可以实现一个或多个接口。 2. 接口定义方法。 接口是由一组方法定义的,这些方法没有实现。一个类型可以实现接口定义的所有方法,只要它们按照接口定义的方式进行实现即可。 3. 接口实现。 一个类型可以实现一个或多个接口。一个类型只需要实现接口定义的所有方法,就可以称之为实现了这个接口。 4. 接口的实现是隐式的。 在Go,接口的实现是隐式的。一个类型只需要实现接口定义的所有方法,就可以自动实现这个接口。 5. 接口变量和接口值。 接口变量是一个接口类型的变量,它可以存储任何实现了该接口的值。接口值是一个接口类型的值,它可以存储任何实现了该接口的值。 6. 空接口。 空接口是一种不包含任何方法的接口。它可以存储任何类型的值。 7. 类型断言。 类型断言是一种将接口值转换为其他类型的方法。如果转换失败,它会返回一个零值和一个错误。 8. 接口的嵌套。 接口的嵌套是一种将多个接口组合成一个新接口的方法。新接口包含了所有组合接口的方法。 9. 接口的多态性。 接口的多态性是一种让不同类型的对象可以以相同的方式进行处理的方法。这样可以让您的代码更加灵活和可维护。 以上是关于Go接口的一些概念和用法的详细介绍。如果您想要更深入地了解接口,可以参考Go的官方文档。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值