ires 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,214 @@
|
|
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
|
+
"testing"
|
23
|
+
)
|
24
|
+
|
25
|
+
type Image interface {
|
26
|
+
image.Image
|
27
|
+
SubImage(image.Rectangle) image.Image
|
28
|
+
}
|
29
|
+
|
30
|
+
func TestImage(t *testing.T) {
|
31
|
+
testImage := []Image{
|
32
|
+
newYCC(image.Rect(0, 0, 10, 10), image.YCbCrSubsampleRatio420),
|
33
|
+
newYCC(image.Rect(0, 0, 10, 10), image.YCbCrSubsampleRatio422),
|
34
|
+
newYCC(image.Rect(0, 0, 10, 10), image.YCbCrSubsampleRatio440),
|
35
|
+
newYCC(image.Rect(0, 0, 10, 10), image.YCbCrSubsampleRatio444),
|
36
|
+
}
|
37
|
+
for _, m := range testImage {
|
38
|
+
if !image.Rect(0, 0, 10, 10).Eq(m.Bounds()) {
|
39
|
+
t.Errorf("%T: want bounds %v, got %v",
|
40
|
+
m, image.Rect(0, 0, 10, 10), m.Bounds())
|
41
|
+
continue
|
42
|
+
}
|
43
|
+
m = m.SubImage(image.Rect(3, 2, 9, 8)).(Image)
|
44
|
+
if !image.Rect(3, 2, 9, 8).Eq(m.Bounds()) {
|
45
|
+
t.Errorf("%T: sub-image want bounds %v, got %v",
|
46
|
+
m, image.Rect(3, 2, 9, 8), m.Bounds())
|
47
|
+
continue
|
48
|
+
}
|
49
|
+
// Test that taking an empty sub-image starting at a corner does not panic.
|
50
|
+
m.SubImage(image.Rect(0, 0, 0, 0))
|
51
|
+
m.SubImage(image.Rect(10, 0, 10, 0))
|
52
|
+
m.SubImage(image.Rect(0, 10, 0, 10))
|
53
|
+
m.SubImage(image.Rect(10, 10, 10, 10))
|
54
|
+
}
|
55
|
+
}
|
56
|
+
|
57
|
+
func TestConvertYCbCr(t *testing.T) {
|
58
|
+
testImage := []Image{
|
59
|
+
image.NewYCbCr(image.Rect(0, 0, 50, 50), image.YCbCrSubsampleRatio420),
|
60
|
+
image.NewYCbCr(image.Rect(0, 0, 50, 50), image.YCbCrSubsampleRatio422),
|
61
|
+
image.NewYCbCr(image.Rect(0, 0, 50, 50), image.YCbCrSubsampleRatio440),
|
62
|
+
image.NewYCbCr(image.Rect(0, 0, 50, 50), image.YCbCrSubsampleRatio444),
|
63
|
+
}
|
64
|
+
|
65
|
+
for _, img := range testImage {
|
66
|
+
m := img.(*image.YCbCr)
|
67
|
+
for y := m.Rect.Min.Y; y < m.Rect.Max.Y; y++ {
|
68
|
+
for x := m.Rect.Min.X; x < m.Rect.Max.X; x++ {
|
69
|
+
yi := m.YOffset(x, y)
|
70
|
+
ci := m.COffset(x, y)
|
71
|
+
m.Y[yi] = uint8(16*y + x)
|
72
|
+
m.Cb[ci] = uint8(y + 16*x)
|
73
|
+
m.Cr[ci] = uint8(y + 16*x)
|
74
|
+
}
|
75
|
+
}
|
76
|
+
|
77
|
+
// test conversion from YCbCr to ycc
|
78
|
+
yc := imageYCbCrToYCC(m)
|
79
|
+
for y := m.Rect.Min.Y; y < m.Rect.Max.Y; y++ {
|
80
|
+
for x := m.Rect.Min.X; x < m.Rect.Max.X; x++ {
|
81
|
+
ystride := 3 * (m.Rect.Max.X - m.Rect.Min.X)
|
82
|
+
xstride := 3
|
83
|
+
yi := m.YOffset(x, y)
|
84
|
+
ci := m.COffset(x, y)
|
85
|
+
si := (y * ystride) + (x * xstride)
|
86
|
+
if m.Y[yi] != yc.Pix[si] {
|
87
|
+
t.Errorf("Err Y - found: %d expected: %d x: %d y: %d yi: %d si: %d",
|
88
|
+
m.Y[yi], yc.Pix[si], x, y, yi, si)
|
89
|
+
}
|
90
|
+
if m.Cb[ci] != yc.Pix[si+1] {
|
91
|
+
t.Errorf("Err Cb - found: %d expected: %d x: %d y: %d ci: %d si: %d",
|
92
|
+
m.Cb[ci], yc.Pix[si+1], x, y, ci, si+1)
|
93
|
+
}
|
94
|
+
if m.Cr[ci] != yc.Pix[si+2] {
|
95
|
+
t.Errorf("Err Cr - found: %d expected: %d x: %d y: %d ci: %d si: %d",
|
96
|
+
m.Cr[ci], yc.Pix[si+2], x, y, ci, si+2)
|
97
|
+
}
|
98
|
+
}
|
99
|
+
}
|
100
|
+
|
101
|
+
// test conversion from ycc back to YCbCr
|
102
|
+
ym := yc.YCbCr()
|
103
|
+
for y := m.Rect.Min.Y; y < m.Rect.Max.Y; y++ {
|
104
|
+
for x := m.Rect.Min.X; x < m.Rect.Max.X; x++ {
|
105
|
+
yi := m.YOffset(x, y)
|
106
|
+
ci := m.COffset(x, y)
|
107
|
+
if m.Y[yi] != ym.Y[yi] {
|
108
|
+
t.Errorf("Err Y - found: %d expected: %d x: %d y: %d yi: %d",
|
109
|
+
m.Y[yi], ym.Y[yi], x, y, yi)
|
110
|
+
}
|
111
|
+
if m.Cb[ci] != ym.Cb[ci] {
|
112
|
+
t.Errorf("Err Cb - found: %d expected: %d x: %d y: %d ci: %d",
|
113
|
+
m.Cb[ci], ym.Cb[ci], x, y, ci)
|
114
|
+
}
|
115
|
+
if m.Cr[ci] != ym.Cr[ci] {
|
116
|
+
t.Errorf("Err Cr - found: %d expected: %d x: %d y: %d ci: %d",
|
117
|
+
m.Cr[ci], ym.Cr[ci], x, y, ci)
|
118
|
+
}
|
119
|
+
}
|
120
|
+
}
|
121
|
+
}
|
122
|
+
}
|
123
|
+
|
124
|
+
func TestYCbCr(t *testing.T) {
|
125
|
+
rects := []image.Rectangle{
|
126
|
+
image.Rect(0, 0, 16, 16),
|
127
|
+
image.Rect(1, 0, 16, 16),
|
128
|
+
image.Rect(0, 1, 16, 16),
|
129
|
+
image.Rect(1, 1, 16, 16),
|
130
|
+
image.Rect(1, 1, 15, 16),
|
131
|
+
image.Rect(1, 1, 16, 15),
|
132
|
+
image.Rect(1, 1, 15, 15),
|
133
|
+
image.Rect(2, 3, 14, 15),
|
134
|
+
image.Rect(7, 0, 7, 16),
|
135
|
+
image.Rect(0, 8, 16, 8),
|
136
|
+
image.Rect(0, 0, 10, 11),
|
137
|
+
image.Rect(5, 6, 16, 16),
|
138
|
+
image.Rect(7, 7, 8, 8),
|
139
|
+
image.Rect(7, 8, 8, 9),
|
140
|
+
image.Rect(8, 7, 9, 8),
|
141
|
+
image.Rect(8, 8, 9, 9),
|
142
|
+
image.Rect(7, 7, 17, 17),
|
143
|
+
image.Rect(8, 8, 17, 17),
|
144
|
+
image.Rect(9, 9, 17, 17),
|
145
|
+
image.Rect(10, 10, 17, 17),
|
146
|
+
}
|
147
|
+
subsampleRatios := []image.YCbCrSubsampleRatio{
|
148
|
+
image.YCbCrSubsampleRatio444,
|
149
|
+
image.YCbCrSubsampleRatio422,
|
150
|
+
image.YCbCrSubsampleRatio420,
|
151
|
+
image.YCbCrSubsampleRatio440,
|
152
|
+
}
|
153
|
+
deltas := []image.Point{
|
154
|
+
image.Pt(0, 0),
|
155
|
+
image.Pt(1000, 1001),
|
156
|
+
image.Pt(5001, -400),
|
157
|
+
image.Pt(-701, -801),
|
158
|
+
}
|
159
|
+
for _, r := range rects {
|
160
|
+
for _, subsampleRatio := range subsampleRatios {
|
161
|
+
for _, delta := range deltas {
|
162
|
+
testYCbCr(t, r, subsampleRatio, delta)
|
163
|
+
}
|
164
|
+
}
|
165
|
+
if testing.Short() {
|
166
|
+
break
|
167
|
+
}
|
168
|
+
}
|
169
|
+
}
|
170
|
+
|
171
|
+
func testYCbCr(t *testing.T, r image.Rectangle, subsampleRatio image.YCbCrSubsampleRatio, delta image.Point) {
|
172
|
+
// Create a YCbCr image m, whose bounds are r translated by (delta.X, delta.Y).
|
173
|
+
r1 := r.Add(delta)
|
174
|
+
img := image.NewYCbCr(r1, subsampleRatio)
|
175
|
+
|
176
|
+
// Initialize img's pixels. For 422 and 420 subsampling, some of the Cb and Cr elements
|
177
|
+
// will be set multiple times. That's OK. We just want to avoid a uniform image.
|
178
|
+
for y := r1.Min.Y; y < r1.Max.Y; y++ {
|
179
|
+
for x := r1.Min.X; x < r1.Max.X; x++ {
|
180
|
+
yi := img.YOffset(x, y)
|
181
|
+
ci := img.COffset(x, y)
|
182
|
+
img.Y[yi] = uint8(16*y + x)
|
183
|
+
img.Cb[ci] = uint8(y + 16*x)
|
184
|
+
img.Cr[ci] = uint8(y + 16*x)
|
185
|
+
}
|
186
|
+
}
|
187
|
+
|
188
|
+
m := imageYCbCrToYCC(img)
|
189
|
+
|
190
|
+
// Make various sub-images of m.
|
191
|
+
for y0 := delta.Y + 3; y0 < delta.Y+7; y0++ {
|
192
|
+
for y1 := delta.Y + 8; y1 < delta.Y+13; y1++ {
|
193
|
+
for x0 := delta.X + 3; x0 < delta.X+7; x0++ {
|
194
|
+
for x1 := delta.X + 8; x1 < delta.X+13; x1++ {
|
195
|
+
subRect := image.Rect(x0, y0, x1, y1)
|
196
|
+
sub := m.SubImage(subRect).(*ycc)
|
197
|
+
|
198
|
+
// For each point in the sub-image's bounds, check that m.At(x, y) equals sub.At(x, y).
|
199
|
+
for y := sub.Rect.Min.Y; y < sub.Rect.Max.Y; y++ {
|
200
|
+
for x := sub.Rect.Min.X; x < sub.Rect.Max.X; x++ {
|
201
|
+
color0 := m.At(x, y).(color.YCbCr)
|
202
|
+
color1 := sub.At(x, y).(color.YCbCr)
|
203
|
+
if color0 != color1 {
|
204
|
+
t.Errorf("r=%v, subsampleRatio=%v, delta=%v, x=%d, y=%d, color0=%v, color1=%v",
|
205
|
+
r, subsampleRatio, delta, x, y, color0, color1)
|
206
|
+
return
|
207
|
+
}
|
208
|
+
}
|
209
|
+
}
|
210
|
+
}
|
211
|
+
}
|
212
|
+
}
|
213
|
+
}
|
214
|
+
}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 Olivier Amblet
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -0,0 +1,88 @@
|
|
1
|
+
Cutter
|
2
|
+
======
|
3
|
+
|
4
|
+
[![Build Status](https://travis-ci.org/oliamb/cutter.png?branch=master)](https://travis-ci.org/oliamb/cutter)
|
5
|
+
[![GoDoc](https://godoc.org/github.com/oliamb/cutter?status.png)](https://godoc.org/github.com/oliamb/cutter)
|
6
|
+
|
7
|
+
What is it?
|
8
|
+
-----------
|
9
|
+
A Go library to crop images.
|
10
|
+
|
11
|
+
Cutter was initially developped to be able
|
12
|
+
to crop image resized using github.com/nfnt/resize.
|
13
|
+
|
14
|
+
Usage
|
15
|
+
-----
|
16
|
+
|
17
|
+
Read the doc on https://godoc.org/github.com/oliamb/cutter
|
18
|
+
|
19
|
+
Import package with
|
20
|
+
|
21
|
+
import "github.com/oliamb/cutter"
|
22
|
+
|
23
|
+
Package cutter provides a function to crop image.
|
24
|
+
|
25
|
+
By default, the original image will be cropped at the
|
26
|
+
given size from the top left corner.
|
27
|
+
|
28
|
+
croppedImg, err := cutter.Crop(img, cutter.Config{
|
29
|
+
Width: 250,
|
30
|
+
Height: 500,
|
31
|
+
})
|
32
|
+
|
33
|
+
It is possible to specify the top left position:
|
34
|
+
|
35
|
+
croppedImg, err := cutter.Crop(img, cutter.Config{
|
36
|
+
Width: 250,
|
37
|
+
Height: 500,
|
38
|
+
Anchor: image.Point{100, 100},
|
39
|
+
Mode: TopLeft, // optional, default value
|
40
|
+
})
|
41
|
+
|
42
|
+
The Anchor property can represents the center of the cropped image
|
43
|
+
instead of the top left corner:
|
44
|
+
|
45
|
+
|
46
|
+
croppedImg, err := cutter.Crop(img, cutter.Config{
|
47
|
+
Width: 250,
|
48
|
+
Height: 500,
|
49
|
+
Mode: Centered,
|
50
|
+
})
|
51
|
+
|
52
|
+
The default crop use the specified dimension, but it is possible
|
53
|
+
to use Width and Heigth as a ratio instead. In this case,
|
54
|
+
the resulting image will be as big as possible to fit the asked ratio
|
55
|
+
from the anchor position.
|
56
|
+
|
57
|
+
croppedImg, err := cutter.Crop(baseImage, cutter.Config{
|
58
|
+
Width: 4,
|
59
|
+
Height: 3,
|
60
|
+
Mode: Centered,
|
61
|
+
Options: Ratio,
|
62
|
+
})
|
63
|
+
|
64
|
+
About resize
|
65
|
+
------------
|
66
|
+
This lib only manage crop and won't resize image, but it works great in combination with [github.com/nfnt/resize](https://github.com/nfnt/resize)
|
67
|
+
|
68
|
+
Contributing
|
69
|
+
------------
|
70
|
+
I'd love to see your contributions to Cutter. If you'd like to hack on it:
|
71
|
+
|
72
|
+
- fork the project,
|
73
|
+
- hack on it,
|
74
|
+
- ensure tests pass,
|
75
|
+
- make a pull request
|
76
|
+
|
77
|
+
If you plan to modify the API, let's disscuss it first.
|
78
|
+
|
79
|
+
Licensing
|
80
|
+
---------
|
81
|
+
MIT License, Please see the file called LICENSE.
|
82
|
+
|
83
|
+
Credits
|
84
|
+
-------
|
85
|
+
Test Picture: Gopher picture from Heidi Schuyt, http://www.flickr.com/photos/hschuyt/7674222278/,
|
86
|
+
© copyright Creative Commons(http://creativecommons.org/licenses/by-nc-sa/2.0/)
|
87
|
+
|
88
|
+
Thanks to Urturn(http://www.urturn.com) for the time allocated to develop the library.
|
@@ -0,0 +1,54 @@
|
|
1
|
+
package cutter
|
2
|
+
|
3
|
+
import (
|
4
|
+
"image"
|
5
|
+
"testing"
|
6
|
+
)
|
7
|
+
|
8
|
+
/*
|
9
|
+
BenchmarkCrop is used to track the Crop with sharing memory.
|
10
|
+
|
11
|
+
On my laptop, the required time is lower than 0.00 ns/op.
|
12
|
+
With a bigger, size it would probably not increase as
|
13
|
+
nothing is copied.
|
14
|
+
*/
|
15
|
+
func BenchmarkCrop(b *testing.B) {
|
16
|
+
img := getImage()
|
17
|
+
|
18
|
+
c := Config{
|
19
|
+
Width: 1000,
|
20
|
+
Height: 1000,
|
21
|
+
Mode: TopLeft,
|
22
|
+
Anchor: image.Point{100, 100},
|
23
|
+
}
|
24
|
+
b.ResetTimer()
|
25
|
+
r, _ := Crop(img, c)
|
26
|
+
r.Bounds()
|
27
|
+
}
|
28
|
+
|
29
|
+
/*
|
30
|
+
BenchmarkCropCopy is used to track the Crop with copy performance.
|
31
|
+
|
32
|
+
Below are the actual result on my laptop given each
|
33
|
+
optimization suggested by Nigel Tao: https://groups.google.com/forum/#!topic/golang-nuts/qxSpOOp1QOk
|
34
|
+
|
35
|
+
1. initial time on my Laptop: 23 ns/op
|
36
|
+
2. after inverting x and y in copy loop: 0.09 ns/op
|
37
|
+
3. after removing useless call to ColorModel().Convert(): 0.08 ns/op
|
38
|
+
4. after replacing the two 'pixel' loops by a call to draw.Draw
|
39
|
+
to obtains the cropped image: 0.04 ns/op
|
40
|
+
*/
|
41
|
+
func BenchmarkCropCopy(b *testing.B) {
|
42
|
+
img := getImage()
|
43
|
+
|
44
|
+
c := Config{
|
45
|
+
Width: 1000,
|
46
|
+
Height: 1000,
|
47
|
+
Mode: TopLeft,
|
48
|
+
Anchor: image.Point{100, 100},
|
49
|
+
Options: Copy,
|
50
|
+
}
|
51
|
+
b.ResetTimer()
|
52
|
+
r, _ := Crop(img, c)
|
53
|
+
r.Bounds()
|
54
|
+
}
|
@@ -0,0 +1,183 @@
|
|
1
|
+
/*
|
2
|
+
Package cutter provides a function to crop image.
|
3
|
+
|
4
|
+
By default, the original image will be cropped at the
|
5
|
+
given size from the top left corner.
|
6
|
+
|
7
|
+
croppedImg, err := cutter.Crop(img, cutter.Config{
|
8
|
+
Width: 250,
|
9
|
+
Height: 500,
|
10
|
+
})
|
11
|
+
|
12
|
+
It is possible to specify the top left position:
|
13
|
+
|
14
|
+
croppedImg, err := cutter.Crop(img, cutter.Config{
|
15
|
+
Width: 250,
|
16
|
+
Height: 500,
|
17
|
+
Anchor: image.Point{100, 100},
|
18
|
+
Mode: TopLeft, // optional, default value
|
19
|
+
})
|
20
|
+
|
21
|
+
The Anchor property can represents the center of the cropped image
|
22
|
+
instead of the top left corner:
|
23
|
+
|
24
|
+
|
25
|
+
croppedImg, err := cutter.Crop(img, cutter.Config{
|
26
|
+
Width: 250,
|
27
|
+
Height: 500,
|
28
|
+
Mode: Centered,
|
29
|
+
})
|
30
|
+
|
31
|
+
The default crop use the specified dimension, but it is possible
|
32
|
+
to use Width and Heigth as a ratio instead. In this case,
|
33
|
+
the resulting image will be as big as possible to fit the asked ratio
|
34
|
+
from the anchor position.
|
35
|
+
|
36
|
+
croppedImg, err := cutter.Crop(baseImage, cutter.Config{
|
37
|
+
Width: 4,
|
38
|
+
Height: 3,
|
39
|
+
Mode: Centered,
|
40
|
+
Options: Ratio,
|
41
|
+
})
|
42
|
+
*/
|
43
|
+
package cutter
|
44
|
+
|
45
|
+
import (
|
46
|
+
"image"
|
47
|
+
"image/draw"
|
48
|
+
)
|
49
|
+
|
50
|
+
// Config is used to defined
|
51
|
+
// the way the crop should be realized.
|
52
|
+
type Config struct {
|
53
|
+
Width, Height int
|
54
|
+
Anchor image.Point // The Anchor Point in the source image
|
55
|
+
Mode AnchorMode // Which point in the resulting image the Anchor Point is referring to
|
56
|
+
Options Option
|
57
|
+
}
|
58
|
+
|
59
|
+
// AnchorMode is an enumeration of the position an anchor can represent.
|
60
|
+
type AnchorMode int
|
61
|
+
|
62
|
+
const (
|
63
|
+
// TopLeft defines the Anchor Point
|
64
|
+
// as the top left of the cropped picture.
|
65
|
+
TopLeft AnchorMode = iota
|
66
|
+
// Centered defines the Anchor Point
|
67
|
+
// as the center of the cropped picture.
|
68
|
+
Centered = iota
|
69
|
+
)
|
70
|
+
|
71
|
+
// Option flags to modify the way the crop is done.
|
72
|
+
type Option int
|
73
|
+
|
74
|
+
const (
|
75
|
+
// Ratio flag is use when Width and Height
|
76
|
+
// must be used to compute a ratio rather
|
77
|
+
// than absolute size in pixels.
|
78
|
+
Ratio Option = 1 << iota
|
79
|
+
// Copy flag is used to enforce the function
|
80
|
+
// to retrieve a copy of the selected pixels.
|
81
|
+
// This disable the use of SubImage method
|
82
|
+
// to compute the result.
|
83
|
+
Copy = 1 << iota
|
84
|
+
)
|
85
|
+
|
86
|
+
// An interface that is
|
87
|
+
// image.Image + SubImage method.
|
88
|
+
type subImageSupported interface {
|
89
|
+
SubImage(r image.Rectangle) image.Image
|
90
|
+
}
|
91
|
+
|
92
|
+
// Crop retrieves an image that is a
|
93
|
+
// cropped copy of the original img.
|
94
|
+
//
|
95
|
+
// The crop is made given the informations provided in config.
|
96
|
+
func Crop(img image.Image, c Config) (image.Image, error) {
|
97
|
+
maxBounds := c.maxBounds(img.Bounds())
|
98
|
+
size := c.computeSize(maxBounds, image.Point{c.Width, c.Height})
|
99
|
+
cr := c.computedCropArea(img.Bounds(), size)
|
100
|
+
cr = img.Bounds().Intersect(cr)
|
101
|
+
|
102
|
+
if c.Options&Copy == Copy {
|
103
|
+
return cropWithCopy(img, cr)
|
104
|
+
}
|
105
|
+
if dImg, ok := img.(subImageSupported); ok {
|
106
|
+
return dImg.SubImage(cr), nil
|
107
|
+
}
|
108
|
+
return cropWithCopy(img, cr)
|
109
|
+
}
|
110
|
+
|
111
|
+
func cropWithCopy(img image.Image, cr image.Rectangle) (image.Image, error) {
|
112
|
+
result := image.NewRGBA(cr)
|
113
|
+
draw.Draw(result, cr, img, cr.Min, draw.Src)
|
114
|
+
return result, nil
|
115
|
+
}
|
116
|
+
|
117
|
+
func (c Config) maxBounds(bounds image.Rectangle) (r image.Rectangle) {
|
118
|
+
if c.Mode == Centered {
|
119
|
+
anchor := c.centeredMin(bounds)
|
120
|
+
w := min(anchor.X-bounds.Min.X, bounds.Max.X-anchor.X)
|
121
|
+
h := min(anchor.Y-bounds.Min.Y, bounds.Max.Y-anchor.Y)
|
122
|
+
r = image.Rect(anchor.X-w, anchor.Y-h, anchor.X+w, anchor.Y+h)
|
123
|
+
} else {
|
124
|
+
r = image.Rect(c.Anchor.X, c.Anchor.Y, bounds.Max.X, bounds.Max.Y)
|
125
|
+
}
|
126
|
+
return
|
127
|
+
}
|
128
|
+
|
129
|
+
// computeSize retrieve the effective size of the cropped image.
|
130
|
+
// It is defined by Height, Width, and Ratio option.
|
131
|
+
func (c Config) computeSize(bounds image.Rectangle, ratio image.Point) (p image.Point) {
|
132
|
+
if c.Options&Ratio == Ratio {
|
133
|
+
// Ratio option is on, so we take the biggest size available that fit the given ratio.
|
134
|
+
if float64(ratio.X)/float64(bounds.Dx()) > float64(ratio.Y)/float64(bounds.Dy()) {
|
135
|
+
p = image.Point{bounds.Dx(), (bounds.Dx() / ratio.X) * ratio.Y}
|
136
|
+
} else {
|
137
|
+
p = image.Point{(bounds.Dy() / ratio.Y) * ratio.X, bounds.Dy()}
|
138
|
+
}
|
139
|
+
} else {
|
140
|
+
p = image.Point{ratio.X, ratio.Y}
|
141
|
+
}
|
142
|
+
return
|
143
|
+
}
|
144
|
+
|
145
|
+
// computedCropArea retrieve the theorical crop area.
|
146
|
+
// It is defined by Height, Width, Mode and
|
147
|
+
func (c Config) computedCropArea(bounds image.Rectangle, size image.Point) (r image.Rectangle) {
|
148
|
+
min := bounds.Min
|
149
|
+
switch c.Mode {
|
150
|
+
case Centered:
|
151
|
+
rMin := c.centeredMin(bounds)
|
152
|
+
r = image.Rect(rMin.X-size.X/2, rMin.Y-size.Y/2, rMin.X+size.X/2, rMin.Y+size.Y/2)
|
153
|
+
default: // TopLeft
|
154
|
+
rMin := image.Point{min.X + c.Anchor.X, min.Y + c.Anchor.Y}
|
155
|
+
r = image.Rect(rMin.X, rMin.Y, rMin.X+size.X, rMin.Y+size.Y)
|
156
|
+
}
|
157
|
+
return
|
158
|
+
}
|
159
|
+
|
160
|
+
func (c *Config) centeredMin(bounds image.Rectangle) (rMin image.Point) {
|
161
|
+
min := bounds.Min
|
162
|
+
if c.Anchor.X == 0 && c.Anchor.Y == 0 {
|
163
|
+
rMin = image.Point{
|
164
|
+
X: min.X + bounds.Dx()/2,
|
165
|
+
Y: min.Y + bounds.Dy()/2,
|
166
|
+
}
|
167
|
+
} else {
|
168
|
+
rMin = image.Point{
|
169
|
+
X: min.X + c.Anchor.X,
|
170
|
+
Y: min.Y + c.Anchor.Y,
|
171
|
+
}
|
172
|
+
}
|
173
|
+
return
|
174
|
+
}
|
175
|
+
|
176
|
+
func min(a, b int) (r int) {
|
177
|
+
if a < b {
|
178
|
+
r = a
|
179
|
+
} else {
|
180
|
+
r = b
|
181
|
+
}
|
182
|
+
return
|
183
|
+
}
|