forked from anthonynsimon/bild
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathresize.go
183 lines (147 loc) · 4.98 KB
/
resize.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
package transform
import (
"image"
"math"
"github.com/anthonynsimon/bild/clone"
"github.com/anthonynsimon/bild/math/f64"
"github.com/anthonynsimon/bild/parallel"
)
// Resize returns a new image with its size adjusted to the new width and height. The filter
// param corresponds to the Resampling Filter to be used when interpolating between the sample points.
//
//
// Usage example:
//
// result := bild.Resize(img, 800, 600, bild.Linear)
//
func Resize(img image.Image, width, height int, filter ResampleFilter) *image.RGBA {
if width <= 0 || height <= 0 || img.Bounds().Empty() {
return image.NewRGBA(image.Rect(0, 0, 0, 0))
}
src := clone.AsRGBA(img)
var dst *image.RGBA
// NearestNeighbor is a special case, it's faster to compute without convolution matrix.
if filter.Support <= 0 {
dst = nearestNeighbor(src, width, height)
} else {
dst = resampleHorizontal(src, width, filter)
dst = resampleVertical(dst, height, filter)
}
return dst
}
// Crop returns a new image which contains the intersection between the rect and the image provided as params.
// Only the intersection is returned. If a rect larger than the image is provided, no fill is done to
// the 'empty' area.
//
// Usage example:
//
// result := bild.Crop(img, image.Rect(0,0,512,256))
//
func Crop(img image.Image, rect image.Rectangle) *image.RGBA {
src := clone.AsRGBA(img)
return clone.AsRGBA(src.SubImage(rect))
}
func resampleHorizontal(src *image.RGBA, width int, filter ResampleFilter) *image.RGBA {
srcWidth, srcHeight := src.Bounds().Dx(), src.Bounds().Dy()
srcStride := src.Stride
delta := float64(srcWidth) / float64(width)
// Scale must be at least 1. Special case for image size reduction filter radius.
scale := math.Max(delta, 1.0)
dst := image.NewRGBA(image.Rect(0, 0, width, srcHeight))
dstStride := dst.Stride
filterRadius := math.Ceil(scale * filter.Support)
parallel.Line(srcHeight, func(start, end int) {
for y := start; y < end; y++ {
for x := 0; x < width; x++ {
// value of x from src
ix := (float64(x)+0.5)*delta - 0.5
istart, iend := int(ix-filterRadius+0.5), int(ix+filterRadius)
if istart < 0 {
istart = 0
}
if iend >= srcWidth {
iend = srcWidth - 1
}
var r, g, b, a float64
var sum float64
for kx := istart; kx <= iend; kx++ {
srcPos := y*srcStride + kx*4
// normalize the sample position to be evaluated by the filter
normPos := (float64(kx) - ix) / scale
fValue := filter.Fn(normPos)
r += float64(src.Pix[srcPos+0]) * fValue
g += float64(src.Pix[srcPos+1]) * fValue
b += float64(src.Pix[srcPos+2]) * fValue
a += float64(src.Pix[srcPos+3]) * fValue
sum += fValue
}
dstPos := y*dstStride + x*4
dst.Pix[dstPos+0] = uint8(f64.Clamp((r/sum)+0.5, 0, 255))
dst.Pix[dstPos+1] = uint8(f64.Clamp((g/sum)+0.5, 0, 255))
dst.Pix[dstPos+2] = uint8(f64.Clamp((b/sum)+0.5, 0, 255))
dst.Pix[dstPos+3] = uint8(f64.Clamp((a/sum)+0.5, 0, 255))
}
}
})
return dst
}
func resampleVertical(src *image.RGBA, height int, filter ResampleFilter) *image.RGBA {
srcWidth, srcHeight := src.Bounds().Dx(), src.Bounds().Dy()
srcStride := src.Stride
delta := float64(srcHeight) / float64(height)
scale := math.Max(delta, 1.0)
dst := image.NewRGBA(image.Rect(0, 0, srcWidth, height))
dstStride := dst.Stride
filterRadius := math.Ceil(scale * filter.Support)
parallel.Line(height, func(start, end int) {
for y := start; y < end; y++ {
iy := (float64(y)+0.5)*delta - 0.5
istart, iend := int(iy-filterRadius+0.5), int(iy+filterRadius)
if istart < 0 {
istart = 0
}
if iend >= srcHeight {
iend = srcHeight - 1
}
for x := 0; x < srcWidth; x++ {
var r, g, b, a float64
var sum float64
for ky := istart; ky <= iend; ky++ {
srcPos := ky*srcStride + x*4
normPos := (float64(ky) - iy) / scale
fValue := filter.Fn(normPos)
r += float64(src.Pix[srcPos+0]) * fValue
g += float64(src.Pix[srcPos+1]) * fValue
b += float64(src.Pix[srcPos+2]) * fValue
a += float64(src.Pix[srcPos+3]) * fValue
sum += fValue
}
dstPos := y*dstStride + x*4
dst.Pix[dstPos+0] = uint8(f64.Clamp((r/sum)+0.5, 0, 255))
dst.Pix[dstPos+1] = uint8(f64.Clamp((g/sum)+0.5, 0, 255))
dst.Pix[dstPos+2] = uint8(f64.Clamp((b/sum)+0.5, 0, 255))
dst.Pix[dstPos+3] = uint8(f64.Clamp((a/sum)+0.5, 0, 255))
}
}
})
return dst
}
func nearestNeighbor(src *image.RGBA, width, height int) *image.RGBA {
srcW, srcH := src.Bounds().Dx(), src.Bounds().Dy()
srcStride := src.Stride
dst := image.NewRGBA(image.Rect(0, 0, width, height))
dstStride := dst.Stride
dx := float64(srcW) / float64(width)
dy := float64(srcH) / float64(height)
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
pos := y*dstStride + x*4
ipos := int((float64(y)+0.5)*dy)*srcStride + int((float64(x)+0.5)*dx)*4
dst.Pix[pos+0] = src.Pix[ipos+0]
dst.Pix[pos+1] = src.Pix[ipos+1]
dst.Pix[pos+2] = src.Pix[ipos+2]
dst.Pix[pos+3] = src.Pix[ipos+3]
}
}
return dst
}