├── .gitignore
├── README.md
├── README_EN.md
├── README_ZH.md
├── errorF2F.go
├── example_test.go
├── go.mod
├── graphics
├── Makefile
├── affine.go
├── blur.go
├── blur_test.go
├── convolve
│ ├── Makefile
│ ├── convolve.go
│ └── convolve_test.go
├── detect
│ ├── Makefile
│ ├── detect.go
│ ├── detect_test.go
│ ├── doc.go
│ ├── integral.go
│ ├── integral_test.go
│ ├── opencv_parser.go
│ ├── opencv_parser_test.go
│ ├── projector.go
│ └── projector_test.go
├── graphicstest
│ ├── Makefile
│ └── graphicstest.go
├── interp
│ ├── Makefile
│ ├── bilinear.go
│ ├── bilinear_test.go
│ ├── doc.go
│ └── interp.go
├── rotate.go
├── rotate_test.go
├── scale.go
├── scale_test.go
├── shared_test.go
├── thumbnail.go
└── thumbnail_test.go
├── imageF2F.go
├── lib
└── codereview
│ └── codereview.cfg
└── testdata
├── gopher.png
├── gopher500.png
└── gopher500_800.png
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/*
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Simple Golang Picture transformation lib | Thumbnail | Scale
2 |
3 | [中文说明](README_ZH.md)
4 |
5 | ## What
6 |
7 | 1. Very hard to find such lib so I make this.
8 | 2. Scale the size of Picture such the format of jpg or png.
9 |
10 |
11 | ## New
12 |
13 | - 2019/5/12 Support Bytes Stream of Picture to Scale.
14 |
15 | ## How
16 |
17 | ### Install
18 |
19 | ```
20 | go get -v -u github.com/hunterhug/go_image
21 | ```
22 |
23 | ### Main funciton
24 |
25 | - Scale by width,input and output is bytes:
26 |
27 | ```
28 | func ScaleB2B(InRaw []byte, width int) (OutRaw []byte, err error)
29 | ```
30 |
31 | - Scale by width,input and output is the location(filename) of picture:
32 |
33 | ```
34 | func ScaleF2F(filename string, savepath string, width int) (err error)
35 | ```
36 |
37 | - Scale by width and height,input and output is bytes:
38 |
39 | ```
40 | func ThumbnailB2B(InRaw []byte, width int, height int) (OutRaw []byte, err error)
41 | ```
42 |
43 | - Scale by width and height,input and output is the location(filename) of picture:
44 |
45 | ```
46 | func ThumbnailF2F(filename string, savepath string, width int, height int) (err error)
47 | ```
48 |
49 | - Check the image real file type, such 4.jpg return 4.png(input is the location of file):
50 |
51 | ```
52 | func RealImageName(filename string) (filerealname string, err error)
53 | ```
54 |
55 | - Rename the file,if file exist will throw err if force is false, when force is true overwrite:
56 |
57 | ```
58 | func ChangeImageName(oldname string, newname string, force bool) (err error)
59 | ```
60 |
61 | ## Example
62 |
63 | ### example_test.go
64 |
65 | ```
66 | package go_image
67 |
68 | import (
69 | "fmt"
70 | "testing"
71 | )
72 |
73 | //将某一图片文件进行缩放后存入另外的文件中
74 | func TestImage(t *testing.T) {
75 | //打印当前文件夹位置
76 | fmt.Printf("本文件文件夹位置:%s\n", CurDir())
77 |
78 | //图像位置
79 | filename := "./testdata/gopher.png"
80 |
81 | //宽度,高度
82 | width := 500
83 | height := 800
84 |
85 | //保存位置
86 | save1 := "./testdata/gopher500.jpg"
87 | save2 := "./testdata/gopher500_800.png"
88 |
89 | //按照宽度进行等比例缩放
90 | err := ScaleF2F(filename, save1, width)
91 | if err != nil {
92 | fmt.Printf("生成按宽度缩放图失败:%s\n", err.Error())
93 | } else {
94 | fmt.Printf("生成按宽度缩放图:%s\n", save1)
95 | }
96 |
97 | //按照宽度和高度进行等比例缩放
98 | err = ThumbnailF2F(filename, save2, width, height)
99 | if err != nil {
100 | fmt.Printf("生成按宽度高度缩放图:%s\n", err.Error())
101 | } else {
102 | fmt.Printf("生成按宽度高度缩放图:%s\n", save2)
103 | }
104 |
105 | //查看图像文件的真正名字
106 | //如 ./testdata/gopher500.jpg其实是png类型,但是命名错误,需要纠正!
107 | realfilename, err := RealImageName(save1)
108 | if err != nil {
109 | fmt.Printf("真正的文件名: %s->? err:%s\n", save1, err.Error())
110 | } else {
111 | fmt.Printf("真正的文件名:%s->%s\n", save1, realfilename)
112 | }
113 |
114 | //文件改名,强制性
115 | err = ChangeImageName(save1, realfilename, true)
116 | if err != nil {
117 | fmt.Printf("文件改名失败:%s->%s,%s\n", save1, realfilename, err.Error())
118 | } else {
119 | fmt.Println("改名成功")
120 | }
121 |
122 | //文件改名,不强制性
123 | err = ChangeImageName(save1, realfilename, false)
124 | if err != nil {
125 | fmt.Printf("文件改名失败:%s->%s,%s\n", save1, realfilename, err.Error())
126 | }
127 | }
128 |
129 | ```
130 |
131 | ### Result
132 |
133 | ```
134 | === RUN TestImage
135 | 本文件文件夹位置:E:\gocode\src\github.com\hunterhug\go_image
136 | 生成按宽度缩放图:./testdata/gopher500.jpg
137 | 生成按宽度高度缩放图:./testdata/gopher500_800.png
138 | 真正的文件名:./testdata/gopher500.jpg->./testdata/gopher500.png
139 | 改名成功
140 | 文件改名失败:./testdata/gopher500.jpg->./testdata/gopher500.png,File already exist error
141 | --- PASS: TestImage (1.66s)
142 | PASS
143 | ```
144 |
145 | Origin:
146 |
147 | 
148 |
149 |
150 | Width 500px Scale:
151 |
152 |
153 | 
154 |
155 | Width 500px,Height 800px Scale:
156 |
157 | 
158 |
159 | ## Come from
160 |
161 | This is a Graphics library for the Go programming language.
162 |
163 | Unless otherwise noted, the graphics-go source files are distributed
164 | under the BSD-style license found in the LICENSE file.
165 |
166 | Contributions should follow the same procedure as for the Go project:
167 | http://golang.org/doc/contribute.html
168 |
169 |
--------------------------------------------------------------------------------
/README_EN.md:
--------------------------------------------------------------------------------
1 | # Simple Golang Picture transformation lib | Thumbnail | Scale
2 |
3 | [中文说明](README_ZH.md)
4 |
5 | ## What
6 |
7 | 1. Very hard to find such lib so I make this.
8 | 2. Scale the size of Picture such the format of jpg or png.
9 |
10 |
11 | ## New
12 |
13 | - 2019/5/12 Support Bytes Stream of Picture to Scale.
14 |
15 | ## How
16 |
17 | ### Install
18 |
19 | ```
20 | go get -v -u github.com/hunterhug/go_image
21 | ```
22 |
23 | ### Main funciton
24 |
25 | - Scale by width,input and output is bytes:
26 |
27 | ```
28 | func ScaleB2B(InRaw []byte, width int) (OutRaw []byte, err error)
29 | ```
30 |
31 | - Scale by width,input and output is the location(filename) of picture:
32 |
33 | ```
34 | func ScaleF2F(filename string, savepath string, width int) (err error)
35 | ```
36 |
37 | - Scale by width and height,input and output is bytes:
38 |
39 | ```
40 | func ThumbnailB2B(InRaw []byte, width int, height int) (OutRaw []byte, err error)
41 | ```
42 |
43 | - Scale by width and height,input and output is the location(filename) of picture:
44 |
45 | ```
46 | func ThumbnailF2F(filename string, savepath string, width int, height int) (err error)
47 | ```
48 |
49 | - Check the image real file type, such 4.jpg return 4.png(input is the location of file):
50 |
51 | ```
52 | func RealImageName(filename string) (filerealname string, err error)
53 | ```
54 |
55 | - Rename the file,if file exist will throw err if force is false, when force is true overwrite:
56 |
57 | ```
58 | func ChangeImageName(oldname string, newname string, force bool) (err error)
59 | ```
60 |
61 | ## Example
62 |
63 | ### example_test.go
64 |
65 | ```
66 | package go_image
67 |
68 | import (
69 | "fmt"
70 | "testing"
71 | )
72 |
73 | //将某一图片文件进行缩放后存入另外的文件中
74 | func TestImage(t *testing.T) {
75 | //打印当前文件夹位置
76 | fmt.Printf("本文件文件夹位置:%s\n", CurDir())
77 |
78 | //图像位置
79 | filename := "./testdata/gopher.png"
80 |
81 | //宽度,高度
82 | width := 500
83 | height := 800
84 |
85 | //保存位置
86 | save1 := "./testdata/gopher500.jpg"
87 | save2 := "./testdata/gopher500_800.png"
88 |
89 | //按照宽度进行等比例缩放
90 | err := ScaleF2F(filename, save1, width)
91 | if err != nil {
92 | fmt.Printf("生成按宽度缩放图失败:%s\n", err.Error())
93 | } else {
94 | fmt.Printf("生成按宽度缩放图:%s\n", save1)
95 | }
96 |
97 | //按照宽度和高度进行等比例缩放
98 | err = ThumbnailF2F(filename, save2, width, height)
99 | if err != nil {
100 | fmt.Printf("生成按宽度高度缩放图:%s\n", err.Error())
101 | } else {
102 | fmt.Printf("生成按宽度高度缩放图:%s\n", save2)
103 | }
104 |
105 | //查看图像文件的真正名字
106 | //如 ./testdata/gopher500.jpg其实是png类型,但是命名错误,需要纠正!
107 | realfilename, err := RealImageName(save1)
108 | if err != nil {
109 | fmt.Printf("真正的文件名: %s->? err:%s\n", save1, err.Error())
110 | } else {
111 | fmt.Printf("真正的文件名:%s->%s\n", save1, realfilename)
112 | }
113 |
114 | //文件改名,强制性
115 | err = ChangeImageName(save1, realfilename, true)
116 | if err != nil {
117 | fmt.Printf("文件改名失败:%s->%s,%s\n", save1, realfilename, err.Error())
118 | } else {
119 | fmt.Println("改名成功")
120 | }
121 |
122 | //文件改名,不强制性
123 | err = ChangeImageName(save1, realfilename, false)
124 | if err != nil {
125 | fmt.Printf("文件改名失败:%s->%s,%s\n", save1, realfilename, err.Error())
126 | }
127 | }
128 |
129 | ```
130 |
131 | ### Result
132 |
133 | ```
134 | === RUN TestImage
135 | 本文件文件夹位置:E:\gocode\src\github.com\hunterhug\go_image
136 | 生成按宽度缩放图:./testdata/gopher500.jpg
137 | 生成按宽度高度缩放图:./testdata/gopher500_800.png
138 | 真正的文件名:./testdata/gopher500.jpg->./testdata/gopher500.png
139 | 改名成功
140 | 文件改名失败:./testdata/gopher500.jpg->./testdata/gopher500.png,File already exist error
141 | --- PASS: TestImage (1.66s)
142 | PASS
143 | ```
144 |
145 | Origin:
146 |
147 | 
148 |
149 |
150 | Width 500px Scale:
151 |
152 |
153 | 
154 |
155 | Width 500px,Height 800px Scale:
156 |
157 | 
158 |
159 | ## Come from
160 |
161 | This is a Graphics library for the Go programming language.
162 |
163 | Unless otherwise noted, the graphics-go source files are distributed
164 | under the BSD-style license found in the LICENSE file.
165 |
166 | Contributions should follow the same procedure as for the Go project:
167 | http://golang.org/doc/contribute.html
168 |
169 |
--------------------------------------------------------------------------------
/README_ZH.md:
--------------------------------------------------------------------------------
1 | # 简单二次封装的golang图像处理库:图片裁剪
2 |
3 | [English Version Here](README_EN.md)
4 |
5 | ## 功能
6 |
7 | 1. Go语言下的官方图像处理库,貌似已经找不到了,所以收藏起来
8 | 2. 简单封装后对jpg和png图像进行缩放|裁剪的库
9 |
10 | ## 最新支持
11 |
12 | - 2019/5/12 支持传入图片字节数组来进行裁剪。
13 |
14 | ## 使用说明
15 |
16 | ### 首先下载
17 |
18 | ```
19 | go get -v -u github.com/hunterhug/go_image
20 | ```
21 |
22 | ### 主要函数
23 |
24 | - 按宽度进行比例缩放,输入和输出都是图片字节数组:
25 |
26 | ```
27 | func ScaleB2B(InRaw []byte, width int) (OutRaw []byte, err error)
28 | ```
29 |
30 | - 按宽度进行比例缩放,输入输出都是文件:
31 |
32 | ```
33 | func ScaleF2F(filename string, savepath string, width int) (err error)
34 | ```
35 |
36 | - 按宽度和高度进行比例缩放,输入和输出都是图片字节数组:
37 |
38 | ```
39 | func ThumbnailB2B(InRaw []byte, width int, height int) (OutRaw []byte, err error)
40 | ```
41 |
42 | - 按宽度和高度进行比例缩放,输入和输出都是文件:
43 |
44 | ```
45 | func ThumbnailF2F(filename string, savepath string, width int, height int) (err error)
46 | ```
47 |
48 | - 检测图像文件真正文件类型,并返回真实文件名,参数为图像文件位置
49 |
50 | ```
51 | func RealImageName(filename string) (filerealname string, err error)
52 | ```
53 |
54 | - 文件改名,如果force为假,且新的文件名已经存在,那么抛出错误
55 |
56 | ```
57 | func ChangeImageName(oldname string, newname string, force bool) (err error)
58 | ```
59 |
60 | ## 使用示例
61 |
62 | ### example_test.go
63 |
64 | ```
65 | package go_image
66 |
67 | import (
68 | "fmt"
69 | "testing"
70 | )
71 |
72 | //将某一图片文件进行缩放后存入另外的文件中
73 | func TestImage(t *testing.T) {
74 | //打印当前文件夹位置
75 | fmt.Printf("本文件文件夹位置:%s\n", CurDir())
76 |
77 | //图像位置
78 | filename := "./testdata/gopher.png"
79 |
80 | //宽度,高度
81 | width := 500
82 | height := 800
83 |
84 | //保存位置
85 | save1 := "./testdata/gopher500.jpg"
86 | save2 := "./testdata/gopher500_800.png"
87 |
88 | //按照宽度进行等比例缩放
89 | err := ScaleF2F(filename, save1, width)
90 | if err != nil {
91 | fmt.Printf("生成按宽度缩放图失败:%s\n", err.Error())
92 | } else {
93 | fmt.Printf("生成按宽度缩放图:%s\n", save1)
94 | }
95 |
96 | //按照宽度和高度进行等比例缩放
97 | err = ThumbnailF2F(filename, save2, width, height)
98 | if err != nil {
99 | fmt.Printf("生成按宽度高度缩放图:%s\n", err.Error())
100 | } else {
101 | fmt.Printf("生成按宽度高度缩放图:%s\n", save2)
102 | }
103 |
104 | //查看图像文件的真正名字
105 | //如 ./testdata/gopher500.jpg其实是png类型,但是命名错误,需要纠正!
106 | realfilename, err := RealImageName(save1)
107 | if err != nil {
108 | fmt.Printf("真正的文件名: %s->? err:%s\n", save1, err.Error())
109 | } else {
110 | fmt.Printf("真正的文件名:%s->%s\n", save1, realfilename)
111 | }
112 |
113 | //文件改名,强制性
114 | err = ChangeImageName(save1, realfilename, true)
115 | if err != nil {
116 | fmt.Printf("文件改名失败:%s->%s,%s\n", save1, realfilename, err.Error())
117 | } else {
118 | fmt.Println("改名成功")
119 | }
120 |
121 | //文件改名,不强制性
122 | err = ChangeImageName(save1, realfilename, false)
123 | if err != nil {
124 | fmt.Printf("文件改名失败:%s->%s,%s\n", save1, realfilename, err.Error())
125 | }
126 | }
127 |
128 | ```
129 |
130 | ### 结果
131 |
132 | ```
133 | === RUN TestImage
134 | 本文件文件夹位置:E:\gocode\src\github.com\hunterhug\go_image
135 | 生成按宽度缩放图:./testdata/gopher500.jpg
136 | 生成按宽度高度缩放图:./testdata/gopher500_800.png
137 | 真正的文件名:./testdata/gopher500.jpg->./testdata/gopher500.png
138 | 改名成功
139 | 文件改名失败:./testdata/gopher500.jpg->./testdata/gopher500.png,File already exist error
140 | --- PASS: TestImage (1.66s)
141 | PASS
142 | ```
143 |
144 | 原始图片:
145 |
146 | 
147 |
148 |
149 | 宽度500px等比例缩放裁剪:
150 |
151 |
152 | 
153 |
154 | 宽度500px,高度800px等比例缩放裁剪:
155 |
156 | 
157 |
158 | ## 来自
159 |
160 | This is a Graphics library for the Go programming language.
161 |
162 | Unless otherwise noted, the graphics-go source files are distributed
163 | under the BSD-style license found in the LICENSE file.
164 |
165 | Contributions should follow the same procedure as for the Go project:
166 | http://golang.org/doc/contribute.html
167 |
168 |
--------------------------------------------------------------------------------
/errorF2F.go:
--------------------------------------------------------------------------------
1 | package go_image
2 |
3 | import "errors"
4 |
5 | var (
6 | ExtNotSupportError = errors.New("ext of filename not support")
7 | FileNameError = errors.New("filename error")
8 | FileExistError = errors.New("File already exist error")
9 | )
10 |
--------------------------------------------------------------------------------
/example_test.go:
--------------------------------------------------------------------------------
1 | package go_image
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | )
7 |
8 | //将某一图片文件进行缩放后存入另外的文件中
9 | func TestImage(t *testing.T) {
10 | //打印当前文件夹位置
11 | fmt.Printf("本文件文件夹位置:%s\n", CurDir())
12 |
13 | //图像位置
14 | filename := "./testdata/gopher.png"
15 |
16 | //宽度,高度
17 | width := 500
18 | height := 800
19 |
20 | //保存位置
21 | save1 := "./testdata/gopher500.jpg"
22 | save2 := "./testdata/gopher500_800.png"
23 |
24 | //按照宽度进行等比例缩放
25 | err := ScaleF2F(filename, save1, width)
26 | if err != nil {
27 | fmt.Printf("生成按宽度缩放图失败:%s\n", err.Error())
28 | } else {
29 | fmt.Printf("生成按宽度缩放图:%s\n", save1)
30 | }
31 |
32 | //按照宽度和高度进行等比例缩放
33 | err = ThumbnailF2F(filename, save2, width, height)
34 | if err != nil {
35 | fmt.Printf("生成按宽度高度缩放图:%s\n", err.Error())
36 | } else {
37 | fmt.Printf("生成按宽度高度缩放图:%s\n", save2)
38 | }
39 |
40 | //查看图像文件的真正名字
41 | //如 ./testdata/gopher500.jpg其实是png类型,但是命名错误,需要纠正!
42 | realfilename, err := RealImageName(save1)
43 | if err != nil {
44 | fmt.Printf("真正的文件名: %s->? err:%s\n", save1, err.Error())
45 | } else {
46 | fmt.Printf("真正的文件名:%s->%s\n", save1, realfilename)
47 | }
48 |
49 | //文件改名,强制性
50 | err = ChangeImageName(save1, realfilename, true)
51 | if err != nil {
52 | fmt.Printf("文件改名失败:%s->%s,%s\n", save1, realfilename, err.Error())
53 | } else {
54 | fmt.Println("改名成功")
55 | }
56 |
57 | //文件改名,不强制性
58 | err = ChangeImageName(save1, realfilename, false)
59 | if err != nil {
60 | fmt.Printf("文件改名失败:%s->%s,%s\n", save1, realfilename, err.Error())
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/hunterhug/go_image
2 |
3 | go 1.15
4 |
--------------------------------------------------------------------------------
/graphics/Makefile:
--------------------------------------------------------------------------------
1 | # Copyright 2011 The Graphics-Go Authors. All rights reserved.
2 | # Use of this source code is governed by a BSD-style
3 | # license that can be found in the LICENSE file.
4 |
5 | include $(GOROOT)/src/Make.inc
6 |
7 | TARG=code.google.com/p/graphics-go/graphics
8 | GOFILES=\
9 | affine.go\
10 | blur.go\
11 | rotate.go\
12 | scale.go\
13 | thumbnail.go\
14 |
15 | include $(GOROOT)/src/Make.pkg
16 |
--------------------------------------------------------------------------------
/graphics/affine.go:
--------------------------------------------------------------------------------
1 | // Copyright 2011 The Graphics-Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package graphics
6 |
7 | import (
8 | "github.com/hunterhug/go_image/graphics/interp"
9 | "errors"
10 | "image"
11 | "image/draw"
12 | "math"
13 | )
14 |
15 | // I is the identity Affine transform matrix.
16 | var I = Affine{
17 | 1, 0, 0,
18 | 0, 1, 0,
19 | 0, 0, 1,
20 | }
21 |
22 | // Affine is a 3x3 2D affine transform matrix.
23 | // M(i,j) is Affine[i*3+j].
24 | type Affine [9]float64
25 |
26 | // Mul returns the multiplication of two affine transform matrices.
27 | func (a Affine) Mul(b Affine) Affine {
28 | return Affine{
29 | a[0]*b[0] + a[1]*b[3] + a[2]*b[6],
30 | a[0]*b[1] + a[1]*b[4] + a[2]*b[7],
31 | a[0]*b[2] + a[1]*b[5] + a[2]*b[8],
32 | a[3]*b[0] + a[4]*b[3] + a[5]*b[6],
33 | a[3]*b[1] + a[4]*b[4] + a[5]*b[7],
34 | a[3]*b[2] + a[4]*b[5] + a[5]*b[8],
35 | a[6]*b[0] + a[7]*b[3] + a[8]*b[6],
36 | a[6]*b[1] + a[7]*b[4] + a[8]*b[7],
37 | a[6]*b[2] + a[7]*b[5] + a[8]*b[8],
38 | }
39 | }
40 |
41 | func (a Affine) transformRGBA(dst *image.RGBA, src *image.RGBA, i interp.RGBA) error {
42 | srcb := src.Bounds()
43 | b := dst.Bounds()
44 | for y := b.Min.Y; y < b.Max.Y; y++ {
45 | for x := b.Min.X; x < b.Max.X; x++ {
46 | sx, sy := a.pt(x, y)
47 | if inBounds(srcb, sx, sy) {
48 | c := i.RGBA(src, sx, sy)
49 | off := (y-dst.Rect.Min.Y)*dst.Stride + (x-dst.Rect.Min.X)*4
50 | dst.Pix[off+0] = c.R
51 | dst.Pix[off+1] = c.G
52 | dst.Pix[off+2] = c.B
53 | dst.Pix[off+3] = c.A
54 | }
55 | }
56 | }
57 | return nil
58 | }
59 |
60 | // Transform applies the affine transform to src and produces dst.
61 | func (a Affine) Transform(dst draw.Image, src image.Image, i interp.Interp) error {
62 | if dst == nil {
63 | return errors.New("graphics: dst is nil")
64 | }
65 | if src == nil {
66 | return errors.New("graphics: src is nil")
67 | }
68 |
69 | // RGBA fast path.
70 | dstRGBA, dstOk := dst.(*image.RGBA)
71 | srcRGBA, srcOk := src.(*image.RGBA)
72 | interpRGBA, interpOk := i.(interp.RGBA)
73 | if dstOk && srcOk && interpOk {
74 | return a.transformRGBA(dstRGBA, srcRGBA, interpRGBA)
75 | }
76 |
77 | srcb := src.Bounds()
78 | b := dst.Bounds()
79 | for y := b.Min.Y; y < b.Max.Y; y++ {
80 | for x := b.Min.X; x < b.Max.X; x++ {
81 | sx, sy := a.pt(x, y)
82 | if inBounds(srcb, sx, sy) {
83 | dst.Set(x, y, i.Interp(src, sx, sy))
84 | }
85 | }
86 | }
87 | return nil
88 | }
89 |
90 | func inBounds(b image.Rectangle, x, y float64) bool {
91 | if x < float64(b.Min.X) || x >= float64(b.Max.X) {
92 | return false
93 | }
94 | if y < float64(b.Min.Y) || y >= float64(b.Max.Y) {
95 | return false
96 | }
97 | return true
98 | }
99 |
100 | func (a Affine) pt(x0, y0 int) (x1, y1 float64) {
101 | fx := float64(x0) + 0.5
102 | fy := float64(y0) + 0.5
103 | x1 = fx*a[0] + fy*a[1] + a[2]
104 | y1 = fx*a[3] + fy*a[4] + a[5]
105 | return x1, y1
106 | }
107 |
108 | // TransformCenter applies the affine transform to src and produces dst.
109 | // Equivalent to
110 | // a.CenterFit(dst, src).Transform(dst, src, i).
111 | func (a Affine) TransformCenter(dst draw.Image, src image.Image, i interp.Interp) error {
112 | if dst == nil {
113 | return errors.New("graphics: dst is nil")
114 | }
115 | if src == nil {
116 | return errors.New("graphics: src is nil")
117 | }
118 |
119 | return a.CenterFit(dst.Bounds(), src.Bounds()).Transform(dst, src, i)
120 | }
121 |
122 | // Scale produces a scaling transform of factors x and y.
123 | func (a Affine) Scale(x, y float64) Affine {
124 | return a.Mul(Affine{
125 | 1 / x, 0, 0,
126 | 0, 1 / y, 0,
127 | 0, 0, 1,
128 | })
129 | }
130 |
131 | // Rotate produces a clockwise rotation transform of angle, in radians.
132 | func (a Affine) Rotate(angle float64) Affine {
133 | s, c := math.Sincos(angle)
134 | return a.Mul(Affine{
135 | +c, +s, +0,
136 | -s, +c, +0,
137 | +0, +0, +1,
138 | })
139 | }
140 |
141 | // Shear produces a shear transform by the slopes x and y.
142 | func (a Affine) Shear(x, y float64) Affine {
143 | d := 1 - x*y
144 | return a.Mul(Affine{
145 | +1 / d, -x / d, 0,
146 | -y / d, +1 / d, 0,
147 | 0, 0, 1,
148 | })
149 | }
150 |
151 | // Translate produces a translation transform with pixel distances x and y.
152 | func (a Affine) Translate(x, y float64) Affine {
153 | return a.Mul(Affine{
154 | 1, 0, -x,
155 | 0, 1, -y,
156 | 0, 0, +1,
157 | })
158 | }
159 |
160 | // Center produces the affine transform, centered around the provided point.
161 | func (a Affine) Center(x, y float64) Affine {
162 | return I.Translate(-x, -y).Mul(a).Translate(x, y)
163 | }
164 |
165 | // CenterFit produces the affine transform, centered around the rectangles.
166 | // It is equivalent to
167 | // I.Translate(-
).Mul(a).Translate()
168 | func (a Affine) CenterFit(dst, src image.Rectangle) Affine {
169 | dx := float64(dst.Min.X) + float64(dst.Dx())/2
170 | dy := float64(dst.Min.Y) + float64(dst.Dy())/2
171 | sx := float64(src.Min.X) + float64(src.Dx())/2
172 | sy := float64(src.Min.Y) + float64(src.Dy())/2
173 | return I.Translate(-sx, -sy).Mul(a).Translate(dx, dy)
174 | }
175 |
--------------------------------------------------------------------------------
/graphics/blur.go:
--------------------------------------------------------------------------------
1 | // Copyright 2011 The Graphics-Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package graphics
6 |
7 | import (
8 | "github.com/hunterhug/go_image/graphics/convolve"
9 | "errors"
10 | "image"
11 | "image/draw"
12 | "math"
13 | )
14 |
15 | // DefaultStdDev is the default blurring parameter.
16 | var DefaultStdDev = 0.5
17 |
18 | // BlurOptions are the blurring parameters.
19 | // StdDev is the standard deviation of the normal, higher is blurrier.
20 | // Size is the size of the kernel. If zero, it is set to Ceil(6 * StdDev).
21 | type BlurOptions struct {
22 | StdDev float64
23 | Size int
24 | }
25 |
26 | // Blur produces a blurred version of the image, using a Gaussian blur.
27 | func Blur(dst draw.Image, src image.Image, opt *BlurOptions) error {
28 | if dst == nil {
29 | return errors.New("graphics: dst is nil")
30 | }
31 | if src == nil {
32 | return errors.New("graphics: src is nil")
33 | }
34 |
35 | sd := DefaultStdDev
36 | size := 0
37 |
38 | if opt != nil {
39 | sd = opt.StdDev
40 | size = opt.Size
41 | }
42 |
43 | if size < 1 {
44 | size = int(math.Ceil(sd * 6))
45 | }
46 |
47 | kernel := make([]float64, 2*size+1)
48 | for i := 0; i <= size; i++ {
49 | x := float64(i) / sd
50 | x = math.Pow(1/math.SqrtE, x*x)
51 | kernel[size-i] = x
52 | kernel[size+i] = x
53 | }
54 |
55 | // Normalize the weights to sum to 1.0.
56 | kSum := 0.0
57 | for _, k := range kernel {
58 | kSum += k
59 | }
60 | for i, k := range kernel {
61 | kernel[i] = k / kSum
62 | }
63 |
64 | return convolve.Convolve(dst, src, &convolve.SeparableKernel{
65 | X: kernel,
66 | Y: kernel,
67 | })
68 | }
69 |
--------------------------------------------------------------------------------
/graphics/blur_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2011 The Graphics-Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package graphics
6 |
7 | import (
8 | "github.com/hunterhug/go_image/graphics/graphicstest"
9 | "image"
10 | "image/color"
11 | "testing"
12 |
13 | _ "image/png"
14 | )
15 |
16 | var blurOneColorTests = []transformOneColorTest{
17 | {
18 | "1x1-blank", 1, 1, 1, 1,
19 | &BlurOptions{0.83, 1},
20 | []uint8{0xff},
21 | []uint8{0xff},
22 | },
23 | {
24 | "1x1-spreadblank", 1, 1, 1, 1,
25 | &BlurOptions{0.83, 2},
26 | []uint8{0xff},
27 | []uint8{0xff},
28 | },
29 | {
30 | "3x3-blank", 3, 3, 3, 3,
31 | &BlurOptions{0.83, 2},
32 | []uint8{
33 | 0xff, 0xff, 0xff,
34 | 0xff, 0xff, 0xff,
35 | 0xff, 0xff, 0xff,
36 | },
37 | []uint8{
38 | 0xff, 0xff, 0xff,
39 | 0xff, 0xff, 0xff,
40 | 0xff, 0xff, 0xff,
41 | },
42 | },
43 | {
44 | "3x3-dot", 3, 3, 3, 3,
45 | &BlurOptions{0.34, 1},
46 | []uint8{
47 | 0x00, 0x00, 0x00,
48 | 0x00, 0xff, 0x00,
49 | 0x00, 0x00, 0x00,
50 | },
51 | []uint8{
52 | 0x00, 0x03, 0x00,
53 | 0x03, 0xf2, 0x03,
54 | 0x00, 0x03, 0x00,
55 | },
56 | },
57 | {
58 | "5x5-dot", 5, 5, 5, 5,
59 | &BlurOptions{0.34, 1},
60 | []uint8{
61 | 0x00, 0x00, 0x00, 0x00, 0x00,
62 | 0x00, 0x00, 0x00, 0x00, 0x00,
63 | 0x00, 0x00, 0xff, 0x00, 0x00,
64 | 0x00, 0x00, 0x00, 0x00, 0x00,
65 | 0x00, 0x00, 0x00, 0x00, 0x00,
66 | },
67 | []uint8{
68 | 0x00, 0x00, 0x00, 0x00, 0x00,
69 | 0x00, 0x00, 0x03, 0x00, 0x00,
70 | 0x00, 0x03, 0xf2, 0x03, 0x00,
71 | 0x00, 0x00, 0x03, 0x00, 0x00,
72 | 0x00, 0x00, 0x00, 0x00, 0x00,
73 | },
74 | },
75 | {
76 | "5x5-dot-spread", 5, 5, 5, 5,
77 | &BlurOptions{0.85, 1},
78 | []uint8{
79 | 0x00, 0x00, 0x00, 0x00, 0x00,
80 | 0x00, 0x00, 0x00, 0x00, 0x00,
81 | 0x00, 0x00, 0xff, 0x00, 0x00,
82 | 0x00, 0x00, 0x00, 0x00, 0x00,
83 | 0x00, 0x00, 0x00, 0x00, 0x00,
84 | },
85 | []uint8{
86 | 0x00, 0x00, 0x00, 0x00, 0x00,
87 | 0x00, 0x10, 0x20, 0x10, 0x00,
88 | 0x00, 0x20, 0x40, 0x20, 0x00,
89 | 0x00, 0x10, 0x20, 0x10, 0x00,
90 | 0x00, 0x00, 0x00, 0x00, 0x00,
91 | },
92 | },
93 | {
94 | "4x4-box", 4, 4, 4, 4,
95 | &BlurOptions{0.34, 1},
96 | []uint8{
97 | 0x00, 0x00, 0x00, 0x00,
98 | 0x00, 0xff, 0xff, 0x00,
99 | 0x00, 0xff, 0xff, 0x00,
100 | 0x00, 0x00, 0x00, 0x00,
101 | },
102 | []uint8{
103 | 0x00, 0x03, 0x03, 0x00,
104 | 0x03, 0xf8, 0xf8, 0x03,
105 | 0x03, 0xf8, 0xf8, 0x03,
106 | 0x00, 0x03, 0x03, 0x00,
107 | },
108 | },
109 | {
110 | "5x5-twodots", 5, 5, 5, 5,
111 | &BlurOptions{0.34, 1},
112 | []uint8{
113 | 0x00, 0x00, 0x00, 0x00, 0x00,
114 | 0x00, 0x00, 0x00, 0x00, 0x00,
115 | 0x00, 0x96, 0x00, 0x96, 0x00,
116 | 0x00, 0x00, 0x00, 0x00, 0x00,
117 | 0x00, 0x00, 0x00, 0x00, 0x00,
118 | },
119 | []uint8{
120 | 0x00, 0x00, 0x00, 0x00, 0x00,
121 | 0x00, 0x02, 0x00, 0x02, 0x00,
122 | 0x02, 0x8e, 0x04, 0x8e, 0x02,
123 | 0x00, 0x02, 0x00, 0x02, 0x00,
124 | 0x00, 0x00, 0x00, 0x00, 0x00,
125 | },
126 | },
127 | }
128 |
129 | func TestBlurOneColor(t *testing.T) {
130 | for _, oc := range blurOneColorTests {
131 | dst := oc.newDst()
132 | src := oc.newSrc()
133 | opt := oc.opt.(*BlurOptions)
134 | if err := Blur(dst, src, opt); err != nil {
135 | t.Fatal(err)
136 | }
137 |
138 | if !checkTransformTest(t, &oc, dst) {
139 | continue
140 | }
141 | }
142 | }
143 |
144 | func TestBlurEmpty(t *testing.T) {
145 | empty := image.NewRGBA(image.Rect(0, 0, 0, 0))
146 | if err := Blur(empty, empty, nil); err != nil {
147 | t.Fatal(err)
148 | }
149 | }
150 |
151 | func TestBlurGopher(t *testing.T) {
152 | src, err := graphicstest.LoadImage("../testdata/gopher.png")
153 | if err != nil {
154 | t.Fatal(err)
155 | }
156 |
157 | dst := image.NewRGBA(src.Bounds())
158 | if err = Blur(dst, src, &BlurOptions{StdDev: 1.1}); err != nil {
159 | t.Fatal(err)
160 | }
161 |
162 | cmp, err := graphicstest.LoadImage("../testdata/gopher-blur.png")
163 | if err != nil {
164 | t.Fatal(err)
165 | }
166 | err = graphicstest.ImageWithinTolerance(dst, cmp, 0x101)
167 | if err != nil {
168 | t.Fatal(err)
169 | }
170 | }
171 |
172 | func benchBlur(b *testing.B, bounds image.Rectangle) {
173 | b.StopTimer()
174 |
175 | // Construct a fuzzy image.
176 | src := image.NewRGBA(bounds)
177 | for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
178 | for x := bounds.Min.X; x < bounds.Max.X; x++ {
179 | src.SetRGBA(x, y, color.RGBA{
180 | uint8(5 * x % 0x100),
181 | uint8(7 * y % 0x100),
182 | uint8((7*x + 5*y) % 0x100),
183 | 0xff,
184 | })
185 | }
186 | }
187 | dst := image.NewRGBA(bounds)
188 |
189 | b.StartTimer()
190 | for i := 0; i < b.N; i++ {
191 | Blur(dst, src, &BlurOptions{0.84, 3})
192 | }
193 | }
194 |
195 | func BenchmarkBlur400x400x3(b *testing.B) {
196 | benchBlur(b, image.Rect(0, 0, 400, 400))
197 | }
198 |
199 | // Exactly twice the pixel count of 400x400.
200 | func BenchmarkBlur400x800x3(b *testing.B) {
201 | benchBlur(b, image.Rect(0, 0, 400, 800))
202 | }
203 |
204 | // Exactly twice the pixel count of 400x800
205 | func BenchmarkBlur400x1600x3(b *testing.B) {
206 | benchBlur(b, image.Rect(0, 0, 400, 1600))
207 | }
208 |
--------------------------------------------------------------------------------
/graphics/convolve/Makefile:
--------------------------------------------------------------------------------
1 | # Copyright 2011 The Graphics-Go Authors. All rights reserved.
2 | # Use of this source code is governed by a BSD-style
3 | # license that can be found in the LICENSE file.
4 |
5 | include $(GOROOT)/src/Make.inc
6 |
7 | TARG=code.google.com/p/graphics-go/graphics/convolve
8 | GOFILES=\
9 | convolve.go\
10 |
11 | include $(GOROOT)/src/Make.pkg
12 |
--------------------------------------------------------------------------------
/graphics/convolve/convolve.go:
--------------------------------------------------------------------------------
1 | // Copyright 2011 The Graphics-Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package convolve
6 |
7 | import (
8 | "errors"
9 | "fmt"
10 | "image"
11 | "image/draw"
12 | "math"
13 | )
14 |
15 | // clamp clamps x to the range [x0, x1].
16 | func clamp(x, x0, x1 float64) float64 {
17 | if x < x0 {
18 | return x0
19 | }
20 | if x > x1 {
21 | return x1
22 | }
23 | return x
24 | }
25 |
26 | // Kernel is a square matrix that defines a convolution.
27 | type Kernel interface {
28 | // Weights returns the square matrix of weights in row major order.
29 | Weights() []float64
30 | }
31 |
32 | // SeparableKernel is a linearly separable, square convolution kernel.
33 | // X and Y are the per-axis weights. Each slice must be the same length, and
34 | // have an odd length. The middle element of each slice is the weight for the
35 | // central pixel. For example, the horizontal Sobel kernel is:
36 | // sobelX := &SeparableKernel{
37 | // X: []float64{-1, 0, +1},
38 | // Y: []float64{1, 2, 1},
39 | // }
40 | type SeparableKernel struct {
41 | X, Y []float64
42 | }
43 |
44 | func (k *SeparableKernel) Weights() []float64 {
45 | n := len(k.X)
46 | w := make([]float64, n*n)
47 | for y := 0; y < n; y++ {
48 | for x := 0; x < n; x++ {
49 | w[y*n+x] = k.X[x] * k.Y[y]
50 | }
51 | }
52 | return w
53 | }
54 |
55 | // fullKernel is a square convolution kernel.
56 | type fullKernel []float64
57 |
58 | func (k fullKernel) Weights() []float64 { return k }
59 |
60 | func kernelSize(w []float64) (size int, err error) {
61 | size = int(math.Sqrt(float64(len(w))))
62 | if size*size != len(w) {
63 | return 0, errors.New("graphics: kernel is not square")
64 | }
65 | if size%2 != 1 {
66 | return 0, errors.New("graphics: kernel size is not odd")
67 | }
68 | return size, nil
69 | }
70 |
71 | // NewKernel returns a square convolution kernel.
72 | func NewKernel(w []float64) (Kernel, error) {
73 | if _, err := kernelSize(w); err != nil {
74 | return nil, err
75 | }
76 | return fullKernel(w), nil
77 | }
78 |
79 | func convolveRGBASep(dst *image.RGBA, src image.Image, k *SeparableKernel) error {
80 | if len(k.X) != len(k.Y) {
81 | return fmt.Errorf("graphics: kernel not square (x %d, y %d)", len(k.X), len(k.Y))
82 | }
83 | if len(k.X)%2 != 1 {
84 | return fmt.Errorf("graphics: kernel length (%d) not odd", len(k.X))
85 | }
86 | radius := (len(k.X) - 1) / 2
87 |
88 | // buf holds the result of vertically blurring src.
89 | bounds := dst.Bounds()
90 | width, height := bounds.Dx(), bounds.Dy()
91 | buf := make([]float64, width*height*4)
92 | for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
93 | for x := bounds.Min.X; x < bounds.Max.X; x++ {
94 | var r, g, b, a float64
95 | // k0 is the kernel weight for the center pixel. This may be greater
96 | // than kernel[0], near the boundary of the source image, to avoid
97 | // vignetting.
98 | k0 := k.X[radius]
99 |
100 | // Add the pixels from above.
101 | for i := 1; i <= radius; i++ {
102 | f := k.Y[radius-i]
103 | if y-i < bounds.Min.Y {
104 | k0 += f
105 | } else {
106 | or, og, ob, oa := src.At(x, y-i).RGBA()
107 | r += float64(or>>8) * f
108 | g += float64(og>>8) * f
109 | b += float64(ob>>8) * f
110 | a += float64(oa>>8) * f
111 | }
112 | }
113 |
114 | // Add the pixels from below.
115 | for i := 1; i <= radius; i++ {
116 | f := k.Y[radius+i]
117 | if y+i >= bounds.Max.Y {
118 | k0 += f
119 | } else {
120 | or, og, ob, oa := src.At(x, y+i).RGBA()
121 | r += float64(or>>8) * f
122 | g += float64(og>>8) * f
123 | b += float64(ob>>8) * f
124 | a += float64(oa>>8) * f
125 | }
126 | }
127 |
128 | // Add the central pixel.
129 | or, og, ob, oa := src.At(x, y).RGBA()
130 | r += float64(or>>8) * k0
131 | g += float64(og>>8) * k0
132 | b += float64(ob>>8) * k0
133 | a += float64(oa>>8) * k0
134 |
135 | // Write to buf.
136 | o := (y-bounds.Min.Y)*width*4 + (x-bounds.Min.X)*4
137 | buf[o+0] = r
138 | buf[o+1] = g
139 | buf[o+2] = b
140 | buf[o+3] = a
141 | }
142 | }
143 |
144 | // dst holds the result of horizontally blurring buf.
145 | for y := 0; y < height; y++ {
146 | for x := 0; x < width; x++ {
147 | var r, g, b, a float64
148 | k0, off := k.X[radius], y*width*4+x*4
149 |
150 | // Add the pixels from the left.
151 | for i := 1; i <= radius; i++ {
152 | f := k.X[radius-i]
153 | if x-i < 0 {
154 | k0 += f
155 | } else {
156 | o := off - i*4
157 | r += buf[o+0] * f
158 | g += buf[o+1] * f
159 | b += buf[o+2] * f
160 | a += buf[o+3] * f
161 | }
162 | }
163 |
164 | // Add the pixels from the right.
165 | for i := 1; i <= radius; i++ {
166 | f := k.X[radius+i]
167 | if x+i >= width {
168 | k0 += f
169 | } else {
170 | o := off + i*4
171 | r += buf[o+0] * f
172 | g += buf[o+1] * f
173 | b += buf[o+2] * f
174 | a += buf[o+3] * f
175 | }
176 | }
177 |
178 | // Add the central pixel.
179 | r += buf[off+0] * k0
180 | g += buf[off+1] * k0
181 | b += buf[off+2] * k0
182 | a += buf[off+3] * k0
183 |
184 | // Write to dst, clamping to the range [0, 255].
185 | dstOff := (y-dst.Rect.Min.Y)*dst.Stride + (x-dst.Rect.Min.X)*4
186 | dst.Pix[dstOff+0] = uint8(clamp(r+0.5, 0, 255))
187 | dst.Pix[dstOff+1] = uint8(clamp(g+0.5, 0, 255))
188 | dst.Pix[dstOff+2] = uint8(clamp(b+0.5, 0, 255))
189 | dst.Pix[dstOff+3] = uint8(clamp(a+0.5, 0, 255))
190 | }
191 | }
192 |
193 | return nil
194 | }
195 |
196 | func convolveRGBA(dst *image.RGBA, src image.Image, k Kernel) error {
197 | b := dst.Bounds()
198 | bs := src.Bounds()
199 | w := k.Weights()
200 | size, err := kernelSize(w)
201 | if err != nil {
202 | return err
203 | }
204 | radius := (size - 1) / 2
205 |
206 | for y := b.Min.Y; y < b.Max.Y; y++ {
207 | for x := b.Min.X; x < b.Max.X; x++ {
208 | if !image.Pt(x, y).In(bs) {
209 | continue
210 | }
211 |
212 | var r, g, b, a, adj float64
213 | for cy := y - radius; cy <= y+radius; cy++ {
214 | for cx := x - radius; cx <= x+radius; cx++ {
215 | factor := w[(cy-y+radius)*size+cx-x+radius]
216 | if !image.Pt(cx, cy).In(bs) {
217 | adj += factor
218 | } else {
219 | sr, sg, sb, sa := src.At(cx, cy).RGBA()
220 | r += float64(sr>>8) * factor
221 | g += float64(sg>>8) * factor
222 | b += float64(sb>>8) * factor
223 | a += float64(sa>>8) * factor
224 | }
225 | }
226 | }
227 |
228 | if adj != 0 {
229 | sr, sg, sb, sa := src.At(x, y).RGBA()
230 | r += float64(sr>>8) * adj
231 | g += float64(sg>>8) * adj
232 | b += float64(sb>>8) * adj
233 | a += float64(sa>>8) * adj
234 | }
235 |
236 | off := (y-dst.Rect.Min.Y)*dst.Stride + (x-dst.Rect.Min.X)*4
237 | dst.Pix[off+0] = uint8(clamp(r+0.5, 0, 0xff))
238 | dst.Pix[off+1] = uint8(clamp(g+0.5, 0, 0xff))
239 | dst.Pix[off+2] = uint8(clamp(b+0.5, 0, 0xff))
240 | dst.Pix[off+3] = uint8(clamp(a+0.5, 0, 0xff))
241 | }
242 | }
243 |
244 | return nil
245 | }
246 |
247 | // Convolve produces dst by applying the convolution kernel k to src.
248 | func Convolve(dst draw.Image, src image.Image, k Kernel) (err error) {
249 | if dst == nil || src == nil || k == nil {
250 | return nil
251 | }
252 |
253 | b := dst.Bounds()
254 | dstRgba, ok := dst.(*image.RGBA)
255 | if !ok {
256 | dstRgba = image.NewRGBA(b)
257 | }
258 |
259 | switch k := k.(type) {
260 | case *SeparableKernel:
261 | err = convolveRGBASep(dstRgba, src, k)
262 | default:
263 | err = convolveRGBA(dstRgba, src, k)
264 | }
265 |
266 | if err != nil {
267 | return err
268 | }
269 |
270 | if !ok {
271 | draw.Draw(dst, b, dstRgba, b.Min, draw.Src)
272 | }
273 | return nil
274 | }
275 |
--------------------------------------------------------------------------------
/graphics/convolve/convolve_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2011 The Graphics-Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package convolve
6 |
7 | import (
8 | "github.com/hunterhug/go_image/graphics/graphicstest"
9 | "image"
10 | "reflect"
11 | "testing"
12 |
13 | _ "image/png"
14 | )
15 |
16 | func TestSeparableWeights(t *testing.T) {
17 | sobelXFull := []float64{
18 | -1, 0, 1,
19 | -2, 0, 2,
20 | -1, 0, 1,
21 | }
22 | sobelXSep := &SeparableKernel{
23 | X: []float64{-1, 0, +1},
24 | Y: []float64{1, 2, 1},
25 | }
26 | w := sobelXSep.Weights()
27 | if !reflect.DeepEqual(w, sobelXFull) {
28 | t.Errorf("got %v want %v", w, sobelXFull)
29 | }
30 | }
31 |
32 | func TestConvolve(t *testing.T) {
33 | kernFull, err := NewKernel([]float64{
34 | 0, 0, 0,
35 | 1, 1, 1,
36 | 0, 0, 0,
37 | })
38 | if err != nil {
39 | t.Fatal(err)
40 | }
41 |
42 | kernSep := &SeparableKernel{
43 | X: []float64{1, 1, 1},
44 | Y: []float64{0, 1, 0},
45 | }
46 |
47 | src, err := graphicstest.LoadImage("../../testdata/gopher.png")
48 | if err != nil {
49 | t.Fatal(err)
50 | }
51 | b := src.Bounds()
52 |
53 | sep := image.NewRGBA(b)
54 | if err = Convolve(sep, src, kernSep); err != nil {
55 | t.Fatal(err)
56 | }
57 |
58 | full := image.NewRGBA(b)
59 | Convolve(full, src, kernFull)
60 |
61 | err = graphicstest.ImageWithinTolerance(sep, full, 0x101)
62 | if err != nil {
63 | t.Fatal(err)
64 | }
65 | }
66 |
67 | func TestConvolveNil(t *testing.T) {
68 | if err := Convolve(nil, nil, nil); err != nil {
69 | t.Fatal(err)
70 | }
71 | }
72 |
73 | func TestConvolveEmpty(t *testing.T) {
74 | empty := image.NewRGBA(image.Rect(0, 0, 0, 0))
75 | if err := Convolve(empty, empty, nil); err != nil {
76 | t.Fatal(err)
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/graphics/detect/Makefile:
--------------------------------------------------------------------------------
1 | # Copyright 2011 The Graphics-Go Authors. All rights reserved.
2 | # Use of this source code is governed by a BSD-style
3 | # license that can be found in the LICENSE file.
4 |
5 | include $(GOROOT)/src/Make.inc
6 |
7 | TARG=code.google.com/p/graphics-go/graphics
8 | GOFILES=\
9 | detect.go\
10 | doc.go\
11 | integral.go\
12 | opencv_parser.go\
13 | projector.go\
14 |
15 | include $(GOROOT)/src/Make.pkg
16 |
--------------------------------------------------------------------------------
/graphics/detect/detect.go:
--------------------------------------------------------------------------------
1 | // Copyright 2011 The Graphics-Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package detect
6 |
7 | import (
8 | "image"
9 | "math"
10 | )
11 |
12 | // Feature is a Haar-like feature.
13 | type Feature struct {
14 | Rect image.Rectangle
15 | Weight float64
16 | }
17 |
18 | // Classifier is a set of features with a threshold.
19 | type Classifier struct {
20 | Feature []Feature
21 | Threshold float64
22 | Left float64
23 | Right float64
24 | }
25 |
26 | // CascadeStage is a cascade of classifiers.
27 | type CascadeStage struct {
28 | Classifier []Classifier
29 | Threshold float64
30 | }
31 |
32 | // Cascade is a degenerate tree of Haar-like classifiers.
33 | type Cascade struct {
34 | Stage []CascadeStage
35 | Size image.Point
36 | }
37 |
38 | // Match returns true if the full image is classified as an object.
39 | func (c *Cascade) Match(m image.Image) bool {
40 | return c.classify(newWindow(m))
41 | }
42 |
43 | // Find returns a set of areas of m that match the feature cascade c.
44 | func (c *Cascade) Find(m image.Image) []image.Rectangle {
45 | // TODO(crawshaw): Consider de-duping strategies.
46 | matches := []image.Rectangle{}
47 | w := newWindow(m)
48 |
49 | b := m.Bounds()
50 | origScale := c.Size
51 | for s := origScale; s.X < b.Dx() && s.Y < b.Dy(); s = s.Add(s.Div(10)) {
52 | // translate region and classify
53 | tx := image.Pt(s.X/10, 0)
54 | ty := image.Pt(0, s.Y/10)
55 | for r := image.Rect(0, 0, s.X, s.Y).Add(b.Min); r.In(b); r = r.Add(ty) {
56 | for r1 := r; r1.In(b); r1 = r1.Add(tx) {
57 | if c.classify(w.subWindow(r1)) {
58 | matches = append(matches, r1)
59 | }
60 | }
61 | }
62 | }
63 | return matches
64 | }
65 |
66 | type window struct {
67 | mi *integral
68 | miSq *integral
69 | rect image.Rectangle
70 | invArea float64
71 | stdDev float64
72 | }
73 |
74 | func (w *window) init() {
75 | w.invArea = 1 / float64(w.rect.Dx()*w.rect.Dy())
76 | mean := float64(w.mi.sum(w.rect)) * w.invArea
77 | vr := float64(w.miSq.sum(w.rect))*w.invArea - mean*mean
78 | if vr < 0 {
79 | vr = 1
80 | }
81 | w.stdDev = math.Sqrt(vr)
82 | }
83 |
84 | func newWindow(m image.Image) *window {
85 | mi, miSq := newIntegrals(m)
86 | res := &window{
87 | mi: mi,
88 | miSq: miSq,
89 | rect: m.Bounds(),
90 | }
91 | res.init()
92 | return res
93 | }
94 |
95 | func (w *window) subWindow(r image.Rectangle) *window {
96 | res := &window{
97 | mi: w.mi,
98 | miSq: w.miSq,
99 | rect: r,
100 | }
101 | res.init()
102 | return res
103 | }
104 |
105 | func (c *Classifier) classify(w *window, pr *projector) float64 {
106 | s := 0.0
107 | for _, f := range c.Feature {
108 | s += float64(w.mi.sum(pr.rect(f.Rect))) * f.Weight
109 | }
110 | s *= w.invArea // normalize to maintain scale invariance
111 | if s < c.Threshold*w.stdDev {
112 | return c.Left
113 | }
114 | return c.Right
115 | }
116 |
117 | func (s *CascadeStage) classify(w *window, pr *projector) bool {
118 | sum := 0.0
119 | for _, c := range s.Classifier {
120 | sum += c.classify(w, pr)
121 | }
122 | return sum >= s.Threshold
123 | }
124 |
125 | func (c *Cascade) classify(w *window) bool {
126 | pr := newProjector(w.rect, image.Rectangle{image.Pt(0, 0), c.Size})
127 | for _, s := range c.Stage {
128 | if !s.classify(w, pr) {
129 | return false
130 | }
131 | }
132 | return true
133 | }
134 |
--------------------------------------------------------------------------------
/graphics/detect/detect_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2011 The Graphics-Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package detect
6 |
7 | import (
8 | "image"
9 | "image/draw"
10 | "testing"
11 | )
12 |
13 | var (
14 | c0 = Classifier{
15 | Feature: []Feature{
16 | Feature{Rect: image.Rect(0, 0, 3, 4), Weight: 4.0},
17 | },
18 | Threshold: 0.2,
19 | Left: 0.8,
20 | Right: 0.2,
21 | }
22 | c1 = Classifier{
23 | Feature: []Feature{
24 | Feature{Rect: image.Rect(3, 4, 4, 5), Weight: 4.0},
25 | },
26 | Threshold: 0.2,
27 | Left: 0.8,
28 | Right: 0.2,
29 | }
30 | c2 = Classifier{
31 | Feature: []Feature{
32 | Feature{Rect: image.Rect(0, 0, 1, 1), Weight: +4.0},
33 | Feature{Rect: image.Rect(0, 0, 2, 2), Weight: -1.0},
34 | },
35 | Threshold: 0.2,
36 | Left: 0.8,
37 | Right: 0.2,
38 | }
39 | )
40 |
41 | func TestClassifier(t *testing.T) {
42 | m := image.NewGray(image.Rect(0, 0, 20, 20))
43 | b := m.Bounds()
44 | draw.Draw(m, image.Rect(0, 0, 20, 20), image.White, image.ZP, draw.Src)
45 | draw.Draw(m, image.Rect(3, 4, 4, 5), image.Black, image.ZP, draw.Src)
46 | w := newWindow(m)
47 | pr := newProjector(b, b)
48 |
49 | if res := c0.classify(w, pr); res != c0.Right {
50 | t.Errorf("c0 got %f want %f", res, c0.Right)
51 | }
52 | if res := c1.classify(w, pr); res != c1.Left {
53 | t.Errorf("c1 got %f want %f", res, c1.Left)
54 | }
55 | if res := c2.classify(w, pr); res != c1.Left {
56 | t.Errorf("c2 got %f want %f", res, c1.Left)
57 | }
58 | }
59 |
60 | func TestClassifierScale(t *testing.T) {
61 | m := image.NewGray(image.Rect(0, 0, 50, 50))
62 | b := m.Bounds()
63 | draw.Draw(m, image.Rect(0, 0, 8, 10), image.White, b.Min, draw.Src)
64 | draw.Draw(m, image.Rect(8, 10, 10, 13), image.Black, b.Min, draw.Src)
65 | w := newWindow(m)
66 | pr := newProjector(b, image.Rect(0, 0, 20, 20))
67 |
68 | if res := c0.classify(w, pr); res != c0.Right {
69 | t.Errorf("scaled c0 got %f want %f", res, c0.Right)
70 | }
71 | if res := c1.classify(w, pr); res != c1.Left {
72 | t.Errorf("scaled c1 got %f want %f", res, c1.Left)
73 | }
74 | if res := c2.classify(w, pr); res != c1.Left {
75 | t.Errorf("scaled c2 got %f want %f", res, c1.Left)
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/graphics/detect/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright 2011 The Graphics-Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | /*
6 | Package detect implements an object detector cascade.
7 |
8 | The technique used is a degenerate tree of Haar-like classifiers, commonly
9 | used for face detection. It is described in
10 |
11 | P. Viola, M. Jones.
12 | Rapid Object Detection using a Boosted Cascade of Simple Features, 2001
13 | IEEE Conference on Computer Vision and Pattern Recognition
14 |
15 | A Cascade can be constructed manually from a set of Classifiers in stages,
16 | or can be loaded from an XML file in the OpenCV format with
17 |
18 | classifier, _, err := detect.ParseOpenCV(r)
19 |
20 | The classifier can be used to determine if a full image is detected as an
21 | object using Detect
22 |
23 | if classifier.Match(m) {
24 | // m is an image of a face.
25 | }
26 |
27 | It is also possible to search an image for occurrences of an object
28 |
29 | objs := classifier.Find(m)
30 | */
31 | package detect
32 |
--------------------------------------------------------------------------------
/graphics/detect/integral.go:
--------------------------------------------------------------------------------
1 | // Copyright 2011 The Graphics-Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package detect
6 |
7 | import (
8 | "image"
9 | "image/draw"
10 | )
11 |
12 | // integral is an image.Image-like structure that stores the cumulative
13 | // sum of the preceding pixels. This allows for O(1) summation of any
14 | // rectangular region within the image.
15 | type integral struct {
16 | // pix holds the cumulative sum of the image's pixels. The pixel at
17 | // (x, y) starts at pix[(y-rect.Min.Y)*stride + (x-rect.Min.X)*1].
18 | pix []uint64
19 | stride int
20 | rect image.Rectangle
21 | }
22 |
23 | func (p *integral) at(x, y int) uint64 {
24 | return p.pix[(y-p.rect.Min.Y)*p.stride+(x-p.rect.Min.X)]
25 | }
26 |
27 | func (p *integral) sum(b image.Rectangle) uint64 {
28 | c := p.at(b.Max.X-1, b.Max.Y-1)
29 | inY := b.Min.Y > p.rect.Min.Y
30 | inX := b.Min.X > p.rect.Min.X
31 | if inY && inX {
32 | c += p.at(b.Min.X-1, b.Min.Y-1)
33 | }
34 | if inY {
35 | c -= p.at(b.Max.X-1, b.Min.Y-1)
36 | }
37 | if inX {
38 | c -= p.at(b.Min.X-1, b.Max.Y-1)
39 | }
40 | return c
41 | }
42 |
43 | func (m *integral) integrate() {
44 | b := m.rect
45 | for y := b.Min.Y; y < b.Max.Y; y++ {
46 | for x := b.Min.X; x < b.Max.X; x++ {
47 | c := uint64(0)
48 | if y > b.Min.Y && x > b.Min.X {
49 | c += m.at(x-1, y)
50 | c += m.at(x, y-1)
51 | c -= m.at(x-1, y-1)
52 | } else if y > b.Min.Y {
53 | c += m.at(b.Min.X, y-1)
54 | } else if x > b.Min.X {
55 | c += m.at(x-1, b.Min.Y)
56 | }
57 | m.pix[(y-m.rect.Min.Y)*m.stride+(x-m.rect.Min.X)] += c
58 | }
59 | }
60 | }
61 |
62 | // newIntegrals returns the integral and the squared integral.
63 | func newIntegrals(src image.Image) (*integral, *integral) {
64 | b := src.Bounds()
65 | srcg, ok := src.(*image.Gray)
66 | if !ok {
67 | srcg = image.NewGray(b)
68 | draw.Draw(srcg, b, src, b.Min, draw.Src)
69 | }
70 |
71 | m := integral{
72 | pix: make([]uint64, b.Max.Y*b.Max.X),
73 | stride: b.Max.X,
74 | rect: b,
75 | }
76 | mSq := integral{
77 | pix: make([]uint64, b.Max.Y*b.Max.X),
78 | stride: b.Max.X,
79 | rect: b,
80 | }
81 | for y := b.Min.Y; y < b.Max.Y; y++ {
82 | for x := b.Min.X; x < b.Max.X; x++ {
83 | os := (y-b.Min.Y)*srcg.Stride + x - b.Min.X
84 | om := (y-b.Min.Y)*m.stride + x - b.Min.X
85 | c := uint64(srcg.Pix[os])
86 | m.pix[om] = c
87 | mSq.pix[om] = c * c
88 | }
89 | }
90 | m.integrate()
91 | mSq.integrate()
92 | return &m, &mSq
93 | }
94 |
--------------------------------------------------------------------------------
/graphics/detect/integral_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2011 The Graphics-Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package detect
6 |
7 | import (
8 | "bytes"
9 | "fmt"
10 | "image"
11 | "testing"
12 | )
13 |
14 | type integralTest struct {
15 | x int
16 | y int
17 | src []uint8
18 | res []uint8
19 | }
20 |
21 | var integralTests = []integralTest{
22 | {
23 | 1, 1,
24 | []uint8{0x01},
25 | []uint8{0x01},
26 | },
27 | {
28 | 2, 2,
29 | []uint8{
30 | 0x01, 0x02,
31 | 0x03, 0x04,
32 | },
33 | []uint8{
34 | 0x01, 0x03,
35 | 0x04, 0x0a,
36 | },
37 | },
38 | {
39 | 4, 4,
40 | []uint8{
41 | 0x02, 0x03, 0x00, 0x01,
42 | 0x01, 0x02, 0x01, 0x05,
43 | 0x01, 0x01, 0x01, 0x01,
44 | 0x01, 0x01, 0x01, 0x01,
45 | },
46 | []uint8{
47 | 0x02, 0x05, 0x05, 0x06,
48 | 0x03, 0x08, 0x09, 0x0f,
49 | 0x04, 0x0a, 0x0c, 0x13,
50 | 0x05, 0x0c, 0x0f, 0x17,
51 | },
52 | },
53 | }
54 |
55 | func sprintBox(box []byte, width, height int) string {
56 | buf := bytes.NewBuffer(nil)
57 | i := 0
58 | for y := 0; y < height; y++ {
59 | for x := 0; x < width; x++ {
60 | fmt.Fprintf(buf, " 0x%02x,", box[i])
61 | i++
62 | }
63 | buf.WriteByte('\n')
64 | }
65 | return buf.String()
66 | }
67 |
68 | func TestIntegral(t *testing.T) {
69 | for i, oc := range integralTests {
70 | src := &image.Gray{
71 | Pix: oc.src,
72 | Stride: oc.x,
73 | Rect: image.Rect(0, 0, oc.x, oc.y),
74 | }
75 | dst, _ := newIntegrals(src)
76 | res := make([]byte, len(dst.pix))
77 | for i, p := range dst.pix {
78 | res[i] = byte(p)
79 | }
80 |
81 | if !bytes.Equal(res, oc.res) {
82 | got := sprintBox(res, oc.x, oc.y)
83 | want := sprintBox(oc.res, oc.x, oc.y)
84 | t.Errorf("%d: got\n%s\n want\n%s", i, got, want)
85 | }
86 | }
87 | }
88 |
89 | func TestIntegralSum(t *testing.T) {
90 | src := &image.Gray{
91 | Pix: []uint8{
92 | 0x02, 0x03, 0x00, 0x01, 0x03,
93 | 0x01, 0x02, 0x01, 0x05, 0x05,
94 | 0x01, 0x01, 0x01, 0x01, 0x02,
95 | 0x01, 0x01, 0x01, 0x01, 0x07,
96 | 0x02, 0x01, 0x00, 0x03, 0x01,
97 | },
98 | Stride: 5,
99 | Rect: image.Rect(0, 0, 5, 5),
100 | }
101 | img, _ := newIntegrals(src)
102 |
103 | type sumTest struct {
104 | rect image.Rectangle
105 | sum uint64
106 | }
107 |
108 | var sumTests = []sumTest{
109 | {image.Rect(0, 0, 1, 1), 2},
110 | {image.Rect(0, 0, 2, 1), 5},
111 | {image.Rect(0, 0, 1, 3), 4},
112 | {image.Rect(1, 1, 3, 3), 5},
113 | {image.Rect(2, 2, 4, 4), 4},
114 | {image.Rect(4, 3, 5, 5), 8},
115 | {image.Rect(2, 4, 3, 5), 0},
116 | }
117 |
118 | for _, st := range sumTests {
119 | s := img.sum(st.rect)
120 | if s != st.sum {
121 | t.Errorf("%v: got %d want %d", st.rect, s, st.sum)
122 | return
123 | }
124 | }
125 | }
126 |
127 | func TestIntegralSubImage(t *testing.T) {
128 | m0 := &image.Gray{
129 | Pix: []uint8{
130 | 0x02, 0x03, 0x00, 0x01, 0x03,
131 | 0x01, 0x02, 0x01, 0x05, 0x05,
132 | 0x01, 0x04, 0x01, 0x01, 0x02,
133 | 0x01, 0x02, 0x01, 0x01, 0x07,
134 | 0x02, 0x01, 0x09, 0x03, 0x01,
135 | },
136 | Stride: 5,
137 | Rect: image.Rect(0, 0, 5, 5),
138 | }
139 | b := image.Rect(1, 1, 4, 4)
140 | m1 := m0.SubImage(b)
141 | mi0, _ := newIntegrals(m0)
142 | mi1, _ := newIntegrals(m1)
143 |
144 | sum0 := mi0.sum(b)
145 | sum1 := mi1.sum(b)
146 | if sum0 != sum1 {
147 | t.Errorf("b got %d want %d", sum0, sum1)
148 | }
149 |
150 | r0 := image.Rect(2, 2, 4, 4)
151 | sum0 = mi0.sum(r0)
152 | sum1 = mi1.sum(r0)
153 | if sum0 != sum1 {
154 | t.Errorf("r0 got %d want %d", sum1, sum0)
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/graphics/detect/opencv_parser.go:
--------------------------------------------------------------------------------
1 | // Copyright 2011 The Graphics-Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package detect
6 |
7 | import (
8 | "bytes"
9 | "encoding/xml"
10 | "errors"
11 | "fmt"
12 | "image"
13 | "io"
14 | "io/ioutil"
15 | "strconv"
16 | "strings"
17 | )
18 |
19 | type xmlFeature struct {
20 | Rects []string `xml:"grp>feature>rects>grp"`
21 | Tilted int `xml:"grp>feature>tilted"`
22 | Threshold float64 `xml:"grp>threshold"`
23 | Left float64 `xml:"grp>left_val"`
24 | Right float64 `xml:"grp>right_val"`
25 | }
26 |
27 | type xmlStages struct {
28 | Trees []xmlFeature `xml:"trees>grp"`
29 | Stage_threshold float64 `xml:"stage_threshold"`
30 | Parent int `xml:"parent"`
31 | Next int `xml:"next"`
32 | }
33 |
34 | type opencv_storage struct {
35 | Any struct {
36 | XMLName xml.Name
37 | Type string `xml:"type_id,attr"`
38 | Size string `xml:"size"`
39 | Stages []xmlStages `xml:"stages>grp"`
40 | } `xml:",any"`
41 | }
42 |
43 | func buildFeature(r string) (f Feature, err error) {
44 | var x, y, w, h int
45 | var weight float64
46 | _, err = fmt.Sscanf(r, "%d %d %d %d %f", &x, &y, &w, &h, &weight)
47 | if err != nil {
48 | return
49 | }
50 | f.Rect = image.Rect(x, y, x+w, y+h)
51 | f.Weight = weight
52 | return
53 | }
54 |
55 | func buildCascade(s *opencv_storage) (c *Cascade, name string, err error) {
56 | if s.Any.Type != "opencv-haar-classifier" {
57 | err = fmt.Errorf("got %s want opencv-haar-classifier", s.Any.Type)
58 | return
59 | }
60 | name = s.Any.XMLName.Local
61 |
62 | c = &Cascade{}
63 | sizes := strings.Split(s.Any.Size, " ")
64 | w, err := strconv.Atoi(sizes[0])
65 | if err != nil {
66 | return nil, "", err
67 | }
68 | h, err := strconv.Atoi(sizes[1])
69 | if err != nil {
70 | return nil, "", err
71 | }
72 | c.Size = image.Pt(w, h)
73 | c.Stage = []CascadeStage{}
74 |
75 | for _, stage := range s.Any.Stages {
76 | cs := CascadeStage{
77 | Classifier: []Classifier{},
78 | Threshold: stage.Stage_threshold,
79 | }
80 | for _, tree := range stage.Trees {
81 | if tree.Tilted != 0 {
82 | err = errors.New("Cascade does not support tilted features")
83 | return
84 | }
85 |
86 | cls := Classifier{
87 | Feature: []Feature{},
88 | Threshold: tree.Threshold,
89 | Left: tree.Left,
90 | Right: tree.Right,
91 | }
92 |
93 | for _, rect := range tree.Rects {
94 | f, err := buildFeature(rect)
95 | if err != nil {
96 | return nil, "", err
97 | }
98 | cls.Feature = append(cls.Feature, f)
99 | }
100 |
101 | cs.Classifier = append(cs.Classifier, cls)
102 | }
103 | c.Stage = append(c.Stage, cs)
104 | }
105 |
106 | return
107 | }
108 |
109 | // ParseOpenCV produces a detection Cascade from an OpenCV XML file.
110 | func ParseOpenCV(r io.Reader) (cascade *Cascade, name string, err error) {
111 | // BUG(crawshaw): tag-based parsing doesn't seem to work with <_>
112 | buf, err := ioutil.ReadAll(r)
113 | if err != nil {
114 | return
115 | }
116 | buf = bytes.Replace(buf, []byte("<_>"), []byte(""), -1)
117 | buf = bytes.Replace(buf, []byte(""), []byte(""), -1)
118 |
119 | s := &opencv_storage{}
120 | err = xml.Unmarshal(buf, s)
121 | if err != nil {
122 | return
123 | }
124 | return buildCascade(s)
125 | }
126 |
--------------------------------------------------------------------------------
/graphics/detect/opencv_parser_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2011 The Graphics-Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package detect
6 |
7 | import (
8 | "image"
9 | "os"
10 | "reflect"
11 | "testing"
12 | )
13 |
14 | var (
15 | classifier0 = Classifier{
16 | Feature: []Feature{
17 | Feature{Rect: image.Rect(0, 0, 3, 4), Weight: -1},
18 | Feature{Rect: image.Rect(3, 4, 5, 6), Weight: 3.1},
19 | },
20 | Threshold: 0.03,
21 | Left: 0.01,
22 | Right: 0.8,
23 | }
24 | classifier1 = Classifier{
25 | Feature: []Feature{
26 | Feature{Rect: image.Rect(3, 7, 17, 11), Weight: -3.2},
27 | Feature{Rect: image.Rect(3, 9, 17, 11), Weight: 2.},
28 | },
29 | Threshold: 0.11,
30 | Left: 0.03,
31 | Right: 0.83,
32 | }
33 | classifier2 = Classifier{
34 | Feature: []Feature{
35 | Feature{Rect: image.Rect(1, 1, 3, 3), Weight: -1.},
36 | Feature{Rect: image.Rect(3, 3, 5, 5), Weight: 2.5},
37 | },
38 | Threshold: 0.07,
39 | Left: 0.2,
40 | Right: 0.4,
41 | }
42 | cascade = Cascade{
43 | Stage: []CascadeStage{
44 | CascadeStage{
45 | Classifier: []Classifier{classifier0, classifier1},
46 | Threshold: 0.82,
47 | },
48 | CascadeStage{
49 | Classifier: []Classifier{classifier2},
50 | Threshold: 0.22,
51 | },
52 | },
53 | Size: image.Pt(20, 20),
54 | }
55 | )
56 |
57 | func TestParseOpenCV(t *testing.T) {
58 | file, err := os.Open("../../testdata/opencv.xml")
59 | if err != nil {
60 | t.Fatal(err)
61 | }
62 | defer file.Close()
63 |
64 | cascadeFile, name, err := ParseOpenCV(file)
65 | if err != nil {
66 | t.Fatal(err)
67 | }
68 | if name != "name_of_cascade" {
69 | t.Fatalf("name: got %s want name_of_cascade", name)
70 | }
71 |
72 | if !reflect.DeepEqual(cascade, *cascadeFile) {
73 | t.Errorf("got\n %v want\n %v", *cascadeFile, cascade)
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/graphics/detect/projector.go:
--------------------------------------------------------------------------------
1 | // Copyright 2011 The Graphics-Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package detect
6 |
7 | import (
8 | "image"
9 | )
10 |
11 | // projector allows projecting from a source Rectangle onto a target Rectangle.
12 | type projector struct {
13 | // rx, ry is the scaling factor.
14 | rx, ry float64
15 | // dx, dy is the translation factor.
16 | dx, dy float64
17 | // r is the clipping region of the target.
18 | r image.Rectangle
19 | }
20 |
21 | // newProjector creates a Projector with source src and target dst.
22 | func newProjector(dst image.Rectangle, src image.Rectangle) *projector {
23 | return &projector{
24 | rx: float64(dst.Dx()) / float64(src.Dx()),
25 | ry: float64(dst.Dy()) / float64(src.Dy()),
26 | dx: float64(dst.Min.X - src.Min.X),
27 | dy: float64(dst.Min.Y - src.Min.Y),
28 | r: dst,
29 | }
30 | }
31 |
32 | // pt projects p from the source rectangle onto the target rectangle.
33 | func (s *projector) pt(p image.Point) image.Point {
34 | return image.Point{
35 | clamp(s.rx*float64(p.X)+s.dx, s.r.Min.X, s.r.Max.X),
36 | clamp(s.ry*float64(p.Y)+s.dy, s.r.Min.Y, s.r.Max.Y),
37 | }
38 | }
39 |
40 | // rect projects r from the source rectangle onto the target rectangle.
41 | func (s *projector) rect(r image.Rectangle) image.Rectangle {
42 | return image.Rectangle{s.pt(r.Min), s.pt(r.Max)}
43 | }
44 |
45 | // clamp rounds and clamps o to the integer range [x0, x1].
46 | func clamp(o float64, x0, x1 int) int {
47 | x := int(o + 0.5)
48 | if x < x0 {
49 | return x0
50 | }
51 | if x > x1 {
52 | return x1
53 | }
54 | return x
55 | }
56 |
--------------------------------------------------------------------------------
/graphics/detect/projector_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2011 The Graphics-Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package detect
6 |
7 | import (
8 | "image"
9 | "reflect"
10 | "testing"
11 | )
12 |
13 | type projectorTest struct {
14 | dst image.Rectangle
15 | src image.Rectangle
16 | pdst image.Rectangle
17 | psrc image.Rectangle
18 | }
19 |
20 | var projectorTests = []projectorTest{
21 | {
22 | image.Rect(0, 0, 6, 6),
23 | image.Rect(0, 0, 2, 2),
24 | image.Rect(0, 0, 6, 6),
25 | image.Rect(0, 0, 2, 2),
26 | },
27 | {
28 | image.Rect(0, 0, 6, 6),
29 | image.Rect(0, 0, 2, 2),
30 | image.Rect(3, 3, 6, 6),
31 | image.Rect(1, 1, 2, 2),
32 | },
33 | {
34 | image.Rect(30, 30, 40, 40),
35 | image.Rect(10, 10, 20, 20),
36 | image.Rect(32, 33, 34, 37),
37 | image.Rect(12, 13, 14, 17),
38 | },
39 | }
40 |
41 | func TestProjector(t *testing.T) {
42 | for i, tt := range projectorTests {
43 | pr := newProjector(tt.dst, tt.src)
44 | res := pr.rect(tt.psrc)
45 | if !reflect.DeepEqual(res, tt.pdst) {
46 | t.Errorf("%d: got %v want %v", i, res, tt.pdst)
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/graphics/graphicstest/Makefile:
--------------------------------------------------------------------------------
1 | # Copyright 2011 The Graphics-Go Authors. All rights reserved.
2 | # Use of this source code is governed by a BSD-style
3 | # license that can be found in the LICENSE file.
4 |
5 | include $(GOROOT)/src/Make.inc
6 |
7 | TARG=code.google.com/p/graphics-go/graphics/graphicstest
8 | GOFILES=\
9 | graphicstest.go\
10 |
11 | include $(GOROOT)/src/Make.pkg
12 |
--------------------------------------------------------------------------------
/graphics/graphicstest/graphicstest.go:
--------------------------------------------------------------------------------
1 | // Copyright 2011 The Graphics-Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package graphicstest
6 |
7 | import (
8 | "bytes"
9 | "errors"
10 | "fmt"
11 | "image"
12 | "image/color"
13 | "os"
14 | )
15 |
16 | // LoadImage decodes an image from a file.
17 | func LoadImage(path string) (img image.Image, err error) {
18 | file, err := os.Open(path)
19 | if err != nil {
20 | return
21 | }
22 | defer file.Close()
23 | img, _, err = image.Decode(file)
24 | return
25 | }
26 |
27 | func delta(u0, u1 uint32) int {
28 | d := int(u0) - int(u1)
29 | if d < 0 {
30 | return -d
31 | }
32 | return d
33 | }
34 |
35 | func withinTolerance(c0, c1 color.Color, tol int) bool {
36 | r0, g0, b0, a0 := c0.RGBA()
37 | r1, g1, b1, a1 := c1.RGBA()
38 | r := delta(r0, r1)
39 | g := delta(g0, g1)
40 | b := delta(b0, b1)
41 | a := delta(a0, a1)
42 | return r <= tol && g <= tol && b <= tol && a <= tol
43 | }
44 |
45 | // ImageWithinTolerance checks that each pixel varies by no more than tol.
46 | func ImageWithinTolerance(m0, m1 image.Image, tol int) error {
47 | b0 := m0.Bounds()
48 | b1 := m1.Bounds()
49 | if !b0.Eq(b1) {
50 | return errors.New(fmt.Sprintf("got bounds %v want %v", b0, b1))
51 | }
52 |
53 | for y := b0.Min.Y; y < b0.Max.Y; y++ {
54 | for x := b0.Min.X; x < b0.Max.X; x++ {
55 | c0 := m0.At(x, y)
56 | c1 := m1.At(x, y)
57 | if !withinTolerance(c0, c1, tol) {
58 | e := fmt.Sprintf("got %v want %v at (%d, %d)", c0, c1, x, y)
59 | return errors.New(e)
60 | }
61 | }
62 | }
63 | return nil
64 | }
65 |
66 | // SprintBox pretty prints the array as a hexidecimal matrix.
67 | func SprintBox(box []byte, width, height int) string {
68 | buf := bytes.NewBuffer(nil)
69 | i := 0
70 | for y := 0; y < height; y++ {
71 | for x := 0; x < width; x++ {
72 | fmt.Fprintf(buf, " 0x%02x,", box[i])
73 | i++
74 | }
75 | buf.WriteByte('\n')
76 | }
77 | return buf.String()
78 | }
79 |
80 | // SprintImageR pretty prints the red channel of src. It looks like SprintBox.
81 | func SprintImageR(src *image.RGBA) string {
82 | w, h := src.Rect.Dx(), src.Rect.Dy()
83 | i := 0
84 | box := make([]byte, w*h)
85 | for y := src.Rect.Min.Y; y < src.Rect.Max.Y; y++ {
86 | for x := src.Rect.Min.X; x < src.Rect.Max.X; x++ {
87 | off := (y-src.Rect.Min.Y)*src.Stride + (x-src.Rect.Min.X)*4
88 | box[i] = src.Pix[off]
89 | i++
90 | }
91 | }
92 | return SprintBox(box, w, h)
93 | }
94 |
95 | // MakeRGBA returns an image with R, G, B taken from src.
96 | func MakeRGBA(src []uint8, width int) *image.RGBA {
97 | b := image.Rect(0, 0, width, len(src)/width)
98 | ret := image.NewRGBA(b)
99 | i := 0
100 | for y := b.Min.Y; y < b.Max.Y; y++ {
101 | for x := b.Min.X; x < b.Max.X; x++ {
102 | ret.SetRGBA(x, y, color.RGBA{
103 | R: src[i],
104 | G: src[i],
105 | B: src[i],
106 | A: 0xff,
107 | })
108 | i++
109 | }
110 | }
111 | return ret
112 | }
113 |
--------------------------------------------------------------------------------
/graphics/interp/Makefile:
--------------------------------------------------------------------------------
1 | # Copyright 2012 The Graphics-Go Authors. All rights reserved.
2 | # Use of this source code is governed by a BSD-style
3 | # license that can be found in the LICENSE file.
4 |
5 | include $(GOROOT)/src/Make.inc
6 |
7 | TARG=code.google.com/p/graphics-go/graphics/interp
8 | GOFILES=\
9 | bilinear.go\
10 | doc.go\
11 | interp.go\
12 |
13 | include $(GOROOT)/src/Make.pkg
14 |
--------------------------------------------------------------------------------
/graphics/interp/bilinear.go:
--------------------------------------------------------------------------------
1 | // Copyright 2012 The Graphics-Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package interp
6 |
7 | import (
8 | "image"
9 | "image/color"
10 | "math"
11 | )
12 |
13 | // Bilinear implements bilinear interpolation.
14 | var Bilinear Interp = bilinear{}
15 |
16 | type bilinear struct{}
17 |
18 | func (i bilinear) Interp(src image.Image, x, y float64) color.Color {
19 | if src, ok := src.(*image.RGBA); ok {
20 | return i.RGBA(src, x, y)
21 | }
22 | return bilinearGeneral(src, x, y)
23 | }
24 |
25 | func bilinearGeneral(src image.Image, x, y float64) color.Color {
26 | p := findLinearSrc(src.Bounds(), x, y)
27 | var fr, fg, fb, fa float64
28 | var r, g, b, a uint32
29 |
30 | r, g, b, a = src.At(p.low.X, p.low.Y).RGBA()
31 | fr += float64(r) * p.frac00
32 | fg += float64(g) * p.frac00
33 | fb += float64(b) * p.frac00
34 | fa += float64(a) * p.frac00
35 |
36 | r, g, b, a = src.At(p.high.X, p.low.Y).RGBA()
37 | fr += float64(r) * p.frac01
38 | fg += float64(g) * p.frac01
39 | fb += float64(b) * p.frac01
40 | fa += float64(a) * p.frac01
41 |
42 | r, g, b, a = src.At(p.low.X, p.high.Y).RGBA()
43 | fr += float64(r) * p.frac10
44 | fg += float64(g) * p.frac10
45 | fb += float64(b) * p.frac10
46 | fa += float64(a) * p.frac10
47 |
48 | r, g, b, a = src.At(p.high.X, p.high.Y).RGBA()
49 | fr += float64(r) * p.frac11
50 | fg += float64(g) * p.frac11
51 | fb += float64(b) * p.frac11
52 | fa += float64(a) * p.frac11
53 |
54 | var c color.RGBA64
55 | c.R = uint16(fr + 0.5)
56 | c.G = uint16(fg + 0.5)
57 | c.B = uint16(fb + 0.5)
58 | c.A = uint16(fa + 0.5)
59 | return c
60 | }
61 |
62 | func (bilinear) RGBA(src *image.RGBA, x, y float64) color.RGBA {
63 | p := findLinearSrc(src.Bounds(), x, y)
64 |
65 | // Array offsets for the surrounding pixels.
66 | off00 := offRGBA(src, p.low.X, p.low.Y)
67 | off01 := offRGBA(src, p.high.X, p.low.Y)
68 | off10 := offRGBA(src, p.low.X, p.high.Y)
69 | off11 := offRGBA(src, p.high.X, p.high.Y)
70 |
71 | var fr, fg, fb, fa float64
72 |
73 | fr += float64(src.Pix[off00+0]) * p.frac00
74 | fg += float64(src.Pix[off00+1]) * p.frac00
75 | fb += float64(src.Pix[off00+2]) * p.frac00
76 | fa += float64(src.Pix[off00+3]) * p.frac00
77 |
78 | fr += float64(src.Pix[off01+0]) * p.frac01
79 | fg += float64(src.Pix[off01+1]) * p.frac01
80 | fb += float64(src.Pix[off01+2]) * p.frac01
81 | fa += float64(src.Pix[off01+3]) * p.frac01
82 |
83 | fr += float64(src.Pix[off10+0]) * p.frac10
84 | fg += float64(src.Pix[off10+1]) * p.frac10
85 | fb += float64(src.Pix[off10+2]) * p.frac10
86 | fa += float64(src.Pix[off10+3]) * p.frac10
87 |
88 | fr += float64(src.Pix[off11+0]) * p.frac11
89 | fg += float64(src.Pix[off11+1]) * p.frac11
90 | fb += float64(src.Pix[off11+2]) * p.frac11
91 | fa += float64(src.Pix[off11+3]) * p.frac11
92 |
93 | var c color.RGBA
94 | c.R = uint8(fr + 0.5)
95 | c.G = uint8(fg + 0.5)
96 | c.B = uint8(fb + 0.5)
97 | c.A = uint8(fa + 0.5)
98 | return c
99 | }
100 |
101 | func (bilinear) Gray(src *image.Gray, x, y float64) color.Gray {
102 | p := findLinearSrc(src.Bounds(), x, y)
103 |
104 | // Array offsets for the surrounding pixels.
105 | off00 := offGray(src, p.low.X, p.low.Y)
106 | off01 := offGray(src, p.high.X, p.low.Y)
107 | off10 := offGray(src, p.low.X, p.high.Y)
108 | off11 := offGray(src, p.high.X, p.high.Y)
109 |
110 | var fc float64
111 | fc += float64(src.Pix[off00]) * p.frac00
112 | fc += float64(src.Pix[off01]) * p.frac01
113 | fc += float64(src.Pix[off10]) * p.frac10
114 | fc += float64(src.Pix[off11]) * p.frac11
115 |
116 | var c color.Gray
117 | c.Y = uint8(fc + 0.5)
118 | return c
119 | }
120 |
121 | type bilinearSrc struct {
122 | // Top-left and bottom-right interpolation sources
123 | low, high image.Point
124 | // Fraction of each pixel to take. The 0 suffix indicates
125 | // top/left, and the 1 suffix indicates bottom/right.
126 | frac00, frac01, frac10, frac11 float64
127 | }
128 |
129 | func findLinearSrc(b image.Rectangle, sx, sy float64) bilinearSrc {
130 | maxX := float64(b.Max.X)
131 | maxY := float64(b.Max.Y)
132 | minX := float64(b.Min.X)
133 | minY := float64(b.Min.Y)
134 | lowX := math.Floor(sx - 0.5)
135 | lowY := math.Floor(sy - 0.5)
136 | if lowX < minX {
137 | lowX = minX
138 | }
139 | if lowY < minY {
140 | lowY = minY
141 | }
142 |
143 | highX := math.Ceil(sx - 0.5)
144 | highY := math.Ceil(sy - 0.5)
145 | if highX >= maxX {
146 | highX = maxX - 1
147 | }
148 | if highY >= maxY {
149 | highY = maxY - 1
150 | }
151 |
152 | // In the variables below, the 0 suffix indicates top/left, and the
153 | // 1 suffix indicates bottom/right.
154 |
155 | // Center of each surrounding pixel.
156 | x00 := lowX + 0.5
157 | y00 := lowY + 0.5
158 | x01 := highX + 0.5
159 | y01 := lowY + 0.5
160 | x10 := lowX + 0.5
161 | y10 := highY + 0.5
162 | x11 := highX + 0.5
163 | y11 := highY + 0.5
164 |
165 | p := bilinearSrc{
166 | low: image.Pt(int(lowX), int(lowY)),
167 | high: image.Pt(int(highX), int(highY)),
168 | }
169 |
170 | // Literally, edge cases. If we are close enough to the edge of
171 | // the image, curtail the interpolation sources.
172 | if lowX == highX && lowY == highY {
173 | p.frac00 = 1.0
174 | } else if sy-minY <= 0.5 && sx-minX <= 0.5 {
175 | p.frac00 = 1.0
176 | } else if maxY-sy <= 0.5 && maxX-sx <= 0.5 {
177 | p.frac11 = 1.0
178 | } else if sy-minY <= 0.5 || lowY == highY {
179 | p.frac00 = x01 - sx
180 | p.frac01 = sx - x00
181 | } else if sx-minX <= 0.5 || lowX == highX {
182 | p.frac00 = y10 - sy
183 | p.frac10 = sy - y00
184 | } else if maxY-sy <= 0.5 {
185 | p.frac10 = x11 - sx
186 | p.frac11 = sx - x10
187 | } else if maxX-sx <= 0.5 {
188 | p.frac01 = y11 - sy
189 | p.frac11 = sy - y01
190 | } else {
191 | p.frac00 = (x01 - sx) * (y10 - sy)
192 | p.frac01 = (sx - x00) * (y11 - sy)
193 | p.frac10 = (x11 - sx) * (sy - y00)
194 | p.frac11 = (sx - x10) * (sy - y01)
195 | }
196 |
197 | return p
198 | }
199 |
200 | // TODO(crawshaw): When we have inlining, consider func (p *RGBA) Off(x, y) int
201 | func offRGBA(src *image.RGBA, x, y int) int {
202 | return (y-src.Rect.Min.Y)*src.Stride + (x-src.Rect.Min.X)*4
203 | }
204 | func offGray(src *image.Gray, x, y int) int {
205 | return (y-src.Rect.Min.Y)*src.Stride + (x - src.Rect.Min.X)
206 | }
207 |
--------------------------------------------------------------------------------
/graphics/interp/bilinear_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2012 The Graphics-Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package interp
6 |
7 | import (
8 | "image"
9 | "image/color"
10 | "testing"
11 | )
12 |
13 | type interpTest struct {
14 | desc string
15 | src []uint8
16 | srcWidth int
17 | x, y float64
18 | expect uint8
19 | }
20 |
21 | func (p *interpTest) newSrc() *image.RGBA {
22 | b := image.Rect(0, 0, p.srcWidth, len(p.src)/p.srcWidth)
23 | src := image.NewRGBA(b)
24 | i := 0
25 | for y := b.Min.Y; y < b.Max.Y; y++ {
26 | for x := b.Min.X; x < b.Max.X; x++ {
27 | src.SetRGBA(x, y, color.RGBA{
28 | R: p.src[i],
29 | G: p.src[i],
30 | B: p.src[i],
31 | A: 0xff,
32 | })
33 | i++
34 | }
35 | }
36 | return src
37 | }
38 |
39 | var interpTests = []interpTest{
40 | {
41 | desc: "center of a single white pixel should match that pixel",
42 | src: []uint8{0x00},
43 | srcWidth: 1,
44 | x: 0.5,
45 | y: 0.5,
46 | expect: 0x00,
47 | },
48 | {
49 | desc: "middle of a square is equally weighted",
50 | src: []uint8{
51 | 0x00, 0xff,
52 | 0xff, 0x00,
53 | },
54 | srcWidth: 2,
55 | x: 1.0,
56 | y: 1.0,
57 | expect: 0x80,
58 | },
59 | {
60 | desc: "center of a pixel is just that pixel",
61 | src: []uint8{
62 | 0x00, 0xff,
63 | 0xff, 0x00,
64 | },
65 | srcWidth: 2,
66 | x: 1.5,
67 | y: 0.5,
68 | expect: 0xff,
69 | },
70 | {
71 | desc: "asymmetry abounds",
72 | src: []uint8{
73 | 0xaa, 0x11, 0x55,
74 | 0xff, 0x95, 0xdd,
75 | },
76 | srcWidth: 3,
77 | x: 2.0,
78 | y: 1.0,
79 | expect: 0x76, // (0x11 + 0x55 + 0x95 + 0xdd) / 4
80 | },
81 | }
82 |
83 | func TestBilinearRGBA(t *testing.T) {
84 | for _, p := range interpTests {
85 | src := p.newSrc()
86 |
87 | // Fast path.
88 | c := Bilinear.(RGBA).RGBA(src, p.x, p.y)
89 | if c.R != c.G || c.R != c.B || c.A != 0xff {
90 | t.Errorf("expect channels to match, got %v", c)
91 | continue
92 | }
93 | if c.R != p.expect {
94 | t.Errorf("%s: got 0x%02x want 0x%02x", p.desc, c.R, p.expect)
95 | continue
96 | }
97 |
98 | // Standard Interp should use the fast path.
99 | cStd := Bilinear.Interp(src, p.x, p.y)
100 | if cStd != c {
101 | t.Errorf("%s: standard mismatch got %v want %v", p.desc, cStd, c)
102 | continue
103 | }
104 |
105 | // General case should match the fast path.
106 | cGen := color.RGBAModel.Convert(bilinearGeneral(src, p.x, p.y))
107 | r0, g0, b0, a0 := c.RGBA()
108 | r1, g1, b1, a1 := cGen.RGBA()
109 | if r0 != r1 || g0 != g1 || b0 != b1 || a0 != a1 {
110 | t.Errorf("%s: general case mismatch got %v want %v", p.desc, c, cGen)
111 | continue
112 | }
113 | }
114 | }
115 |
116 | func TestBilinearSubImage(t *testing.T) {
117 | b0 := image.Rect(0, 0, 4, 4)
118 | src0 := image.NewRGBA(b0)
119 | b1 := image.Rect(1, 1, 3, 3)
120 | src1 := src0.SubImage(b1).(*image.RGBA)
121 | src1.Set(1, 1, color.RGBA{0x11, 0, 0, 0xff})
122 | src1.Set(2, 1, color.RGBA{0x22, 0, 0, 0xff})
123 | src1.Set(1, 2, color.RGBA{0x33, 0, 0, 0xff})
124 | src1.Set(2, 2, color.RGBA{0x44, 0, 0, 0xff})
125 |
126 | tests := []struct {
127 | x, y float64
128 | want uint8
129 | }{
130 | {1, 1, 0x11},
131 | {3, 1, 0x22},
132 | {1, 3, 0x33},
133 | {3, 3, 0x44},
134 | {2, 2, 0x2b},
135 | }
136 |
137 | for _, p := range tests {
138 | c := Bilinear.(RGBA).RGBA(src1, p.x, p.y)
139 | if c.R != p.want {
140 | t.Errorf("(%.0f, %.0f): got 0x%02x want 0x%02x", p.x, p.y, c.R, p.want)
141 | }
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/graphics/interp/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright 2012 The Graphics-Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | /*
6 | Package interp implements image interpolation.
7 |
8 | An interpolator provides the Interp interface, which can be used
9 | to interpolate a pixel:
10 |
11 | c := interp.Bilinear.Interp(src, 1.2, 1.8)
12 |
13 | To interpolate a large number of RGBA or Gray pixels, an implementation
14 | may provide a fast-path by implementing the RGBA or Gray interfaces.
15 |
16 | i1, ok := i.(interp.RGBA)
17 | if ok {
18 | c := i1.RGBA(src, 1.2, 1.8)
19 | // use c.R, c.G, etc
20 | return
21 | }
22 | c := i.Interp(src, 1.2, 1.8)
23 | // use generic color.Color
24 | */
25 | package interp
26 |
--------------------------------------------------------------------------------
/graphics/interp/interp.go:
--------------------------------------------------------------------------------
1 | // Copyright 2012 The Graphics-Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package interp
6 |
7 | import (
8 | "image"
9 | "image/color"
10 | )
11 |
12 | // Interp interpolates an image's color at fractional co-ordinates.
13 | type Interp interface {
14 | // Interp interpolates (x, y).
15 | Interp(src image.Image, x, y float64) color.Color
16 | }
17 |
18 | // RGBA is a fast-path interpolation implementation for image.RGBA.
19 | // It is common for an Interp to also implement RGBA.
20 | type RGBA interface {
21 | // RGBA interpolates (x, y).
22 | RGBA(src *image.RGBA, x, y float64) color.RGBA
23 | }
24 |
25 | // Gray is a fast-path interpolation implementation for image.Gray.
26 | type Gray interface {
27 | // Gray interpolates (x, y).
28 | Gray(src *image.Gray, x, y float64) color.Gray
29 | }
30 |
--------------------------------------------------------------------------------
/graphics/rotate.go:
--------------------------------------------------------------------------------
1 | // Copyright 2011 The Graphics-Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package graphics
6 |
7 | import (
8 | "github.com/hunterhug/go_image/graphics/interp"
9 | "errors"
10 | "image"
11 | "image/draw"
12 | )
13 |
14 | // RotateOptions are the rotation parameters.
15 | // Angle is the angle, in radians, to rotate the image clockwise.
16 | type RotateOptions struct {
17 | Angle float64
18 | }
19 |
20 | // Rotate produces a rotated version of src, drawn onto dst.
21 | func Rotate(dst draw.Image, src image.Image, opt *RotateOptions) error {
22 | if dst == nil {
23 | return errors.New("graphics: dst is nil")
24 | }
25 | if src == nil {
26 | return errors.New("graphics: src is nil")
27 | }
28 |
29 | angle := 0.0
30 | if opt != nil {
31 | angle = opt.Angle
32 | }
33 |
34 | return I.Rotate(angle).TransformCenter(dst, src, interp.Bilinear)
35 | }
36 |
--------------------------------------------------------------------------------
/graphics/rotate_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2011 The Graphics-Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package graphics
6 |
7 | import (
8 | "github.com/hunterhug/go_image/graphics/graphicstest"
9 | "image"
10 | "math"
11 | "testing"
12 |
13 | _ "image/png"
14 | )
15 |
16 | var rotateOneColorTests = []transformOneColorTest{
17 | {
18 | "onepixel-onequarter", 1, 1, 1, 1,
19 | &RotateOptions{math.Pi / 2},
20 | []uint8{0xff},
21 | []uint8{0xff},
22 | },
23 | {
24 | "onepixel-partial", 1, 1, 1, 1,
25 | &RotateOptions{math.Pi * 2.0 / 3.0},
26 | []uint8{0xff},
27 | []uint8{0xff},
28 | },
29 | {
30 | "onepixel-complete", 1, 1, 1, 1,
31 | &RotateOptions{2 * math.Pi},
32 | []uint8{0xff},
33 | []uint8{0xff},
34 | },
35 | {
36 | "even-onequarter", 2, 2, 2, 2,
37 | &RotateOptions{math.Pi / 2.0},
38 | []uint8{
39 | 0xff, 0x00,
40 | 0x00, 0xff,
41 | },
42 | []uint8{
43 | 0x00, 0xff,
44 | 0xff, 0x00,
45 | },
46 | },
47 | {
48 | "even-complete", 2, 2, 2, 2,
49 | &RotateOptions{2.0 * math.Pi},
50 | []uint8{
51 | 0xff, 0x00,
52 | 0x00, 0xff,
53 | },
54 | []uint8{
55 | 0xff, 0x00,
56 | 0x00, 0xff,
57 | },
58 | },
59 | {
60 | "line-partial", 3, 3, 3, 3,
61 | &RotateOptions{math.Pi * 1.0 / 3.0},
62 | []uint8{
63 | 0x00, 0x00, 0x00,
64 | 0xff, 0xff, 0xff,
65 | 0x00, 0x00, 0x00,
66 | },
67 | []uint8{
68 | 0xa2, 0x80, 0x00,
69 | 0x22, 0xff, 0x22,
70 | 0x00, 0x80, 0xa2,
71 | },
72 | },
73 | {
74 | "line-offset-partial", 3, 3, 3, 3,
75 | &RotateOptions{math.Pi * 3 / 2},
76 | []uint8{
77 | 0x00, 0x00, 0x00,
78 | 0x00, 0xff, 0xff,
79 | 0x00, 0x00, 0x00,
80 | },
81 | []uint8{
82 | 0x00, 0xff, 0x00,
83 | 0x00, 0xff, 0x00,
84 | 0x00, 0x00, 0x00,
85 | },
86 | },
87 | {
88 | "dot-partial", 4, 4, 4, 4,
89 | &RotateOptions{math.Pi},
90 | []uint8{
91 | 0x00, 0x00, 0x00, 0x00,
92 | 0x00, 0xff, 0x00, 0x00,
93 | 0x00, 0x00, 0x00, 0x00,
94 | 0x00, 0x00, 0x00, 0x00,
95 | },
96 | []uint8{
97 | 0x00, 0x00, 0x00, 0x00,
98 | 0x00, 0x00, 0x00, 0x00,
99 | 0x00, 0x00, 0xff, 0x00,
100 | 0x00, 0x00, 0x00, 0x00,
101 | },
102 | },
103 | }
104 |
105 | func TestRotateOneColor(t *testing.T) {
106 | for _, oc := range rotateOneColorTests {
107 | src := oc.newSrc()
108 | dst := oc.newDst()
109 |
110 | if err := Rotate(dst, src, oc.opt.(*RotateOptions)); err != nil {
111 | t.Errorf("rotate %s: %v", oc.desc, err)
112 | continue
113 | }
114 | if !checkTransformTest(t, &oc, dst) {
115 | continue
116 | }
117 | }
118 | }
119 |
120 | func TestRotateEmpty(t *testing.T) {
121 | empty := image.NewRGBA(image.Rect(0, 0, 0, 0))
122 | if err := Rotate(empty, empty, nil); err != nil {
123 | t.Fatal(err)
124 | }
125 | }
126 |
127 | func TestRotateGopherSide(t *testing.T) {
128 | src, err := graphicstest.LoadImage("../testdata/gopher.png")
129 | if err != nil {
130 | t.Fatal(err)
131 | }
132 |
133 | srcb := src.Bounds()
134 | dst := image.NewRGBA(image.Rect(0, 0, srcb.Dy(), srcb.Dx()))
135 | if err := Rotate(dst, src, &RotateOptions{math.Pi / 2.0}); err != nil {
136 | t.Fatal(err)
137 | }
138 |
139 | cmp, err := graphicstest.LoadImage("../testdata/gopher-rotate-side.png")
140 | if err != nil {
141 | t.Fatal(err)
142 | }
143 | err = graphicstest.ImageWithinTolerance(dst, cmp, 0x101)
144 | if err != nil {
145 | t.Fatal(err)
146 | }
147 | }
148 |
149 | func TestRotateGopherPartial(t *testing.T) {
150 | src, err := graphicstest.LoadImage("../testdata/gopher.png")
151 | if err != nil {
152 | t.Fatal(err)
153 | }
154 |
155 | srcb := src.Bounds()
156 | dst := image.NewRGBA(image.Rect(0, 0, srcb.Dx(), srcb.Dy()))
157 | if err := Rotate(dst, src, &RotateOptions{math.Pi / 3.0}); err != nil {
158 | t.Fatal(err)
159 | }
160 |
161 | cmp, err := graphicstest.LoadImage("../testdata/gopher-rotate-partial.png")
162 | if err != nil {
163 | t.Fatal(err)
164 | }
165 | err = graphicstest.ImageWithinTolerance(dst, cmp, 0x101)
166 | if err != nil {
167 | t.Fatal(err)
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/graphics/scale.go:
--------------------------------------------------------------------------------
1 | // Copyright 2011 The Graphics-Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package graphics
6 |
7 | import (
8 | "github.com/hunterhug/go_image/graphics/interp"
9 | "errors"
10 | "image"
11 | "image/draw"
12 | )
13 |
14 | // Scale produces a scaled version of the image using bilinear interpolation.
15 | func Scale(dst draw.Image, src image.Image) error {
16 | if dst == nil {
17 | return errors.New("graphics: dst is nil")
18 | }
19 | if src == nil {
20 | return errors.New("graphics: src is nil")
21 | }
22 |
23 | b := dst.Bounds()
24 | srcb := src.Bounds()
25 | if b.Empty() || srcb.Empty() {
26 | return nil
27 | }
28 | sx := float64(b.Dx()) / float64(srcb.Dx())
29 | sy := float64(b.Dy()) / float64(srcb.Dy())
30 | return I.Scale(sx, sy).Transform(dst, src, interp.Bilinear)
31 | }
32 |
--------------------------------------------------------------------------------
/graphics/scale_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2011 The Graphics-Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package graphics
6 |
7 | import (
8 | "github.com/hunterhug/go_image/graphics/graphicstest"
9 | "image"
10 | "testing"
11 |
12 | _ "image/png"
13 | )
14 |
15 | var scaleOneColorTests = []transformOneColorTest{
16 | {
17 | "down-half",
18 | 1, 1,
19 | 2, 2,
20 | nil,
21 | []uint8{
22 | 0x80, 0x00,
23 | 0x00, 0x80,
24 | },
25 | []uint8{
26 | 0x40,
27 | },
28 | },
29 | {
30 | "up-double",
31 | 4, 4,
32 | 2, 2,
33 | nil,
34 | []uint8{
35 | 0x80, 0x00,
36 | 0x00, 0x80,
37 | },
38 | []uint8{
39 | 0x80, 0x60, 0x20, 0x00,
40 | 0x60, 0x50, 0x30, 0x20,
41 | 0x20, 0x30, 0x50, 0x60,
42 | 0x00, 0x20, 0x60, 0x80,
43 | },
44 | },
45 | {
46 | "up-doublewidth",
47 | 4, 2,
48 | 2, 2,
49 | nil,
50 | []uint8{
51 | 0x80, 0x00,
52 | 0x00, 0x80,
53 | },
54 | []uint8{
55 | 0x80, 0x60, 0x20, 0x00,
56 | 0x00, 0x20, 0x60, 0x80,
57 | },
58 | },
59 | {
60 | "up-doubleheight",
61 | 2, 4,
62 | 2, 2,
63 | nil,
64 | []uint8{
65 | 0x80, 0x00,
66 | 0x00, 0x80,
67 | },
68 | []uint8{
69 | 0x80, 0x00,
70 | 0x60, 0x20,
71 | 0x20, 0x60,
72 | 0x00, 0x80,
73 | },
74 | },
75 | {
76 | "up-partial",
77 | 3, 3,
78 | 2, 2,
79 | nil,
80 | []uint8{
81 | 0x80, 0x00,
82 | 0x00, 0x80,
83 | },
84 | []uint8{
85 | 0x80, 0x40, 0x00,
86 | 0x40, 0x40, 0x40,
87 | 0x00, 0x40, 0x80,
88 | },
89 | },
90 | }
91 |
92 | func TestScaleOneColor(t *testing.T) {
93 | for _, oc := range scaleOneColorTests {
94 | dst := oc.newDst()
95 | src := oc.newSrc()
96 | if err := Scale(dst, src); err != nil {
97 | t.Errorf("scale %s: %v", oc.desc, err)
98 | continue
99 | }
100 |
101 | if !checkTransformTest(t, &oc, dst) {
102 | continue
103 | }
104 | }
105 | }
106 |
107 | func TestScaleEmpty(t *testing.T) {
108 | empty := image.NewRGBA(image.Rect(0, 0, 0, 0))
109 | if err := Scale(empty, empty); err != nil {
110 | t.Fatal(err)
111 | }
112 | }
113 |
114 | func TestScaleGopher(t *testing.T) {
115 | dst := image.NewRGBA(image.Rect(0, 0, 100, 150))
116 |
117 | src, err := graphicstest.LoadImage("../testdata/gopher.png")
118 | if err != nil {
119 | t.Error(err)
120 | return
121 | }
122 |
123 | // Down-sample.
124 | if err := Scale(dst, src); err != nil {
125 | t.Fatal(err)
126 | }
127 | cmp, err := graphicstest.LoadImage("../testdata/gopher-100x150.png")
128 | if err != nil {
129 | t.Error(err)
130 | return
131 | }
132 | err = graphicstest.ImageWithinTolerance(dst, cmp, 0)
133 | if err != nil {
134 | t.Error(err)
135 | return
136 | }
137 |
138 | // Up-sample.
139 | dst = image.NewRGBA(image.Rect(0, 0, 500, 750))
140 | if err := Scale(dst, src); err != nil {
141 | t.Fatal(err)
142 | }
143 | cmp, err = graphicstest.LoadImage("../testdata/gopher-500x750.png")
144 | if err != nil {
145 | t.Error(err)
146 | return
147 | }
148 | err = graphicstest.ImageWithinTolerance(dst, cmp, 0)
149 | if err != nil {
150 | t.Error(err)
151 | return
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/graphics/shared_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2011 The Graphics-Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package graphics
6 |
7 | import (
8 | "bytes"
9 | "github.com/hunterhug/go_image/graphics/graphicstest"
10 | "image"
11 | "image/color"
12 | "testing"
13 | )
14 |
15 | type transformOneColorTest struct {
16 | desc string
17 | dstWidth int
18 | dstHeight int
19 | srcWidth int
20 | srcHeight int
21 | opt interface{}
22 | src []uint8
23 | res []uint8
24 | }
25 |
26 | func (oc *transformOneColorTest) newSrc() *image.RGBA {
27 | b := image.Rect(0, 0, oc.srcWidth, oc.srcHeight)
28 | src := image.NewRGBA(b)
29 | i := 0
30 | for y := b.Min.Y; y < b.Max.Y; y++ {
31 | for x := b.Min.X; x < b.Max.X; x++ {
32 | src.SetRGBA(x, y, color.RGBA{
33 | R: oc.src[i],
34 | G: oc.src[i],
35 | B: oc.src[i],
36 | A: oc.src[i],
37 | })
38 | i++
39 | }
40 | }
41 | return src
42 | }
43 |
44 | func (oc *transformOneColorTest) newDst() *image.RGBA {
45 | return image.NewRGBA(image.Rect(0, 0, oc.dstWidth, oc.dstHeight))
46 | }
47 |
48 | func checkTransformTest(t *testing.T, oc *transformOneColorTest, dst *image.RGBA) bool {
49 | for ch := 0; ch < 4; ch++ {
50 | i := 0
51 | res := make([]byte, len(oc.res))
52 | for y := 0; y < oc.dstHeight; y++ {
53 | for x := 0; x < oc.dstWidth; x++ {
54 | off := (y-dst.Rect.Min.Y)*dst.Stride + (x-dst.Rect.Min.X)*4
55 | res[i] = dst.Pix[off+ch]
56 | i++
57 | }
58 | }
59 |
60 | if !bytes.Equal(res, oc.res) {
61 | got := graphicstest.SprintBox(res, oc.dstWidth, oc.dstHeight)
62 | want := graphicstest.SprintBox(oc.res, oc.dstWidth, oc.dstHeight)
63 | t.Errorf("%s: ch=%d\n got\n%s\n want\n%s", oc.desc, ch, got, want)
64 | return false
65 | }
66 | }
67 |
68 | return true
69 | }
70 |
--------------------------------------------------------------------------------
/graphics/thumbnail.go:
--------------------------------------------------------------------------------
1 | // Copyright 2011 The Graphics-Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package graphics
6 |
7 | import (
8 | "image"
9 | "image/draw"
10 | )
11 |
12 | // Thumbnail scales and crops src so it fits in dst.
13 | func Thumbnail(dst draw.Image, src image.Image) error {
14 | // Scale down src in the dimension that is closer to dst.
15 | sb := src.Bounds()
16 | db := dst.Bounds()
17 | rx := float64(sb.Dx()) / float64(db.Dx())
18 | ry := float64(sb.Dy()) / float64(db.Dy())
19 | var b image.Rectangle
20 | if rx < ry {
21 | b = image.Rect(0, 0, db.Dx(), int(float64(sb.Dy())/rx))
22 | } else {
23 | b = image.Rect(0, 0, int(float64(sb.Dx())/ry), db.Dy())
24 | }
25 |
26 | buf := image.NewRGBA(b)
27 | if err := Scale(buf, src); err != nil {
28 | return err
29 | }
30 |
31 | // Crop.
32 | // TODO(crawshaw): improve on center-alignment.
33 | var pt image.Point
34 | if rx < ry {
35 | pt.Y = (b.Dy() - db.Dy()) / 2
36 | } else {
37 | pt.X = (b.Dx() - db.Dx()) / 2
38 | }
39 | draw.Draw(dst, db, buf, pt, draw.Src)
40 | return nil
41 | }
42 |
--------------------------------------------------------------------------------
/graphics/thumbnail_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2011 The Graphics-Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package graphics
6 |
7 | import (
8 | "github.com/hunterhug/go_image/graphics/graphicstest"
9 | "image"
10 | "testing"
11 |
12 | _ "image/png"
13 | )
14 |
15 | func TestThumbnailGopher(t *testing.T) {
16 | dst := image.NewRGBA(image.Rect(0, 0, 80, 80))
17 |
18 | src, err := graphicstest.LoadImage("../testdata/gopher.png")
19 | if err != nil {
20 | t.Fatal(err)
21 | }
22 | if err := Thumbnail(dst, src); err != nil {
23 | t.Fatal(err)
24 | }
25 | cmp, err := graphicstest.LoadImage("../testdata/gopher-thumb-80x80.png")
26 | if err != nil {
27 | t.Fatal(err)
28 | }
29 | err = graphicstest.ImageWithinTolerance(dst, cmp, 0)
30 | if err != nil {
31 | t.Error(err)
32 | }
33 | }
34 |
35 | func TestThumbnailLongGopher(t *testing.T) {
36 | dst := image.NewRGBA(image.Rect(0, 0, 50, 150))
37 |
38 | src, err := graphicstest.LoadImage("../testdata/gopher.png")
39 | if err != nil {
40 | t.Fatal(err)
41 | }
42 | if err := Thumbnail(dst, src); err != nil {
43 | t.Fatal(err)
44 | }
45 | cmp, err := graphicstest.LoadImage("../testdata/gopher-thumb-50x150.png")
46 | if err != nil {
47 | t.Fatal(err)
48 | }
49 | err = graphicstest.ImageWithinTolerance(dst, cmp, 0)
50 | if err != nil {
51 | t.Error(err)
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/imageF2F.go:
--------------------------------------------------------------------------------
1 | package go_image
2 |
3 | import (
4 | "bytes"
5 | //"errors"
6 | "github.com/hunterhug/go_image/graphics"
7 | "image"
8 | "image/jpeg"
9 | "image/png"
10 | "io"
11 | "io/ioutil"
12 | "os"
13 | "path/filepath"
14 | "runtime"
15 | "strings"
16 | )
17 |
18 | //按宽度和高度进行比例缩放,输入和输出都是图片字节数组
19 | func ThumbnailB2B(InRaw []byte, width int, height int) (OutRaw []byte, err error) {
20 | src, filetype, err := LoadImage(bytes.NewReader(InRaw))
21 | if err != nil {
22 | return
23 | }
24 | dst := image.NewRGBA(image.Rect(0, 0, width, height))
25 | err = graphics.Thumbnail(dst, src)
26 | if err != nil {
27 | return
28 | }
29 |
30 | w := new(bytes.Buffer)
31 | err = SaveImage(dst, filetype, w)
32 | if err != nil {
33 | return
34 | }
35 |
36 | OutRaw = w.Bytes()
37 | return
38 | }
39 |
40 | // 按宽度和高度进行比例缩放,输入和输出都是文件
41 | func ThumbnailF2F(filename string, savepath string, width int, height int) (err error) {
42 | raw, err := ioutil.ReadFile(filename)
43 | if err != nil {
44 | return err
45 | }
46 |
47 | out, err := ThumbnailB2B(raw, width, height)
48 | if err != nil {
49 | return err
50 | }
51 |
52 | err = ioutil.WriteFile(savepath, out, 0777)
53 | if err != nil {
54 | return err
55 | }
56 |
57 | return
58 | }
59 |
60 | //按宽度进行比例缩放,输入和输出都是图片字节数组
61 | func ScaleB2B(InRaw []byte, width int) (OutRaw []byte, err error) {
62 | img, filetype, err := Scale(InRaw, width)
63 | if err != nil {
64 | return
65 | }
66 |
67 | buffer := new(bytes.Buffer)
68 | err = SaveImage(img, filetype, buffer)
69 | if err != nil {
70 | return
71 | }
72 |
73 | OutRaw = buffer.Bytes()
74 | return
75 | }
76 |
77 | //按宽度进行比例缩放,输入输出都是文件
78 | func ScaleF2F(filename string, savepath string, width int) (err error) {
79 | raw, err := ioutil.ReadFile(filename)
80 | if err != nil {
81 | return err
82 | }
83 |
84 | out, err := ScaleB2B(raw, width)
85 | if err != nil {
86 | return err
87 | }
88 |
89 | err = ioutil.WriteFile(savepath, out, 0777)
90 | if err != nil {
91 | return err
92 | }
93 |
94 | return
95 | }
96 |
97 | //图像文件的真正名字
98 | func RealImageName(filename string) (filerealname string, err error) {
99 | raw, err := ioutil.ReadFile(filename)
100 | if err != nil {
101 | return "", err
102 | }
103 |
104 | _, ext, err := LoadImage(bytes.NewReader(raw))
105 | if err != nil {
106 | return
107 | }
108 | temp := strings.Split(filename, ".")
109 | if len(temp) < 2 {
110 | err = FileNameError
111 | }
112 | temp[len(temp)-1] = ext
113 | filerealname = strings.Join(temp, ".")
114 | return
115 | }
116 |
117 | //文件改名,如果force为假,且新的文件名已经存在,那么抛出错误
118 | func ChangeImageName(oldname string, newname string, force bool) (err error) {
119 | if !force {
120 | _, err = os.Open(newname)
121 | if err == nil {
122 | err = FileExistError
123 | return
124 | }
125 | }
126 | err = os.Rename(oldname, newname)
127 | return
128 |
129 | }
130 |
131 | // 获取调用者的当前文件DIR
132 | func CurDir() string {
133 | _, filename, _, _ := runtime.Caller(1)
134 | return filepath.Dir(filename)
135 | }
136 |
137 | // 根据文件名打开图片,并编码,返回编码对象和文件类型
138 | func LoadImage(r io.Reader) (img image.Image, filetype string, err error) {
139 | img, filetype, err = image.Decode(r)
140 | if err != nil {
141 | }
142 | return
143 | }
144 |
145 | // 将编码对象进行处理后返回字节数组
146 | func SaveImage(img *image.RGBA, filetype string, w io.Writer) (err error) {
147 | if filetype == "png" {
148 | err = png.Encode(w, img)
149 | } else if filetype == "jpeg" {
150 | err = jpeg.Encode(w, img, nil)
151 | } else {
152 | err = ExtNotSupportError
153 | }
154 | return
155 | }
156 |
157 | //对图片字节数组等比例变化,宽度为newdx,返回图像编码和文件类型
158 | func Scale(raw []byte, newdx int) (dst *image.RGBA, filetype string, err error) {
159 | src, filetype, err := LoadImage(bytes.NewReader(raw))
160 | if err != nil {
161 | return
162 | }
163 | bound := src.Bounds()
164 | dx := bound.Dx()
165 | dy := bound.Dy()
166 | dst = image.NewRGBA(image.Rect(0, 0, newdx, newdx*dy/dx))
167 | // 产生缩略图,等比例缩放
168 | err = graphics.Scale(dst, src)
169 | return
170 | }
171 |
--------------------------------------------------------------------------------
/lib/codereview/codereview.cfg:
--------------------------------------------------------------------------------
1 | defaultcc: golang-dev@googlegroups.com
2 |
--------------------------------------------------------------------------------
/testdata/gopher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hunterhug/go_image/50c2408b96bfdbdef2267201993c641dd8c397fe/testdata/gopher.png
--------------------------------------------------------------------------------
/testdata/gopher500.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hunterhug/go_image/50c2408b96bfdbdef2267201993c641dd8c397fe/testdata/gopher500.png
--------------------------------------------------------------------------------
/testdata/gopher500_800.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hunterhug/go_image/50c2408b96bfdbdef2267201993c641dd8c397fe/testdata/gopher500_800.png
--------------------------------------------------------------------------------