diff --git a/blog/image/image.md b/blog/image/image.md new file mode 100644 index 0000000..cc62178 --- /dev/null +++ b/blog/image/image.md @@ -0,0 +1,648 @@ +# image package + +​ 作用:image包实现了一个基本的2D图像库. + +~~~go +func NewNRGBA + +func NewNRGBA(r Rectangle) *NRGBA +~~~ + +​ NewNRGBA返回一个新的带有给定边界的NRGBA图像。NRGBA是一个内存中的图像,它的At方法返回颜色的NRGBA值。 + +~~~go +type NRGBA struct { + + Pix []uint8 + + Stride int + + Rect Rectangle + +} + +func (*NRGBA) Set + +func (p *NRGBA) Set(x, y int, c color.Color) //设定指定位置的color。 + +func Rect + +func Rect(x0, y0, x1, y1 int) Rectangle //Rect是Rectangle的缩写{Pt(x0, y0), Pt(x1, y1)}. +~~~ + +# Image/draw包用法: + +​ draw 包仅仅定义了一个操作:通过可选的蒙版图(mask image),把一个原始图片绘制到目标图片上,这个操作是出奇的灵活,可以优雅和高效的执行很多常见的图像处理任务,比如使用nil蒙版调用DrawMask,其代码如下: + +~~~go +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) +~~~ + +​ 第一个函数Draw是没有使用蒙版mask的调用方法,它内部其实就是调用的mask为 nil的方法,它的参数描述如下: + +​ dst 是绘图的背景图,r是背景图的绘图区域,src 是要绘制的图,sp是src对应的绘图开始点(绘制的大小 r变量定义),mask 是绘图时用的蒙版,控制替换图片的方式。mp是绘图时蒙版开始点(绘制的大小r变量定义了) + +​ 下图就是几个相关的例子: + +​ mask 蒙版是渐变 + +​ 1 + +​ 给一个矩形填充颜色: + +​ 使用 Draw方法的逻辑效果图: + +​ 2 + +​ 代码: + +~~~go + 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) +~~~ + +​ 拷贝图片的一部分效果特效如下: + +​ 3 + +​ 相关代码: + +~~~go + r := image.Rectangle{dp, dp.Add(sr.Size())} // 获得更换区域 + + draw.Draw(dst, r, src, sr.Min, draw.Src) +~~~ + +​ 如果是复制整个图片,则更简单: + +~~~go + sr = src.Bounds() // 获取要复制图片的尺寸 + + r := sr.Sub(sr.Min).Add(dp) // 目标图的要剪切区域 + + draw.Draw(dst, r, src, sr.Min, draw.Src) +~~~ + +​ 图片滚动效果如下图: + +​ ![4](./images/图片滚动效果.png) + +​ 假设我们需要把图片 m 上移20个像素. + +​ 相关代码: + +~~~go + b := m.Bounds() + + p := image.Pt(0, 20) + + // 注意,尽管第二个参数是b,但由于剪切,有效矩形变小了。 + + draw.Draw(m, b, m, b.Min.Add(p), draw.Src) + + dirtyRect := b.Intersect(image.Rect(b.Min.X, b.Max.Y-20, b.Max.X, b.Max.Y)) +~~~ + +​ 把一个图片转成RGBA格式,效果图: + +​ 5 + +​ 相关代码: + +~~~go + b := src.Bounds() + + m := image.NewRGBA(b) + + draw.Draw(m, b, src, b.Min, draw.Src) +~~~ + +​ 通过蒙版画特效效果图 + +​ 6 + +相关代码 + +~~~go + 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{255} + + } + + return color.Alpha{0} + + } + + draw.DrawMask(dst, dst.Bounds(), src, image.ZP, &circle{p, r}, image.ZP, draw.Over) +~~~ + +注意,一个image对象只需要实现下面几个就可,这也就是Go接口强大的地方. + +~~~go + type Image interface { + + // 返回图像的颜色模型。 + + ColorModel() color.Model + + // Bounds返回一个可以返回非零颜色的域。 + + // 边界不一定包含点(0,0)。 + + Bounds() Rectangle + + // 返回(x, y)处像素的颜色。 + + // At(Bounds().Min.X, Bounds().Min.Y) 返回网格左上角的像素的值。 + + // At(Bounds().Max.X-1, Bounds().Max.Y-1) 返回右下角的像素的值。 + + At(x, y int) color.Color + + } +~~~ + +​ 画一个字体 + +​ 效果图,画一个蓝色背景的字体。 + +​ 7 + +​ 相关伪代码: + +~~~go + src := &image.Uniform{color.RGBA{0, 0, 255, 255}} + + mask := theGlyphImageForAFont() + + mr := theBoundsFor(glyphIndex) + + draw.DrawMask(dst, mr.Sub(mr.Min).Add(p), src, image.ZP, mask, mr.Min, draw.Over) +~~~ + +# image/color包用法: + +​ Color包实现了一个基本的颜色库:color是一个接口,它定义了可以被视为颜色的任何类型的最小方法集:可以转换为红色、绿色、蓝色和alpha值的颜色。转换可能是有损的,例如从CMYK或YCbCr颜色空间转换。其代码演示如下: + +~~~go +type Color interface { + + RGBA() (r, g, b, a uint32) + +} +~~~ + +​ RGBA返回字母预先乘上的红色、绿色、蓝色和颜色的alpha值。每个值的范围都在[0,0xFFFF]内,但是由uint32表示,因此乘以一个直到0xFFFF的混合因子不会溢出。 + +​ 关于返回值有三个重要的点:首先,红色,绿色和蓝色是先预乘alpha:完全饱和的红色也是25%透明,由RGBA返回75%。第二,容器有16位:100%的红色是由RGBA返回一个65535的r的值,而不是255,所以从CMYK或YCbCr转换没有损耗。第三,返回的类型是uint32,将最大值设置为65535以保证两个值相乘不会溢出。 + +​ Image/color包还定义了许多实现color接口的具体类型。例如,RGBA是一个结构体,它代表了经典的“每个容器8位颜色”,代码如下: + +~~~go +type RGBA struct { + + R, G, B, A uint8 + +} +~~~ + +​ 不过要注意的是:RGBA的R字段是8位字符预乘范围[0,255]的颜色。RGBA通过将该值乘以0x101来满足颜色接口,从而在范围[0,65535]内生成一个16位的颜色。类似地,NRGBA结构类型表示一种8位的没有alpha预乘的颜色,就像PNG图像格式所使用的那样。当直接操作'NRGBA '的字段时,其值没有预乘alpha,但当调用RGBA方法时,返回值要预乘alpha。 + +# Image/palette包用法: + +​ 调色板这个包为提供为图像库提供了一个标准的调色板。 + +Image/color/palette/gen.go:运行程序时会生成palette .go文件。 + +Image/color/palette/generate.go:在生成palette.go文件时会自动运行。 + +Image/color/palette/palette.go:定义了两种调色板——Plan9和WebSafe: + +​ Plan9是一个256色的调色板,将24位RGB空间分割成4×4×4的细分,每个子立方体有4个阴影。与WebSafe相比,其想法是通过将颜色立方体切成更少的单元来降低颜色分辨率,并使用额外的空间来增加强度分辨率。这样就得到了16个灰色阴影(4个灰色子立方体,每个子立方体中有4个样本),每种原色和副色的13个阴影(3个子立方体,4个样本加上黑色),并合理地选择了覆盖颜色立方体其余部分的颜色。其优点是可以更好地表示连续色调。 + +​ WebSafe是一个216色的调色板,由于Netscape Navigator的早期版本而流行起来。它也被称为网景颜色多维数据集。 + +# Image/gif包用法: + +​ gif包实现了gif图片的解码及编码。 + +~~~go +func Decode(r io.Reader) (image.Image, error) //Decode从r中读取一个GIF图像,然后返回的image.Image是第一个嵌入的图。 + +func DecodeConfig(r io.Reader) (image.Config, error) //DecodeConfig不需要解码整个图像就可以返回全局的颜色模型和GIF图片的尺寸。 + +type Config struct { + + ColorModel color.Model + + Width, Height int + +} + +//Config返回图像的颜色model和尺寸 +func Encode(w io.Writer, m image.Image, o *Options) error //将图片m按照gif模式写入w中 + +type Options struct { + + // NumColors是图片中使用颜色的最大值,它的范围是1-256 + + NumColors int + + // Quantizer经常被用来通过NumColors产生调色板,palette.Plan9 被用来替代nil Quantizer + + Quantizer draw.Quantizer + + // Drawer i用于将源图片转化为期望的调色板, draw.FloydSteinberg 用来替代一个空 Drawer. + + Drawer draw.Drawer + +} +func EncodeAll(w io.Writer, g *GIF) error //将图片按照帧与帧之间指定的循环次数和时延写入w中 + +type GIF struct { + + Image []*image.Paletted // 连续的图片 + + Delay []int // 连续的延迟时间,每一帧单位都是百分之一秒,delay中数值表示其两个图像动态展示的时间间隔 + + LoopCount int // 循环次数,如果为0则一直循环。 + +} + +func DecodeAll(r io.Reader) (*GIF, error) //DecodeAll 从r上读取一个GIF图片,并且返回顺序的帧和时间信息 +~~~ + +# image/jpeg包用法: + +​ jpeg包实现了jpeg图片的编码和解码。 + +~~~go +func Decode(r io.Reader) (image.Image, error) //Decode读取一个jpeg文件,并将他作为image.Image返回 + + func DecodeConfig(r io.Reader) (image.Config, error) //无需解码整个图像,DecodeConfig变能够返回整个图像的尺寸和颜色(Config具体定义查看gif包中的定义) + + func Encode(w io.Writer, m image.Image, o *Options) error //按照4:2:0的基准格式将image写入w中,如果options为空的话,则传递默认参数 + +type Options struct { + Quality int +}//Options是编码参数,它的取值范围是1-100,值越高质量越好 + +type FormatError //用来报告一个输入不是有效的jpeg格式 + +type FormatError string + +func (e FormatError) Error() string + +type Reader //不推荐使用Reader + +type Reader interface { + io.ByteReader + io.Reader +} + +type UnsupportedError + +func (e UnsupportedError) Error() string //报告输入使用一个有效但是未实现的jpeg功能 +~~~ + +# image/png包用法: + +​ image/png实现了png图像的编码和解码。 + +​ png和jpeg实现方法基本相同,都是对图像进行了编码和解码操作。 + +~~~go +func Decode(r io.Reader) (image.Image, error) //Decode从r中读取一个图片,并返回一个image.image,返回image类型取决于png图片的内容 + +func DecodeConfig(r io.Reader) (image.Config, error) //无需解码整个图像变能够获取整个图片的尺寸和颜色 + +func Encode(w io.Writer, m image.Image) error //Encode将图片m以PNG的格式写到w中。任何图片都可以被编码,但是哪些不是image.NRGBA的图片编码可能是有损的。 + +type FormatError + +func (e FormatError) Error() string //FormatError会提示一个输入不是有效PNG的错误。 + +~~~ + + 由此可见,png和jpeg使用方法类似,只是两种不同的编码和解码方式。 + +# 应用如下: + +## 1.生成图片: + +~~~go +package main + +import "image"import "image/color"import "image/png"import "os" + +func main() { + + //生成一个100X50的图像。 + + img := image.NewRGBA(image.Rect(0, 0, 100, 50)) + + // 在 (2, 3)处画一个红点。 + + img.Set(2, 3, color.RGBA{255, 0, 0, 255}) + + // 保存到out.png + + f, _ := os.OpenFile("out.png", os.O_WRONLY|os.O_CREATE, 0600) + + defer f.Close() + + png.Encode(f, img) + +} +~~~ + +运行结果,就是生成了一张png文件。 + +## 2.生成复杂色彩的图片: + +~~~go +package main + +import ( + + "fmt" + + "image" + + "image/color" + + "image/png" + + "math" + + "os" + +) + +type Circle struct { + + X, Y, R float64 + +} + +func (c *Circle) Brightness(x, y float64) uint8 { + + var dx, dy float64 = c.X - x, c.Y - y + + d := math.Sqrt(dx*dx+dy*dy) / c.R + + if d > 1 { + + return 0 + + } else { + + return 255 + + } + +} + +func main() { + + var w, h int = 280, 240 + + var hw, hh float64 = float64(w / 2), float64(h / 2) + + r := 40.0 + + θ := 2 * math.Pi / 3 + + cr := &Circle{hw - r*math.Sin(0), hh - r*math.Cos(0), 60} + + cg := &Circle{hw - r*math.Sin(θ), hh - r*math.Cos(θ), 60} + + cb := &Circle{hw - r*math.Sin(-θ), hh - r*math.Cos(-θ), 60} + + m := image.NewRGBA(image.Rect(0, 0, w, h)) + + for x := 0; x < w; x++ { + + for y := 0; y < h; y++ { + + c := color.RGBA{ + + cr.Brightness(float64(x), float64(y)), + + cg.Brightness(float64(x), float64(y)), + + cb.Brightness(float64(x), float64(y)), + + 255, + + } + + m.Set(x, y, c) + + } + + } + + f, err := os.OpenFile("rgb.png", os.O_WRONLY|os.O_CREATE, 0600) + + if err != nil { + + fmt.Println(err) + + return + + } + + defer f.Close() + + png.Encode(f, m) + +} +~~~ + +运行结果: + 8 + +## 3.获取一张图片的尺寸并生成略缩图 + +~~~go +import ( + "fmt" + "github.com/nfnt/resize" + "image" + _ "image/gif" + _ "image/jpeg" + "image/png" + "math" + "os" + "path/filepath" + "time" +) + +const DEFAULT_MAX_WIDTH float64 = 320 +const DEFAULT_MAX_HEIGHT float64 = 240 + +// 计算图片缩放后的尺寸 +func calculateRatioFit(srcWidth, srcHeight int) (int, int) { + ratio := math.Min(DEFAULT_MAX_WIDTH/float64(srcWidth), DEFAULT_MAX_HEIGHT/float64(srcHeight)) + return int(math.Ceil(float64(srcWidth) * ratio)), int(math.Ceil(float64(srcHeight) * ratio)) +} + +// 生成缩略图 +func makeThumbnail(imagePath, savePath string) (string, error) { + + file, _ := os.Open(imagePath) + defer file.Close() + + img, _, err := image.Decode(file) + if err != nil { + return err + } + + b := img.Bounds() + width := b.Max.X + height := b.Max.Y + + w, h := calculateRatioFit(width, height) + + fmt.Println("width = ", width, " height = ", height) + fmt.Println("w = ", w, " h = ", h) + + // 调用resize库进行图片缩放 + m := resize.Resize(uint(w), uint(h), img, resize.Lanczos3) + + // 需要保存的文件 + imgfile, _ := os.Create(savePath) + defer imgfile.Close() + + // 以PNG格式保存文件 + err = png.Encode(imgfile, m) + if err != nil { + return err + } + + return nil +} +~~~ + +## 4.两张图片上下排列合成为一张新图片 + +~~~go +package main + +import ( + "github.com/nfnt/resize" + "image" + "image/draw" + "image/jpeg" + "log" + "math" + "os" +) + +const MaxWidth float64 = 600 + +func fixSize(img1W, img2W int) (new1W, new2W int) { + var ( //为了方便计算,将两个图片的宽转为 float64 + img1Width, img2Width = float64(img1W), float64(img2W) + ratio1, ratio2 float64 + ) + + minWidth := math.Min(img1Width, img2Width) // 取出两张图片中宽度最小的为基准 + + if minWidth > 600 { // 如果最小宽度大于600,那么两张图片都需要进行缩放 + ratio1 = MaxWidth / img1Width // 图片1的缩放比例 + ratio2 = MaxWidth / img2Width // 图片2的缩放比例 + + // 原宽度 * 比例 = 新宽度 + return int(img1Width * ratio1), int(img2Width * ratio2) + } + + // 如果最小宽度小于600,那么需要将较大的图片缩放,使得两张图片的宽度一致 + if minWidth == img1Width { + ratio2 = minWidth / img2Width // 图片2的缩放比例 + return img1W, int(img2Width * ratio2) + } + + ratio1 = minWidth / img1Width // 图片1的缩放比例 + return int(img1Width * ratio1), img2W +} + +func main() { + file1, _ := os.Open("001.jpg") //打开图片1 + file2, _ := os.Open("002.jpg") //打开图片2 + defer file1.Close() + defer file2.Close() + + // image.Decode 图片 + var ( + img1, img2 image.Image + err error + ) + if img1, _, err = image.Decode(file1); err != nil { + log.Fatal(err) + return + } + if img2, _, err = image.Decode(file2); err != nil { + log.Fatal(err) + return + } + b1 := img1.Bounds() + b2 := img2.Bounds() + new1W, new2W := fixSize(b1.Max.X, b2.Max.X) + + // 调用resize库进行图片缩放(高度填0,resize.Resize函数中会自动计算缩放图片的宽高比) + m1 := resize.Resize(uint(new1W), 0, img1, resize.Lanczos3) + m2 := resize.Resize(uint(new2W), 0, img2, resize.Lanczos3) + + // 将两个图片合成一张 + newWidth := m1.Bounds().Max.X //新宽度 = 随意一张图片的宽度 + newHeight := m1.Bounds().Max.Y + m2.Bounds().Max.Y // 新图片的高度为两张图片高度的和 + newImg := image.NewNRGBA(image.Rect(0, 0, newWidth, newHeight)) //创建一个新RGBA图像 + draw.Draw(newImg, newImg.Bounds(), m1, m1.Bounds().Min, draw.Over) //画上第一张缩放后的图片 + draw.Draw(newImg, newImg.Bounds(), m2, m2.Bounds().Min.Sub(image.Pt(0, m1.Bounds().Max.Y)), draw.Over) //画上第二张缩放后的图片(这里需要注意Y值的起始位置) + + // 保存文件 + imgfile, _ := os.Create("003.jpg") + defer imgfile.Close() + jpeg.Encode(imgfile, newImg, &jpeg.Options{100}) +} +~~~ + + 效果如下所示: + +图片合成 \ No newline at end of file diff --git "a/blog/image/images/Draw\346\226\271\346\263\225\351\200\273\350\276\221\346\225\210\346\236\234\345\233\276.png" "b/blog/image/images/Draw\346\226\271\346\263\225\351\200\273\350\276\221\346\225\210\346\236\234\345\233\276.png" new file mode 100644 index 0000000..33e068c Binary files /dev/null and "b/blog/image/images/Draw\346\226\271\346\263\225\351\200\273\350\276\221\346\225\210\346\236\234\345\233\276.png" differ diff --git "a/blog/image/images/RGBA\346\240\274\345\274\217\350\275\254\346\215\242.png" "b/blog/image/images/RGBA\346\240\274\345\274\217\350\275\254\346\215\242.png" new file mode 100644 index 0000000..f9f064c Binary files /dev/null and "b/blog/image/images/RGBA\346\240\274\345\274\217\350\275\254\346\215\242.png" differ diff --git "a/blog/image/images/\345\233\276\347\211\207\345\220\210\346\210\220.png" "b/blog/image/images/\345\233\276\347\211\207\345\220\210\346\210\220.png" new file mode 100644 index 0000000..2a8d372 Binary files /dev/null and "b/blog/image/images/\345\233\276\347\211\207\345\220\210\346\210\220.png" differ diff --git "a/blog/image/images/\345\233\276\347\211\207\346\273\232\345\212\250\346\225\210\346\236\234.png" "b/blog/image/images/\345\233\276\347\211\207\346\273\232\345\212\250\346\225\210\346\236\234.png" new file mode 100644 index 0000000..c59f3c0 Binary files /dev/null and "b/blog/image/images/\345\233\276\347\211\207\346\273\232\345\212\250\346\225\210\346\236\234.png" differ diff --git "a/blog/image/images/\346\213\267\350\264\235\346\225\210\346\236\234.png" "b/blog/image/images/\346\213\267\350\264\235\346\225\210\346\236\234.png" new file mode 100644 index 0000000..fe2899a Binary files /dev/null and "b/blog/image/images/\346\213\267\350\264\235\346\225\210\346\236\234.png" differ diff --git "a/blog/image/images/\346\270\220\345\217\230.png" "b/blog/image/images/\346\270\220\345\217\230.png" new file mode 100644 index 0000000..272e4a4 Binary files /dev/null and "b/blog/image/images/\346\270\220\345\217\230.png" differ diff --git "a/blog/image/images/\347\224\237\346\210\220\345\275\251\350\211\262\345\233\276\347\211\207.png" "b/blog/image/images/\347\224\237\346\210\220\345\275\251\350\211\262\345\233\276\347\211\207.png" new file mode 100644 index 0000000..8d79bbf Binary files /dev/null and "b/blog/image/images/\347\224\237\346\210\220\345\275\251\350\211\262\345\233\276\347\211\207.png" differ diff --git "a/blog/image/images/\347\224\273\350\223\235\350\211\262\345\255\227\344\275\223\346\225\210\346\236\234\345\233\276.png" "b/blog/image/images/\347\224\273\350\223\235\350\211\262\345\255\227\344\275\223\346\225\210\346\236\234\345\233\276.png" new file mode 100644 index 0000000..ccc2e2f Binary files /dev/null and "b/blog/image/images/\347\224\273\350\223\235\350\211\262\345\255\227\344\275\223\346\225\210\346\236\234\345\233\276.png" differ diff --git "a/blog/image/images/\350\222\231\347\211\210\347\224\273\347\211\271\346\225\210.png" "b/blog/image/images/\350\222\231\347\211\210\347\224\273\347\211\271\346\225\210.png" new file mode 100644 index 0000000..259cb3a Binary files /dev/null and "b/blog/image/images/\350\222\231\347\211\210\347\224\273\347\211\271\346\225\210.png" differ