ires 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +20 -0
- data/README.md +111 -0
- data/Rakefile +30 -0
- data/ext/Gopkg.lock +21 -0
- data/ext/Gopkg.toml +30 -0
- data/ext/main.go +90 -0
- data/ext/operate/image.go +139 -0
- data/ext/util/uri/uri.go +96 -0
- data/ext/vendor/github.com/nfnt/resize/LICENSE +13 -0
- data/ext/vendor/github.com/nfnt/resize/README.md +149 -0
- data/ext/vendor/github.com/nfnt/resize/converter.go +438 -0
- data/ext/vendor/github.com/nfnt/resize/converter_test.go +43 -0
- data/ext/vendor/github.com/nfnt/resize/filters.go +143 -0
- data/ext/vendor/github.com/nfnt/resize/nearest.go +318 -0
- data/ext/vendor/github.com/nfnt/resize/nearest_test.go +57 -0
- data/ext/vendor/github.com/nfnt/resize/resize.go +614 -0
- data/ext/vendor/github.com/nfnt/resize/resize_test.go +330 -0
- data/ext/vendor/github.com/nfnt/resize/thumbnail.go +55 -0
- data/ext/vendor/github.com/nfnt/resize/thumbnail_test.go +47 -0
- data/ext/vendor/github.com/nfnt/resize/ycc.go +227 -0
- data/ext/vendor/github.com/nfnt/resize/ycc_test.go +214 -0
- data/ext/vendor/github.com/oliamb/cutter/LICENSE +20 -0
- data/ext/vendor/github.com/oliamb/cutter/README.md +88 -0
- data/ext/vendor/github.com/oliamb/cutter/benchmark_test.go +54 -0
- data/ext/vendor/github.com/oliamb/cutter/cutter.go +183 -0
- data/ext/vendor/github.com/oliamb/cutter/cutter/main.go +68 -0
- data/ext/vendor/github.com/oliamb/cutter/cutter_test.go +267 -0
- data/ext/vendor/github.com/oliamb/cutter/example_test.go +35 -0
- data/ext/vendor/github.com/oliamb/cutter/fixtures/gopher.jpg +0 -0
- data/lib/ires.rb +4 -0
- data/lib/ires/engine.rb +7 -0
- data/lib/ires/service.rb +19 -0
- data/lib/ires/util.rb +39 -0
- data/lib/ires/version.rb +3 -0
- data/lib/ires/view_helper.rb +42 -0
- data/lib/tasks/ires.rake +11 -0
- data/shared/darwin/ires.h +64 -0
- data/shared/darwin/ires.so +0 -0
- data/shared/linux/ires.h +64 -0
- data/shared/linux/ires.so +0 -0
- metadata +154 -0
@@ -0,0 +1,330 @@
|
|
1
|
+
package resize
|
2
|
+
|
3
|
+
import (
|
4
|
+
"image"
|
5
|
+
"image/color"
|
6
|
+
"runtime"
|
7
|
+
"testing"
|
8
|
+
)
|
9
|
+
|
10
|
+
var img = image.NewGray16(image.Rect(0, 0, 3, 3))
|
11
|
+
|
12
|
+
func init() {
|
13
|
+
runtime.GOMAXPROCS(runtime.NumCPU())
|
14
|
+
img.Set(1, 1, color.White)
|
15
|
+
}
|
16
|
+
|
17
|
+
func Test_Param1(t *testing.T) {
|
18
|
+
m := Resize(0, 0, img, NearestNeighbor)
|
19
|
+
if m.Bounds() != img.Bounds() {
|
20
|
+
t.Fail()
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
func Test_Param2(t *testing.T) {
|
25
|
+
m := Resize(100, 0, img, NearestNeighbor)
|
26
|
+
if m.Bounds() != image.Rect(0, 0, 100, 100) {
|
27
|
+
t.Fail()
|
28
|
+
}
|
29
|
+
}
|
30
|
+
|
31
|
+
func Test_ZeroImg(t *testing.T) {
|
32
|
+
zeroImg := image.NewGray16(image.Rect(0, 0, 0, 0))
|
33
|
+
|
34
|
+
m := Resize(0, 0, zeroImg, NearestNeighbor)
|
35
|
+
if m.Bounds() != zeroImg.Bounds() {
|
36
|
+
t.Fail()
|
37
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
func Test_CorrectResize(t *testing.T) {
|
41
|
+
zeroImg := image.NewGray16(image.Rect(0, 0, 256, 256))
|
42
|
+
|
43
|
+
m := Resize(60, 0, zeroImg, NearestNeighbor)
|
44
|
+
if m.Bounds() != image.Rect(0, 0, 60, 60) {
|
45
|
+
t.Fail()
|
46
|
+
}
|
47
|
+
}
|
48
|
+
|
49
|
+
func Test_SameColorWithRGBA(t *testing.T) {
|
50
|
+
img := image.NewRGBA(image.Rect(0, 0, 20, 20))
|
51
|
+
for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ {
|
52
|
+
for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ {
|
53
|
+
img.SetRGBA(x, y, color.RGBA{0x80, 0x80, 0x80, 0xFF})
|
54
|
+
}
|
55
|
+
}
|
56
|
+
out := Resize(10, 10, img, Lanczos3)
|
57
|
+
for y := out.Bounds().Min.Y; y < out.Bounds().Max.Y; y++ {
|
58
|
+
for x := out.Bounds().Min.X; x < out.Bounds().Max.X; x++ {
|
59
|
+
color := out.At(x, y).(color.RGBA)
|
60
|
+
if color.R != 0x80 || color.G != 0x80 || color.B != 0x80 || color.A != 0xFF {
|
61
|
+
t.Errorf("%+v", color)
|
62
|
+
}
|
63
|
+
}
|
64
|
+
}
|
65
|
+
}
|
66
|
+
|
67
|
+
func Test_SameColorWithNRGBA(t *testing.T) {
|
68
|
+
img := image.NewNRGBA(image.Rect(0, 0, 20, 20))
|
69
|
+
for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ {
|
70
|
+
for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ {
|
71
|
+
img.SetNRGBA(x, y, color.NRGBA{0x80, 0x80, 0x80, 0xFF})
|
72
|
+
}
|
73
|
+
}
|
74
|
+
out := Resize(10, 10, img, Lanczos3)
|
75
|
+
for y := out.Bounds().Min.Y; y < out.Bounds().Max.Y; y++ {
|
76
|
+
for x := out.Bounds().Min.X; x < out.Bounds().Max.X; x++ {
|
77
|
+
color := out.At(x, y).(color.RGBA)
|
78
|
+
if color.R != 0x80 || color.G != 0x80 || color.B != 0x80 || color.A != 0xFF {
|
79
|
+
t.Errorf("%+v", color)
|
80
|
+
}
|
81
|
+
}
|
82
|
+
}
|
83
|
+
}
|
84
|
+
|
85
|
+
func Test_SameColorWithRGBA64(t *testing.T) {
|
86
|
+
img := image.NewRGBA64(image.Rect(0, 0, 20, 20))
|
87
|
+
for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ {
|
88
|
+
for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ {
|
89
|
+
img.SetRGBA64(x, y, color.RGBA64{0x8000, 0x8000, 0x8000, 0xFFFF})
|
90
|
+
}
|
91
|
+
}
|
92
|
+
out := Resize(10, 10, img, Lanczos3)
|
93
|
+
for y := out.Bounds().Min.Y; y < out.Bounds().Max.Y; y++ {
|
94
|
+
for x := out.Bounds().Min.X; x < out.Bounds().Max.X; x++ {
|
95
|
+
color := out.At(x, y).(color.RGBA64)
|
96
|
+
if color.R != 0x8000 || color.G != 0x8000 || color.B != 0x8000 || color.A != 0xFFFF {
|
97
|
+
t.Errorf("%+v", color)
|
98
|
+
}
|
99
|
+
}
|
100
|
+
}
|
101
|
+
}
|
102
|
+
|
103
|
+
func Test_SameColorWithNRGBA64(t *testing.T) {
|
104
|
+
img := image.NewNRGBA64(image.Rect(0, 0, 20, 20))
|
105
|
+
for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ {
|
106
|
+
for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ {
|
107
|
+
img.SetNRGBA64(x, y, color.NRGBA64{0x8000, 0x8000, 0x8000, 0xFFFF})
|
108
|
+
}
|
109
|
+
}
|
110
|
+
out := Resize(10, 10, img, Lanczos3)
|
111
|
+
for y := out.Bounds().Min.Y; y < out.Bounds().Max.Y; y++ {
|
112
|
+
for x := out.Bounds().Min.X; x < out.Bounds().Max.X; x++ {
|
113
|
+
color := out.At(x, y).(color.RGBA64)
|
114
|
+
if color.R != 0x8000 || color.G != 0x8000 || color.B != 0x8000 || color.A != 0xFFFF {
|
115
|
+
t.Errorf("%+v", color)
|
116
|
+
}
|
117
|
+
}
|
118
|
+
}
|
119
|
+
}
|
120
|
+
|
121
|
+
func Test_SameColorWithGray(t *testing.T) {
|
122
|
+
img := image.NewGray(image.Rect(0, 0, 20, 20))
|
123
|
+
for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ {
|
124
|
+
for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ {
|
125
|
+
img.SetGray(x, y, color.Gray{0x80})
|
126
|
+
}
|
127
|
+
}
|
128
|
+
out := Resize(10, 10, img, Lanczos3)
|
129
|
+
for y := out.Bounds().Min.Y; y < out.Bounds().Max.Y; y++ {
|
130
|
+
for x := out.Bounds().Min.X; x < out.Bounds().Max.X; x++ {
|
131
|
+
color := out.At(x, y).(color.Gray)
|
132
|
+
if color.Y != 0x80 {
|
133
|
+
t.Errorf("%+v", color)
|
134
|
+
}
|
135
|
+
}
|
136
|
+
}
|
137
|
+
}
|
138
|
+
|
139
|
+
func Test_SameColorWithGray16(t *testing.T) {
|
140
|
+
img := image.NewGray16(image.Rect(0, 0, 20, 20))
|
141
|
+
for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ {
|
142
|
+
for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ {
|
143
|
+
img.SetGray16(x, y, color.Gray16{0x8000})
|
144
|
+
}
|
145
|
+
}
|
146
|
+
out := Resize(10, 10, img, Lanczos3)
|
147
|
+
for y := out.Bounds().Min.Y; y < out.Bounds().Max.Y; y++ {
|
148
|
+
for x := out.Bounds().Min.X; x < out.Bounds().Max.X; x++ {
|
149
|
+
color := out.At(x, y).(color.Gray16)
|
150
|
+
if color.Y != 0x8000 {
|
151
|
+
t.Errorf("%+v", color)
|
152
|
+
}
|
153
|
+
}
|
154
|
+
}
|
155
|
+
}
|
156
|
+
|
157
|
+
func Test_Bounds(t *testing.T) {
|
158
|
+
img := image.NewRGBA(image.Rect(20, 10, 200, 99))
|
159
|
+
out := Resize(80, 80, img, Lanczos2)
|
160
|
+
out.At(0, 0)
|
161
|
+
}
|
162
|
+
|
163
|
+
func Test_SameSizeReturnsOriginal(t *testing.T) {
|
164
|
+
img := image.NewRGBA(image.Rect(0, 0, 10, 10))
|
165
|
+
out := Resize(0, 0, img, Lanczos2)
|
166
|
+
|
167
|
+
if img != out {
|
168
|
+
t.Fail()
|
169
|
+
}
|
170
|
+
|
171
|
+
out = Resize(10, 10, img, Lanczos2)
|
172
|
+
|
173
|
+
if img != out {
|
174
|
+
t.Fail()
|
175
|
+
}
|
176
|
+
}
|
177
|
+
|
178
|
+
func Test_PixelCoordinates(t *testing.T) {
|
179
|
+
checkers := image.NewGray(image.Rect(0, 0, 4, 4))
|
180
|
+
checkers.Pix = []uint8{
|
181
|
+
255, 0, 255, 0,
|
182
|
+
0, 255, 0, 255,
|
183
|
+
255, 0, 255, 0,
|
184
|
+
0, 255, 0, 255,
|
185
|
+
}
|
186
|
+
|
187
|
+
resized := Resize(12, 12, checkers, NearestNeighbor).(*image.Gray)
|
188
|
+
|
189
|
+
if resized.Pix[0] != 255 || resized.Pix[1] != 255 || resized.Pix[2] != 255 {
|
190
|
+
t.Fail()
|
191
|
+
}
|
192
|
+
|
193
|
+
if resized.Pix[3] != 0 || resized.Pix[4] != 0 || resized.Pix[5] != 0 {
|
194
|
+
t.Fail()
|
195
|
+
}
|
196
|
+
}
|
197
|
+
|
198
|
+
func Test_ResizeWithPremultipliedAlpha(t *testing.T) {
|
199
|
+
img := image.NewRGBA(image.Rect(0, 0, 1, 4))
|
200
|
+
for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ {
|
201
|
+
// 0x80 = 0.5 * 0xFF.
|
202
|
+
img.SetRGBA(0, y, color.RGBA{0x80, 0x80, 0x80, 0x80})
|
203
|
+
}
|
204
|
+
|
205
|
+
out := Resize(1, 2, img, MitchellNetravali)
|
206
|
+
|
207
|
+
outputColor := out.At(0, 0).(color.RGBA)
|
208
|
+
if outputColor.R != 0x80 {
|
209
|
+
t.Fail()
|
210
|
+
}
|
211
|
+
}
|
212
|
+
|
213
|
+
func Test_ResizeWithTranslucentColor(t *testing.T) {
|
214
|
+
img := image.NewNRGBA(image.Rect(0, 0, 1, 2))
|
215
|
+
|
216
|
+
// Set the pixel colors to an "invisible green" and white.
|
217
|
+
// After resizing, the green shouldn't be visible.
|
218
|
+
img.SetNRGBA(0, 0, color.NRGBA{0x00, 0xFF, 0x00, 0x00})
|
219
|
+
img.SetNRGBA(0, 1, color.NRGBA{0x00, 0x00, 0x00, 0xFF})
|
220
|
+
|
221
|
+
out := Resize(1, 1, img, Bilinear)
|
222
|
+
|
223
|
+
_, g, _, _ := out.At(0, 0).RGBA()
|
224
|
+
if g != 0x00 {
|
225
|
+
t.Errorf("%+v", g)
|
226
|
+
}
|
227
|
+
}
|
228
|
+
|
229
|
+
const (
|
230
|
+
// Use a small image size for benchmarks. We don't want memory performance
|
231
|
+
// to affect the benchmark results.
|
232
|
+
benchMaxX = 250
|
233
|
+
benchMaxY = 250
|
234
|
+
|
235
|
+
// Resize values near the original size require increase the amount of time
|
236
|
+
// resize spends converting the image.
|
237
|
+
benchWidth = 200
|
238
|
+
benchHeight = 200
|
239
|
+
)
|
240
|
+
|
241
|
+
func benchRGBA(b *testing.B, interp InterpolationFunction) {
|
242
|
+
m := image.NewRGBA(image.Rect(0, 0, benchMaxX, benchMaxY))
|
243
|
+
// Initialize m's pixels to create a non-uniform image.
|
244
|
+
for y := m.Rect.Min.Y; y < m.Rect.Max.Y; y++ {
|
245
|
+
for x := m.Rect.Min.X; x < m.Rect.Max.X; x++ {
|
246
|
+
i := m.PixOffset(x, y)
|
247
|
+
m.Pix[i+0] = uint8(y + 4*x)
|
248
|
+
m.Pix[i+1] = uint8(y + 4*x)
|
249
|
+
m.Pix[i+2] = uint8(y + 4*x)
|
250
|
+
m.Pix[i+3] = uint8(4*y + x)
|
251
|
+
}
|
252
|
+
}
|
253
|
+
|
254
|
+
var out image.Image
|
255
|
+
b.ResetTimer()
|
256
|
+
for i := 0; i < b.N; i++ {
|
257
|
+
out = Resize(benchWidth, benchHeight, m, interp)
|
258
|
+
}
|
259
|
+
out.At(0, 0)
|
260
|
+
}
|
261
|
+
|
262
|
+
// The names of some interpolation functions are truncated so that the columns
|
263
|
+
// of 'go test -bench' line up.
|
264
|
+
func Benchmark_Nearest_RGBA(b *testing.B) {
|
265
|
+
benchRGBA(b, NearestNeighbor)
|
266
|
+
}
|
267
|
+
|
268
|
+
func Benchmark_Bilinear_RGBA(b *testing.B) {
|
269
|
+
benchRGBA(b, Bilinear)
|
270
|
+
}
|
271
|
+
|
272
|
+
func Benchmark_Bicubic_RGBA(b *testing.B) {
|
273
|
+
benchRGBA(b, Bicubic)
|
274
|
+
}
|
275
|
+
|
276
|
+
func Benchmark_Mitchell_RGBA(b *testing.B) {
|
277
|
+
benchRGBA(b, MitchellNetravali)
|
278
|
+
}
|
279
|
+
|
280
|
+
func Benchmark_Lanczos2_RGBA(b *testing.B) {
|
281
|
+
benchRGBA(b, Lanczos2)
|
282
|
+
}
|
283
|
+
|
284
|
+
func Benchmark_Lanczos3_RGBA(b *testing.B) {
|
285
|
+
benchRGBA(b, Lanczos3)
|
286
|
+
}
|
287
|
+
|
288
|
+
func benchYCbCr(b *testing.B, interp InterpolationFunction) {
|
289
|
+
m := image.NewYCbCr(image.Rect(0, 0, benchMaxX, benchMaxY), image.YCbCrSubsampleRatio422)
|
290
|
+
// Initialize m's pixels to create a non-uniform image.
|
291
|
+
for y := m.Rect.Min.Y; y < m.Rect.Max.Y; y++ {
|
292
|
+
for x := m.Rect.Min.X; x < m.Rect.Max.X; x++ {
|
293
|
+
yi := m.YOffset(x, y)
|
294
|
+
ci := m.COffset(x, y)
|
295
|
+
m.Y[yi] = uint8(16*y + x)
|
296
|
+
m.Cb[ci] = uint8(y + 16*x)
|
297
|
+
m.Cr[ci] = uint8(y + 16*x)
|
298
|
+
}
|
299
|
+
}
|
300
|
+
var out image.Image
|
301
|
+
b.ResetTimer()
|
302
|
+
for i := 0; i < b.N; i++ {
|
303
|
+
out = Resize(benchWidth, benchHeight, m, interp)
|
304
|
+
}
|
305
|
+
out.At(0, 0)
|
306
|
+
}
|
307
|
+
|
308
|
+
func Benchmark_Nearest_YCC(b *testing.B) {
|
309
|
+
benchYCbCr(b, NearestNeighbor)
|
310
|
+
}
|
311
|
+
|
312
|
+
func Benchmark_Bilinear_YCC(b *testing.B) {
|
313
|
+
benchYCbCr(b, Bilinear)
|
314
|
+
}
|
315
|
+
|
316
|
+
func Benchmark_Bicubic_YCC(b *testing.B) {
|
317
|
+
benchYCbCr(b, Bicubic)
|
318
|
+
}
|
319
|
+
|
320
|
+
func Benchmark_Mitchell_YCC(b *testing.B) {
|
321
|
+
benchYCbCr(b, MitchellNetravali)
|
322
|
+
}
|
323
|
+
|
324
|
+
func Benchmark_Lanczos2_YCC(b *testing.B) {
|
325
|
+
benchYCbCr(b, Lanczos2)
|
326
|
+
}
|
327
|
+
|
328
|
+
func Benchmark_Lanczos3_YCC(b *testing.B) {
|
329
|
+
benchYCbCr(b, Lanczos3)
|
330
|
+
}
|
@@ -0,0 +1,55 @@
|
|
1
|
+
/*
|
2
|
+
Copyright (c) 2012, Jan Schlicht <jan.schlicht@gmail.com>
|
3
|
+
|
4
|
+
Permission to use, copy, modify, and/or distribute this software for any purpose
|
5
|
+
with or without fee is hereby granted, provided that the above copyright notice
|
6
|
+
and this permission notice appear in all copies.
|
7
|
+
|
8
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
9
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
10
|
+
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
11
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
12
|
+
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
13
|
+
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
14
|
+
THIS SOFTWARE.
|
15
|
+
*/
|
16
|
+
|
17
|
+
package resize
|
18
|
+
|
19
|
+
import (
|
20
|
+
"image"
|
21
|
+
)
|
22
|
+
|
23
|
+
// Thumbnail will downscale provided image to max width and height preserving
|
24
|
+
// original aspect ratio and using the interpolation function interp.
|
25
|
+
// It will return original image, without processing it, if original sizes
|
26
|
+
// are already smaller than provided constraints.
|
27
|
+
func Thumbnail(maxWidth, maxHeight uint, img image.Image, interp InterpolationFunction) image.Image {
|
28
|
+
origBounds := img.Bounds()
|
29
|
+
origWidth := uint(origBounds.Dx())
|
30
|
+
origHeight := uint(origBounds.Dy())
|
31
|
+
newWidth, newHeight := origWidth, origHeight
|
32
|
+
|
33
|
+
// Return original image if it have same or smaller size as constraints
|
34
|
+
if maxWidth >= origWidth && maxHeight >= origHeight {
|
35
|
+
return img
|
36
|
+
}
|
37
|
+
|
38
|
+
// Preserve aspect ratio
|
39
|
+
if origWidth > maxWidth {
|
40
|
+
newHeight = uint(origHeight * maxWidth / origWidth)
|
41
|
+
if newHeight < 1 {
|
42
|
+
newHeight = 1
|
43
|
+
}
|
44
|
+
newWidth = maxWidth
|
45
|
+
}
|
46
|
+
|
47
|
+
if newHeight > maxHeight {
|
48
|
+
newWidth = uint(newWidth * maxHeight / newHeight)
|
49
|
+
if newWidth < 1 {
|
50
|
+
newWidth = 1
|
51
|
+
}
|
52
|
+
newHeight = maxHeight
|
53
|
+
}
|
54
|
+
return Resize(newWidth, newHeight, img, interp)
|
55
|
+
}
|
@@ -0,0 +1,47 @@
|
|
1
|
+
package resize
|
2
|
+
|
3
|
+
import (
|
4
|
+
"image"
|
5
|
+
"runtime"
|
6
|
+
"testing"
|
7
|
+
)
|
8
|
+
|
9
|
+
func init() {
|
10
|
+
runtime.GOMAXPROCS(runtime.NumCPU())
|
11
|
+
}
|
12
|
+
|
13
|
+
var thumbnailTests = []struct {
|
14
|
+
origWidth int
|
15
|
+
origHeight int
|
16
|
+
maxWidth uint
|
17
|
+
maxHeight uint
|
18
|
+
expectedWidth uint
|
19
|
+
expectedHeight uint
|
20
|
+
}{
|
21
|
+
{5, 5, 10, 10, 5, 5},
|
22
|
+
{10, 10, 5, 5, 5, 5},
|
23
|
+
{10, 50, 10, 10, 2, 10},
|
24
|
+
{50, 10, 10, 10, 10, 2},
|
25
|
+
{50, 100, 60, 90, 45, 90},
|
26
|
+
{120, 100, 60, 90, 60, 50},
|
27
|
+
{200, 250, 200, 150, 120, 150},
|
28
|
+
}
|
29
|
+
|
30
|
+
func TestThumbnail(t *testing.T) {
|
31
|
+
for i, tt := range thumbnailTests {
|
32
|
+
img := image.NewGray16(image.Rect(0, 0, tt.origWidth, tt.origHeight))
|
33
|
+
|
34
|
+
outImg := Thumbnail(tt.maxWidth, tt.maxHeight, img, NearestNeighbor)
|
35
|
+
|
36
|
+
newWidth := uint(outImg.Bounds().Dx())
|
37
|
+
newHeight := uint(outImg.Bounds().Dy())
|
38
|
+
if newWidth != tt.expectedWidth ||
|
39
|
+
newHeight != tt.expectedHeight {
|
40
|
+
t.Errorf("%d. Thumbnail(%v, %v, img, NearestNeighbor) => "+
|
41
|
+
"width: %v, height: %v, want width: %v, height: %v",
|
42
|
+
i, tt.maxWidth, tt.maxHeight,
|
43
|
+
newWidth, newHeight, tt.expectedWidth, tt.expectedHeight,
|
44
|
+
)
|
45
|
+
}
|
46
|
+
}
|
47
|
+
}
|
@@ -0,0 +1,227 @@
|
|
1
|
+
/*
|
2
|
+
Copyright (c) 2014, Charlie Vieth <charlie.vieth@gmail.com>
|
3
|
+
|
4
|
+
Permission to use, copy, modify, and/or distribute this software for any purpose
|
5
|
+
with or without fee is hereby granted, provided that the above copyright notice
|
6
|
+
and this permission notice appear in all copies.
|
7
|
+
|
8
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
9
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
10
|
+
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
11
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
12
|
+
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
13
|
+
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
14
|
+
THIS SOFTWARE.
|
15
|
+
*/
|
16
|
+
|
17
|
+
package resize
|
18
|
+
|
19
|
+
import (
|
20
|
+
"image"
|
21
|
+
"image/color"
|
22
|
+
)
|
23
|
+
|
24
|
+
// ycc is an in memory YCbCr image. The Y, Cb and Cr samples are held in a
|
25
|
+
// single slice to increase resizing performance.
|
26
|
+
type ycc struct {
|
27
|
+
// Pix holds the image's pixels, in Y, Cb, Cr order. The pixel at
|
28
|
+
// (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*3].
|
29
|
+
Pix []uint8
|
30
|
+
// Stride is the Pix stride (in bytes) between vertically adjacent pixels.
|
31
|
+
Stride int
|
32
|
+
// Rect is the image's bounds.
|
33
|
+
Rect image.Rectangle
|
34
|
+
// SubsampleRatio is the subsample ratio of the original YCbCr image.
|
35
|
+
SubsampleRatio image.YCbCrSubsampleRatio
|
36
|
+
}
|
37
|
+
|
38
|
+
// PixOffset returns the index of the first element of Pix that corresponds to
|
39
|
+
// the pixel at (x, y).
|
40
|
+
func (p *ycc) PixOffset(x, y int) int {
|
41
|
+
return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*3
|
42
|
+
}
|
43
|
+
|
44
|
+
func (p *ycc) Bounds() image.Rectangle {
|
45
|
+
return p.Rect
|
46
|
+
}
|
47
|
+
|
48
|
+
func (p *ycc) ColorModel() color.Model {
|
49
|
+
return color.YCbCrModel
|
50
|
+
}
|
51
|
+
|
52
|
+
func (p *ycc) At(x, y int) color.Color {
|
53
|
+
if !(image.Point{x, y}.In(p.Rect)) {
|
54
|
+
return color.YCbCr{}
|
55
|
+
}
|
56
|
+
i := p.PixOffset(x, y)
|
57
|
+
return color.YCbCr{
|
58
|
+
p.Pix[i+0],
|
59
|
+
p.Pix[i+1],
|
60
|
+
p.Pix[i+2],
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
func (p *ycc) Opaque() bool {
|
65
|
+
return true
|
66
|
+
}
|
67
|
+
|
68
|
+
// SubImage returns an image representing the portion of the image p visible
|
69
|
+
// through r. The returned value shares pixels with the original image.
|
70
|
+
func (p *ycc) SubImage(r image.Rectangle) image.Image {
|
71
|
+
r = r.Intersect(p.Rect)
|
72
|
+
if r.Empty() {
|
73
|
+
return &ycc{SubsampleRatio: p.SubsampleRatio}
|
74
|
+
}
|
75
|
+
i := p.PixOffset(r.Min.X, r.Min.Y)
|
76
|
+
return &ycc{
|
77
|
+
Pix: p.Pix[i:],
|
78
|
+
Stride: p.Stride,
|
79
|
+
Rect: r,
|
80
|
+
SubsampleRatio: p.SubsampleRatio,
|
81
|
+
}
|
82
|
+
}
|
83
|
+
|
84
|
+
// newYCC returns a new ycc with the given bounds and subsample ratio.
|
85
|
+
func newYCC(r image.Rectangle, s image.YCbCrSubsampleRatio) *ycc {
|
86
|
+
w, h := r.Dx(), r.Dy()
|
87
|
+
buf := make([]uint8, 3*w*h)
|
88
|
+
return &ycc{Pix: buf, Stride: 3 * w, Rect: r, SubsampleRatio: s}
|
89
|
+
}
|
90
|
+
|
91
|
+
// YCbCr converts ycc to a YCbCr image with the same subsample ratio
|
92
|
+
// as the YCbCr image that ycc was generated from.
|
93
|
+
func (p *ycc) YCbCr() *image.YCbCr {
|
94
|
+
ycbcr := image.NewYCbCr(p.Rect, p.SubsampleRatio)
|
95
|
+
var off int
|
96
|
+
|
97
|
+
switch ycbcr.SubsampleRatio {
|
98
|
+
case image.YCbCrSubsampleRatio422:
|
99
|
+
for y := ycbcr.Rect.Min.Y; y < ycbcr.Rect.Max.Y; y++ {
|
100
|
+
yy := (y - ycbcr.Rect.Min.Y) * ycbcr.YStride
|
101
|
+
cy := (y - ycbcr.Rect.Min.Y) * ycbcr.CStride
|
102
|
+
for x := ycbcr.Rect.Min.X; x < ycbcr.Rect.Max.X; x++ {
|
103
|
+
xx := (x - ycbcr.Rect.Min.X)
|
104
|
+
yi := yy + xx
|
105
|
+
ci := cy + xx/2
|
106
|
+
ycbcr.Y[yi] = p.Pix[off+0]
|
107
|
+
ycbcr.Cb[ci] = p.Pix[off+1]
|
108
|
+
ycbcr.Cr[ci] = p.Pix[off+2]
|
109
|
+
off += 3
|
110
|
+
}
|
111
|
+
}
|
112
|
+
case image.YCbCrSubsampleRatio420:
|
113
|
+
for y := ycbcr.Rect.Min.Y; y < ycbcr.Rect.Max.Y; y++ {
|
114
|
+
yy := (y - ycbcr.Rect.Min.Y) * ycbcr.YStride
|
115
|
+
cy := (y/2 - ycbcr.Rect.Min.Y/2) * ycbcr.CStride
|
116
|
+
for x := ycbcr.Rect.Min.X; x < ycbcr.Rect.Max.X; x++ {
|
117
|
+
xx := (x - ycbcr.Rect.Min.X)
|
118
|
+
yi := yy + xx
|
119
|
+
ci := cy + xx/2
|
120
|
+
ycbcr.Y[yi] = p.Pix[off+0]
|
121
|
+
ycbcr.Cb[ci] = p.Pix[off+1]
|
122
|
+
ycbcr.Cr[ci] = p.Pix[off+2]
|
123
|
+
off += 3
|
124
|
+
}
|
125
|
+
}
|
126
|
+
case image.YCbCrSubsampleRatio440:
|
127
|
+
for y := ycbcr.Rect.Min.Y; y < ycbcr.Rect.Max.Y; y++ {
|
128
|
+
yy := (y - ycbcr.Rect.Min.Y) * ycbcr.YStride
|
129
|
+
cy := (y/2 - ycbcr.Rect.Min.Y/2) * ycbcr.CStride
|
130
|
+
for x := ycbcr.Rect.Min.X; x < ycbcr.Rect.Max.X; x++ {
|
131
|
+
xx := (x - ycbcr.Rect.Min.X)
|
132
|
+
yi := yy + xx
|
133
|
+
ci := cy + xx
|
134
|
+
ycbcr.Y[yi] = p.Pix[off+0]
|
135
|
+
ycbcr.Cb[ci] = p.Pix[off+1]
|
136
|
+
ycbcr.Cr[ci] = p.Pix[off+2]
|
137
|
+
off += 3
|
138
|
+
}
|
139
|
+
}
|
140
|
+
default:
|
141
|
+
// Default to 4:4:4 subsampling.
|
142
|
+
for y := ycbcr.Rect.Min.Y; y < ycbcr.Rect.Max.Y; y++ {
|
143
|
+
yy := (y - ycbcr.Rect.Min.Y) * ycbcr.YStride
|
144
|
+
cy := (y - ycbcr.Rect.Min.Y) * ycbcr.CStride
|
145
|
+
for x := ycbcr.Rect.Min.X; x < ycbcr.Rect.Max.X; x++ {
|
146
|
+
xx := (x - ycbcr.Rect.Min.X)
|
147
|
+
yi := yy + xx
|
148
|
+
ci := cy + xx
|
149
|
+
ycbcr.Y[yi] = p.Pix[off+0]
|
150
|
+
ycbcr.Cb[ci] = p.Pix[off+1]
|
151
|
+
ycbcr.Cr[ci] = p.Pix[off+2]
|
152
|
+
off += 3
|
153
|
+
}
|
154
|
+
}
|
155
|
+
}
|
156
|
+
return ycbcr
|
157
|
+
}
|
158
|
+
|
159
|
+
// imageYCbCrToYCC converts a YCbCr image to a ycc image for resizing.
|
160
|
+
func imageYCbCrToYCC(in *image.YCbCr) *ycc {
|
161
|
+
w, h := in.Rect.Dx(), in.Rect.Dy()
|
162
|
+
r := image.Rect(0, 0, w, h)
|
163
|
+
buf := make([]uint8, 3*w*h)
|
164
|
+
p := ycc{Pix: buf, Stride: 3 * w, Rect: r, SubsampleRatio: in.SubsampleRatio}
|
165
|
+
var off int
|
166
|
+
|
167
|
+
switch in.SubsampleRatio {
|
168
|
+
case image.YCbCrSubsampleRatio422:
|
169
|
+
for y := in.Rect.Min.Y; y < in.Rect.Max.Y; y++ {
|
170
|
+
yy := (y - in.Rect.Min.Y) * in.YStride
|
171
|
+
cy := (y - in.Rect.Min.Y) * in.CStride
|
172
|
+
for x := in.Rect.Min.X; x < in.Rect.Max.X; x++ {
|
173
|
+
xx := (x - in.Rect.Min.X)
|
174
|
+
yi := yy + xx
|
175
|
+
ci := cy + xx/2
|
176
|
+
p.Pix[off+0] = in.Y[yi]
|
177
|
+
p.Pix[off+1] = in.Cb[ci]
|
178
|
+
p.Pix[off+2] = in.Cr[ci]
|
179
|
+
off += 3
|
180
|
+
}
|
181
|
+
}
|
182
|
+
case image.YCbCrSubsampleRatio420:
|
183
|
+
for y := in.Rect.Min.Y; y < in.Rect.Max.Y; y++ {
|
184
|
+
yy := (y - in.Rect.Min.Y) * in.YStride
|
185
|
+
cy := (y/2 - in.Rect.Min.Y/2) * in.CStride
|
186
|
+
for x := in.Rect.Min.X; x < in.Rect.Max.X; x++ {
|
187
|
+
xx := (x - in.Rect.Min.X)
|
188
|
+
yi := yy + xx
|
189
|
+
ci := cy + xx/2
|
190
|
+
p.Pix[off+0] = in.Y[yi]
|
191
|
+
p.Pix[off+1] = in.Cb[ci]
|
192
|
+
p.Pix[off+2] = in.Cr[ci]
|
193
|
+
off += 3
|
194
|
+
}
|
195
|
+
}
|
196
|
+
case image.YCbCrSubsampleRatio440:
|
197
|
+
for y := in.Rect.Min.Y; y < in.Rect.Max.Y; y++ {
|
198
|
+
yy := (y - in.Rect.Min.Y) * in.YStride
|
199
|
+
cy := (y/2 - in.Rect.Min.Y/2) * in.CStride
|
200
|
+
for x := in.Rect.Min.X; x < in.Rect.Max.X; x++ {
|
201
|
+
xx := (x - in.Rect.Min.X)
|
202
|
+
yi := yy + xx
|
203
|
+
ci := cy + xx
|
204
|
+
p.Pix[off+0] = in.Y[yi]
|
205
|
+
p.Pix[off+1] = in.Cb[ci]
|
206
|
+
p.Pix[off+2] = in.Cr[ci]
|
207
|
+
off += 3
|
208
|
+
}
|
209
|
+
}
|
210
|
+
default:
|
211
|
+
// Default to 4:4:4 subsampling.
|
212
|
+
for y := in.Rect.Min.Y; y < in.Rect.Max.Y; y++ {
|
213
|
+
yy := (y - in.Rect.Min.Y) * in.YStride
|
214
|
+
cy := (y - in.Rect.Min.Y) * in.CStride
|
215
|
+
for x := in.Rect.Min.X; x < in.Rect.Max.X; x++ {
|
216
|
+
xx := (x - in.Rect.Min.X)
|
217
|
+
yi := yy + xx
|
218
|
+
ci := cy + xx
|
219
|
+
p.Pix[off+0] = in.Y[yi]
|
220
|
+
p.Pix[off+1] = in.Cb[ci]
|
221
|
+
p.Pix[off+2] = in.Cr[ci]
|
222
|
+
off += 3
|
223
|
+
}
|
224
|
+
}
|
225
|
+
}
|
226
|
+
return &p
|
227
|
+
}
|