Browse Source

Vendoring, Policies, etc...

Chris Walker 3 years ago
parent
commit
e0af7a642e
100 changed files with 31957 additions and 224 deletions
  1. 7
    4
      cmd/opfsd/opfsd.go
  2. 26
    12
      opfs/api.go
  3. 64
    30
      opfs/collection_index.go
  4. 1
    1
      opfs/collections.go
  5. 22
    19
      opfs/http.go
  6. 21
    1
      opfs/item_index.go
  7. 7
    82
      opfs/permission_layer.go
  8. 0
    29
      opfs/permission_layer_action.go
  9. 4
    11
      opfs/permission_layer_allow_all.go
  10. 6
    6
      opfs/permission_layer_auth_google.go
  11. 98
    0
      opfs/policy.go
  12. 45
    29
      opfs/query.go
  13. 23
    0
      vendor.yml
  14. 19
    0
      vendor/github.com/disintegration/imaging/.travis.yml
  15. 21
    0
      vendor/github.com/disintegration/imaging/LICENSE
  16. 198
    0
      vendor/github.com/disintegration/imaging/README.md
  17. 200
    0
      vendor/github.com/disintegration/imaging/adjust.go
  18. 504
    0
      vendor/github.com/disintegration/imaging/adjust_test.go
  19. 187
    0
      vendor/github.com/disintegration/imaging/effects.go
  20. 190
    0
      vendor/github.com/disintegration/imaging/effects_test.go
  21. 400
    0
      vendor/github.com/disintegration/imaging/helpers.go
  22. 399
    0
      vendor/github.com/disintegration/imaging/helpers_test.go
  23. 583
    0
      vendor/github.com/disintegration/imaging/resize.go
  24. 570
    0
      vendor/github.com/disintegration/imaging/resize_test.go
  25. 201
    0
      vendor/github.com/disintegration/imaging/tools.go
  26. 652
    0
      vendor/github.com/disintegration/imaging/tools_test.go
  27. 201
    0
      vendor/github.com/disintegration/imaging/transform.go
  28. 261
    0
      vendor/github.com/disintegration/imaging/transform_test.go
  29. 77
    0
      vendor/github.com/disintegration/imaging/utils.go
  30. 81
    0
      vendor/github.com/disintegration/imaging/utils_test.go
  31. 3
    0
      vendor/github.com/golang/protobuf/AUTHORS
  32. 3
    0
      vendor/github.com/golang/protobuf/CONTRIBUTORS
  33. 31
    0
      vendor/github.com/golang/protobuf/LICENSE
  34. 40
    0
      vendor/github.com/golang/protobuf/Make.protobuf
  35. 54
    0
      vendor/github.com/golang/protobuf/Makefile
  36. 199
    0
      vendor/github.com/golang/protobuf/README.md
  37. 716
    0
      vendor/github.com/golang/protobuf/jsonpb/jsonpb.go
  38. 473
    0
      vendor/github.com/golang/protobuf/jsonpb/jsonpb_test.go
  39. 33
    0
      vendor/github.com/golang/protobuf/jsonpb/jsonpb_test_proto/Makefile
  40. 121
    0
      vendor/github.com/golang/protobuf/jsonpb/jsonpb_test_proto/more_test_objects.pb.go
  41. 46
    0
      vendor/github.com/golang/protobuf/jsonpb/jsonpb_test_proto/more_test_objects.proto
  42. 739
    0
      vendor/github.com/golang/protobuf/jsonpb/jsonpb_test_proto/test_objects.pb.go
  43. 132
    0
      vendor/github.com/golang/protobuf/jsonpb/jsonpb_test_proto/test_objects.proto
  44. 43
    0
      vendor/github.com/golang/protobuf/proto/Makefile
  45. 2212
    0
      vendor/github.com/golang/protobuf/proto/all_test.go
  46. 272
    0
      vendor/github.com/golang/protobuf/proto/any_test.go
  47. 223
    0
      vendor/github.com/golang/protobuf/proto/clone.go
  48. 267
    0
      vendor/github.com/golang/protobuf/proto/clone_test.go
  49. 868
    0
      vendor/github.com/golang/protobuf/proto/decode.go
  50. 1331
    0
      vendor/github.com/golang/protobuf/proto/encode.go
  51. 276
    0
      vendor/github.com/golang/protobuf/proto/equal.go
  52. 212
    0
      vendor/github.com/golang/protobuf/proto/equal_test.go
  53. 399
    0
      vendor/github.com/golang/protobuf/proto/extensions.go
  54. 430
    0
      vendor/github.com/golang/protobuf/proto/extensions_test.go
  55. 894
    0
      vendor/github.com/golang/protobuf/proto/lib.go
  56. 280
    0
      vendor/github.com/golang/protobuf/proto/message_set.go
  57. 66
    0
      vendor/github.com/golang/protobuf/proto/message_set_test.go
  58. 479
    0
      vendor/github.com/golang/protobuf/proto/pointer_reflect.go
  59. 266
    0
      vendor/github.com/golang/protobuf/proto/pointer_unsafe.go
  60. 846
    0
      vendor/github.com/golang/protobuf/proto/properties.go
  61. 196
    0
      vendor/github.com/golang/protobuf/proto/proto3_proto/proto3.pb.go
  62. 72
    0
      vendor/github.com/golang/protobuf/proto/proto3_proto/proto3.proto
  63. 125
    0
      vendor/github.com/golang/protobuf/proto/proto3_test.go
  64. 63
    0
      vendor/github.com/golang/protobuf/proto/size2_test.go
  65. 164
    0
      vendor/github.com/golang/protobuf/proto/size_test.go
  66. 50
    0
      vendor/github.com/golang/protobuf/proto/testdata/Makefile
  67. 86
    0
      vendor/github.com/golang/protobuf/proto/testdata/golden_test.go
  68. 3992
    0
      vendor/github.com/golang/protobuf/proto/testdata/test.pb.go
  69. 535
    0
      vendor/github.com/golang/protobuf/proto/testdata/test.proto
  70. 849
    0
      vendor/github.com/golang/protobuf/proto/text.go
  71. 871
    0
      vendor/github.com/golang/protobuf/proto/text_parser.go
  72. 557
    0
      vendor/github.com/golang/protobuf/proto/text_parser_test.go
  73. 474
    0
      vendor/github.com/golang/protobuf/proto/text_test.go
  74. 33
    0
      vendor/github.com/golang/protobuf/protoc-gen-go/Makefile
  75. 39
    0
      vendor/github.com/golang/protobuf/protoc-gen-go/descriptor/Makefile
  76. 2006
    0
      vendor/github.com/golang/protobuf/protoc-gen-go/descriptor/descriptor.pb.go
  77. 51
    0
      vendor/github.com/golang/protobuf/protoc-gen-go/doc.go
  78. 40
    0
      vendor/github.com/golang/protobuf/protoc-gen-go/generator/Makefile
  79. 2781
    0
      vendor/github.com/golang/protobuf/protoc-gen-go/generator/generator.go
  80. 85
    0
      vendor/github.com/golang/protobuf/protoc-gen-go/generator/name_test.go
  81. 456
    0
      vendor/github.com/golang/protobuf/protoc-gen-go/grpc/grpc.go
  82. 34
    0
      vendor/github.com/golang/protobuf/protoc-gen-go/link_grpc.go
  83. 98
    0
      vendor/github.com/golang/protobuf/protoc-gen-go/main.go
  84. 45
    0
      vendor/github.com/golang/protobuf/protoc-gen-go/plugin/Makefile
  85. 225
    0
      vendor/github.com/golang/protobuf/protoc-gen-go/plugin/plugin.pb.go
  86. 83
    0
      vendor/github.com/golang/protobuf/protoc-gen-go/plugin/plugin.pb.golden
  87. 72
    0
      vendor/github.com/golang/protobuf/protoc-gen-go/testdata/Makefile
  88. 46
    0
      vendor/github.com/golang/protobuf/protoc-gen-go/testdata/extension_base.proto
  89. 38
    0
      vendor/github.com/golang/protobuf/protoc-gen-go/testdata/extension_extra.proto
  90. 210
    0
      vendor/github.com/golang/protobuf/protoc-gen-go/testdata/extension_test.go
  91. 100
    0
      vendor/github.com/golang/protobuf/protoc-gen-go/testdata/extension_user.proto
  92. 59
    0
      vendor/github.com/golang/protobuf/protoc-gen-go/testdata/grpc.proto
  93. 113
    0
      vendor/github.com/golang/protobuf/protoc-gen-go/testdata/imp.pb.go.golden
  94. 70
    0
      vendor/github.com/golang/protobuf/protoc-gen-go/testdata/imp.proto
  95. 43
    0
      vendor/github.com/golang/protobuf/protoc-gen-go/testdata/imp2.proto
  96. 38
    0
      vendor/github.com/golang/protobuf/protoc-gen-go/testdata/imp3.proto
  97. 46
    0
      vendor/github.com/golang/protobuf/protoc-gen-go/testdata/main_test.go
  98. 44
    0
      vendor/github.com/golang/protobuf/protoc-gen-go/testdata/multi/multi1.proto
  99. 46
    0
      vendor/github.com/golang/protobuf/protoc-gen-go/testdata/multi/multi2.proto
  100. 0
    0
      vendor/github.com/golang/protobuf/protoc-gen-go/testdata/multi/multi3.proto

+ 7
- 4
cmd/opfsd/opfsd.go View File

@@ -35,15 +35,13 @@ func main() {
35 35
 	}
36 36
 
37 37
 	adminUser := func(email string) bool {
38
-		return email == "thechriswalker@gmail.com"
38
+		return false && email == "thechriswalker@gmail.com"
39 39
 	}
40 40
 
41 41
 	//create the API
42 42
 	api := opfs.NewAPI(
43 43
 		opfs.VersionInfo{},
44
-		&opfs.UserPermissionLayer{
45
-			Auth: opfs.NewGoogleAuth(*gaClientId, *gaClientSecret, *origin, validUser, adminUser),
46
-		},
44
+		opfs.NewGoogleAuth(*gaClientId, *gaClientSecret, *origin, validUser, adminUser),
47 45
 		&opfs.LocalBlobStore{Root: *storePath},
48 46
 		maxConcurrentThumbnailCreation,
49 47
 	)
@@ -79,6 +77,7 @@ func main() {
79 77
 				for {
80 78
 					//read lines as items to reindex.
81 79
 					//@TODO limit how much is read here, lines should be short.
80
+					//@TODO we need to handle more commands here, even if it is reindex item and reindex collection
82 81
 					str, err := buf.ReadString('\n')
83 82
 					str = strings.TrimSpace(str)
84 83
 					if idRegex.MatchString(str) {
@@ -88,6 +87,10 @@ func main() {
88 87
 							conn.Write([]byte{'\n'})
89 88
 							log.Println("Failed to reindex:", str)
90 89
 						}
90
+					} else {
91
+						//bad input
92
+						conn.Write([]byte("Bad input. goodbye"))
93
+						return
91 94
 					}
92 95
 					if err != nil {
93 96
 						break

+ 26
- 12
opfs/api.go View File

@@ -131,14 +131,17 @@ func (a *Api) GetInfo() map[string]interface{} {
131 131
 //
132 132
 //  Perform a search query on the index
133 133
 //
134
-func (a *Api) Search(ident Identity, query *Query) (*QueryResult, *apiError) {
134
+func (a *Api) Search(ident Identity, query *InputQuery) (*QueryResult, *apiError) {
135 135
 	apiDebug("api.Search()")
136
-	if !a.Permissions.ShouldAllow(a, ident, PolicyActionSearch, nil) {
136
+	if !a.ShouldAllow(ident, PolicyActionSearch, nil) {
137 137
 		apiDebug("api.Search(): Identity not allowed to search: %s", ident)
138 138
 		return nil, NewNotAllowedError(ident, PolicyActionSearch, "Sorry, you do not have permission to search")
139 139
 	}
140
-	query.Filter = a.Permissions.SearchFilter(a, ident, query.Filter)
141
-	res, err := a.Items.Query(query)
140
+	realQuery := query.Query(a, ident)
141
+	if realQuery.Filter == nil {
142
+		return zeroResults, nil
143
+	}
144
+	res, err := a.Items.Query(realQuery)
142 145
 	if err != nil {
143 146
 		return nil, NewApiError(err, "Search Error", http.StatusInternalServerError)
144 147
 	}
@@ -146,24 +149,35 @@ func (a *Api) Search(ident Identity, query *Query) (*QueryResult, *apiError) {
146 149
 }
147 150
 
148 151
 //
149
-// Get all tags with a given prefix
152
+// Get all tags with a given prefix, distinct from getting collections.
153
+// This only get tags you own, designed for an auto-complete, to get all collections
154
+// use GetCollections()
150 155
 //
151 156
 func (a *Api) GetTags(ident Identity, prefix string) (map[string]int, *apiError) {
152 157
 	apiDebug("api.GetTags(%s)", prefix)
153
-	if !a.Permissions.ShouldAllow(a, ident, PolicyActionSearch, nil) {
158
+	if !a.ShouldAllow(ident, PolicyActionSearch, nil) {
154 159
 		apiDebug("api.GetTags(%s): Identity not allowed to search (for tags): %s", prefix, ident)
155 160
 		return nil, NewNotAllowedError(ident, PolicyActionSearch, "Sorry, you don't have permission to get tags")
156 161
 	}
157 162
 	var filter index.Filter
158 163
 	if prefix == "" {
159
-		filter = index.NewMatchAllFilter()
164
+		//all mine
165
+		filter = index.NewStringTermFilter("owner", ident.String())
160 166
 	} else {
161
-		filter = index.NewStringPrefixFilter("tags", prefix)
167
+		//all mine with prefix
168
+		filter = index.NewLogicalAndFilter(
169
+			index.NewStringTermFilter("owner", ident.String()),
170
+			index.NewStringPrefixFilter("tags", prefix),
171
+		)
162 172
 	}
163 173
 	return a.Items.Tags(filter), nil
164 174
 }
165 175
 
166 176
 //
177
+// Get Collections, this can be mine, shared, public
178
+//
179
+
180
+//
167 181
 // Get the meta data for a single item.
168 182
 //
169 183
 func (a *Api) GetMeta(ident Identity, id string) (*ItemInfo, *apiError) {
@@ -172,7 +186,7 @@ func (a *Api) GetMeta(ident Identity, id string) (*ItemInfo, *apiError) {
172 186
 	if meta == nil {
173 187
 		return nil, errItemNotFound
174 188
 	}
175
-	if !a.Permissions.ShouldAllow(a, ident, PolicyActionView, meta) {
189
+	if !a.ShouldAllow(ident, PolicyActionView, meta) {
176 190
 		apiDebug("api.GetMeta(%s): Identity not allowed to View item: %s", id, ident)
177 191
 		//not allowed, or 404?
178 192
 		return nil, NewNotAllowedError(ident, PolicyActionView, "Sorry, you don't have permission to view that item")
@@ -206,7 +220,7 @@ func (a *Api) GetData(ident Identity, id string) (ReadSeekCloser, *apiError) {
206 220
 //
207 221
 func (a *Api) GetThumbnail(ident Identity, meta *ItemInfo, size ThumbSize) (data ReadSeekCloser, err *apiError) {
208 222
 	apiDebug("api.GetThumbnail(%s)", meta.Id)
209
-	if !a.Permissions.ShouldAllow(a, ident, PolicyActionView, meta) {
223
+	if !a.ShouldAllow(ident, PolicyActionView, meta) {
210 224
 		apiDebug("api.GetThumbnail(%s): Identity not allowed to View item: %s", meta.Id, ident)
211 225
 		//not allowed, or 404?
212 226
 		return nil, NewNotAllowedError(ident, PolicyActionView, "Sorry, you do not have permission to view that item")
@@ -254,7 +268,7 @@ func (a *Api) GetThumbnail(ident Identity, meta *ItemInfo, size ThumbSize) (data
254 268
 func (a *Api) ImportItem(ident Identity, file ReadSeekCloser, name, mimetype string) (*ItemInfo, *apiError) {
255 269
 	defer file.Close()
256 270
 	apiDebug("api.ImportItem(%s)", name)
257
-	if !a.Permissions.ShouldAllow(a, ident, PolicyActionImport, nil) {
271
+	if !a.ShouldAllow(ident, PolicyActionImport, nil) {
258 272
 		apiDebug("api.ImportItem(): Identity not allowed to import: %s", ident)
259 273
 		return nil, NewNotAllowedError(ident, PolicyActionImport, "Sorry, you don't have permission to import items.")
260 274
 	}
@@ -322,7 +336,7 @@ func (a *Api) UpdateMeta(ident Identity, id string, update *ItemUpdate) (*ItemIn
322 336
 		return nil, err
323 337
 	}
324 338
 	//but we need to check that too.
325
-	if !a.Permissions.ShouldAllow(a, ident, PolicyActionUpdateMeta, meta) {
339
+	if !a.ShouldAllow(ident, PolicyActionUpdateMeta, meta) {
326 340
 		apiDebug("api.ImportItem(%s): Identity not allowed to Update Item: %s", id, ident)
327 341
 		return nil, NewNotAllowedError(ident, PolicyActionUpdateMeta, "Sorry, you don't have permission to update that item")
328 342
 	}

+ 64
- 30
opfs/collection_index.go View File

@@ -1,30 +1,37 @@
1 1
 package opfs
2 2
 
3 3
 import (
4
+	"sync"
5
+
4 6
 	"github.com/thechriswalker/opfs-server/index"
5 7
 )
6 8
 
7 9
 type CollectionIndexer interface {
8
-	Insert(item *Collection) error            //store an item in the index.
9
-	Update(item *Collection) error            //update info in the index
10
-	Delete(id string) error                   //delete from the index
11
-	Has(id string) bool                       //does the index know about this id?
12
-	Get(id string) *Collection                //returns a single item (or nil if not indexed)
13
-	CreateAccessFilter(Identity) index.Filter //creates the filter to us to limit items by collections the user has access to.
14
-	HasSharedAccess(Identity, *ItemInfo) bool //check an item against the collection index, and see if we have access
10
+	Insert(item *Collection) error             //store an item in the index.
11
+	Update(item *Collection) error             //update info in the index
12
+	Delete(id string) error                    //delete from the index
13
+	Has(id string) bool                        //does the index know about this id?
14
+	Get(id string) *Collection                 //returns a single item (or nil if not indexed)
15
+	GetSharedItemFilter(Identity) index.Filter //Gets a filter that matches `shared-with-me` items
16
+	GetPublicItemFilter() index.Filter         // Gets a filter that matches public items.
17
+	HasSharedAccess(Identity, *ItemInfo) bool  //check an item against the collection index, and see if we have access
15 18
 }
16 19
 
17 20
 type CollectionIndex struct {
18 21
 	store map[string]*Collection
19 22
 	index *index.Index
23
+	mtx   *sync.RWMutex
20 24
 }
21 25
 
26
+var matchNothingFilter = index.NewLogicalNotFilter(index.NewMatchAllFilter())
27
+
22 28
 var _ CollectionIndexer = (*CollectionIndex)(nil)
23 29
 
24 30
 func NewCollectionIndex() CollectionIndexer {
25 31
 	return &CollectionIndex{
26 32
 		store: make(map[string]*Collection),
27 33
 		index: index.NewIndex(),
34
+		mtx:   &sync.RWMutex{},
28 35
 	}
29 36
 }
30 37
 
@@ -32,66 +39,89 @@ func (ci *CollectionIndex) Insert(item *Collection) error {
32 39
 	if err := ci.index.Add(item); err != nil {
33 40
 		return err
34 41
 	}
42
+	ci.mtx.Lock()
43
+	defer ci.mtx.Unlock()
35 44
 	ci.store[item.GetID()] = item
36 45
 	return nil
37 46
 }
38 47
 
39 48
 func (ci *CollectionIndex) Update(item *Collection) error {
40
-	_, ok := ci.store[item.GetID()]
41
-	if !ok {
42
-		return errItemNotFound
43
-	}
44 49
 	if err := ci.index.Replace(item); err != nil {
45 50
 		return err
46 51
 	}
52
+	ci.mtx.Lock()
53
+	defer ci.mtx.Unlock()
47 54
 	ci.store[item.GetID()] = item
48 55
 	return nil
49 56
 }
50 57
 
51 58
 func (ci *CollectionIndex) Delete(id string) error {
52
-	_, ok := ci.store[id]
53
-	if !ok {
54
-		return errItemNotFound
55
-	}
56 59
 	if err := ci.index.Remove(id); err != nil {
57 60
 		return err
58 61
 	}
62
+	ci.mtx.Lock()
63
+	defer ci.mtx.Unlock()
59 64
 	delete(ci.store, id)
60 65
 	return nil
61 66
 }
62 67
 
63 68
 func (ci *CollectionIndex) Has(id string) bool {
69
+	ci.mtx.RLock()
70
+	defer ci.mtx.RUnlock()
64 71
 	_, ok := ci.store[id]
65 72
 	return ok
66 73
 }
67 74
 
68 75
 func (ci *CollectionIndex) Get(id string) *Collection {
76
+	ci.mtx.RLock()
77
+	defer ci.mtx.RUnlock()
69 78
 	item, _ := ci.store[id]
70 79
 	return item
71 80
 }
72 81
 
73 82
 //This is a complicated one. We need to create a filter for the "item index"
74
-//that restricts to items that have owner/tag combo that matches a public/shared
75
-//collection. Not this excludes collections *you* own.
76
-//this is because the items in collections you own, all belong to you.
77
-func (ci *CollectionIndex) CreateAccessFilter(ident Identity) index.Filter {
78
-	//query for public or shared collections. then get there owner/tag
79
-	query := index.NewLogicalOrFilter(
80
-		//public collections.
81
-		index.NewBooleanTermFilter("public", true),
82
-		//shared
83
-		index.NewStringTermFilter("shared", ident.String()),
84
-	)
85
-	all := ci.index.Search(query)
83
+//that restricts to items that have owner/tag combo that matches a collection
84
+//shared with you.
85
+func (ci *CollectionIndex) GetSharedItemFilter(ident Identity) index.Filter {
86
+	//query for shared collections. then get there owner/tag
87
+	shared := ci.index.Search(index.NewStringTermFilter("shared", ident.String()))
86 88
 
87
-	itemFilters := make([]index.Filter, len(all))
88
-	for i, id := range all {
89
+	if len(shared) == 0 {
90
+		return matchNothingFilter
91
+	}
92
+
93
+	itemFilters := make([]index.Filter, len(shared))
94
+
95
+	ci.mtx.RLock()
96
+	defer ci.mtx.RUnlock()
97
+
98
+	for i, id := range shared {
89 99
 		collection := ci.store[id]
90 100
 		itemFilters[i] = collectionFilter(collection.Owner, collection.Tag)
91 101
 	}
92 102
 	return index.NewLogicalOrFilter(itemFilters...)
93 103
 }
94 104
 
105
+func (ci *CollectionIndex) GetPublicItemFilter() index.Filter {
106
+	//query for shared collections. then get there owner/tag
107
+	public := ci.index.Search(index.NewBooleanTermFilter("public", true))
108
+
109
+	if len(public) == 0 {
110
+		return matchNothingFilter
111
+	}
112
+
113
+	itemFilters := make([]index.Filter, len(public))
114
+
115
+	ci.mtx.RLock()
116
+	defer ci.mtx.RUnlock()
117
+
118
+	for i, id := range public {
119
+		collection := ci.store[id]
120
+		itemFilters[i] = collectionFilter(collection.Owner, collection.Tag)
121
+	}
122
+
123
+	return index.NewLogicalOrFilter(itemFilters...)
124
+}
95 125
 func (ci *CollectionIndex) HasSharedAccess(ident Identity, item *ItemInfo) bool {
96 126
 	//find collections that this belongs to, then see if the ident is OK.
97 127
 	if len(item.Tags) == 0 {
@@ -106,6 +136,10 @@ func (ci *CollectionIndex) HasSharedAccess(ident Identity, item *ItemInfo) bool
106 136
 		index.NewStringTermFilter("owner", item.Owner),
107 137
 		index.NewLogicalOrFilter(tagFilters...),
108 138
 	))
139
+
140
+	ci.mtx.RLock()
141
+	defer ci.mtx.RUnlock()
142
+
109 143
 	//now find one that matches the ident.
110 144
 	for _, id := range collections {
111 145
 		col := ci.store[id]
@@ -119,7 +153,7 @@ func (ci *CollectionIndex) HasSharedAccess(ident Identity, item *ItemInfo) bool
119 153
 func collectionFilter(owner, tag string) index.Filter {
120 154
 	return index.NewLogicalAndFilter(
121 155
 		index.NewStringTermFilter("owner", owner),
122
-		index.NewStringTermFilter("tag", tag),
156
+		index.NewStringTermFilter("tags", tag),
123 157
 	)
124 158
 }
125 159
 

+ 1
- 1
opfs/collections.go View File

@@ -8,7 +8,7 @@ import (
8 8
 type Collection struct {
9 9
 	Description string   `json:"description"`
10 10
 	Owner       string   `json:"owner"`
11
-	Tag         string   `json:tag`
11
+	Tag         string   `json:"tag"`
12 12
 	Shared      []string `json:"shared"`
13 13
 	Public      bool     `json:"public"`
14 14
 }

+ 22
- 19
opfs/http.go View File

@@ -8,7 +8,6 @@ import (
8 8
 	"os"
9 9
 	"path"
10 10
 	"strconv"
11
-	"strings"
12 11
 
13 12
 	"github.com/thechriswalker/opfs-server/debug"
14 13
 
@@ -17,7 +16,7 @@ import (
17 16
 )
18 17
 
19 18
 const DEFAULT_SEARCH_RESULT_SIZE = 45
20
-const UI_PREFIX = "/ui/"
19
+const UI_PREFIX = "/ui"
21 20
 const API_PREFIX = "/api"
22 21
 
23 22
 var httpDebug = debug.Logger("http")
@@ -49,7 +48,7 @@ func createMux(api *Api, allowImportOverHTTP bool, ui http.FileSystem) http.Hand
49 48
 	//helper to wrap our functions as http.Handlers
50 49
 	f := func(fn func(*Api, *http.Request, Identity) ApiResponse) apiFunc {
51 50
 		return apiFunc(func(r *http.Request) ApiResponse {
52
-			return fn(api, r, api.Permissions.IdentifyRequest(r))
51
+			return fn(api, r, api.Permissions.Identify(r))
53 52
 		})
54 53
 	}
55 54
 
@@ -64,10 +63,23 @@ func createMux(api *Api, allowImportOverHTTP bool, ui http.FileSystem) http.Hand
64 63
 
65 64
 	//pass the responsibility for /auth endpoints to the AccessControllers
66 65
 	subrouter := router.PathPrefix("/auth").Subrouter()
67
-	api.Permissions.Routes(subrouter)
66
+	api.Permissions.AuthRoutes(subrouter)
68 67
 
69 68
 	apiRouter := router.PathPrefix(API_PREFIX).Subrouter()
70 69
 
70
+	//auth controller also handles /api/login and /api/logout
71
+	//which means login/logout is consistent, even if those handlers just return redirects to /auth/something
72
+	login := api.Permissions.LoginHandler()
73
+	if login != nil {
74
+		apiRouter.Handle("/login", login)
75
+	}
76
+	logout := api.Permissions.LogoutHandler()
77
+	if logout != nil {
78
+		apiRouter.Handle("/logout", logout)
79
+	}
80
+
81
+	apiRouter.Handle("/logout", api.Permissions.LogoutHandler())
82
+
71 83
 	// GET /info - version info, metadata and current user info
72 84
 	apiRouter.Handle("/info", f(apiGetInfo)).Methods("GET", "HEAD")
73 85
 
@@ -267,29 +279,20 @@ func apiSearch(a *Api, r *http.Request, ident Identity) ApiResponse {
267 279
 		//GET
268 280
 		query.QueryString = r.FormValue("query")
269 281
 		query.SortOrder = r.FormValue("order")
270
-		query.AllowDeleted = r.FormValue("deleted") == "true"
271
-		//types multi form values
272
-		qtypes, ok := r.Form["types"]
273
-		httpDebug("qtypes %T %v", qtypes, qtypes)
274
-		if ok && len(qtypes) > 0 {
275
-			query.Types = make([]ItemType, 0, len(qtypes))
276
-			for _, typeString := range qtypes {
277
-				httpDebug("%s", typeString)
278
-				it := ItemTypeUnknown
279
-				it.UnmarshalText([]byte(strings.TrimSpace(typeString)))
280
-				if it != ItemTypeUnknown {
281
-					query.Types = append(query.Types, it)
282
-				}
282
+		query.Restrictions = []string{}
283
+		for _, s := range []string{"deleted", "mine", "shared", "public"} {
284
+			if r.FormValue(s) == "true" {
285
+				query.Restrictions = append(query.Restrictions, s)
283 286
 			}
284 287
 		}
285 288
 	}
286 289
 	httpDebug("query.Parse")
287
-	realQuery, err := query.Parse()
290
+	err := query.Parse()
288 291
 	if err != nil {
289 292
 		httpDebug("query error? %v", err)
290 293
 		return NewApiError(err, "Invalid Query", 400)
291 294
 	}
292
-	results, aerr := a.Search(ident, realQuery)
295
+	results, aerr := a.Search(ident, query)
293 296
 	if aerr != nil {
294 297
 		httpDebug("search error? %#v", aerr)
295 298
 		return aerr

+ 21
- 1
opfs/item_index.go View File

@@ -2,6 +2,7 @@ package opfs
2 2
 
3 3
 import (
4 4
 	"sort"
5
+	"sync"
5 6
 	"time"
6 7
 
7 8
 	"github.com/thechriswalker/opfs-server/debug"
@@ -23,10 +24,10 @@ type ItemIndexer interface {
23 24
 }
24 25
 
25 26
 //simplest indexer possible. but we must load it from the store each time...
26
-//@TODO I need a mutex on this. maps are dangerous
27 27
 type ItemIndex struct {
28 28
 	store map[string]*ItemInfo //the actual items
29 29
 	index *index.Index         //this stores all item meta data
30
+	mtx   *sync.RWMutex
30 31
 }
31 32
 
32 33
 var _ ItemIndexer = (*ItemIndex)(nil)
@@ -35,19 +36,26 @@ func NewItemIndex() ItemIndexer {
35 36
 	return &ItemIndex{
36 37
 		store: make(map[string]*ItemInfo),
37 38
 		index: index.NewIndex(),
39
+		mtx:   &sync.RWMutex{},
38 40
 	}
39 41
 }
40 42
 
41 43
 func (ii *ItemIndex) Size() int {
44
+	ii.mtx.RLock()
45
+	defer ii.mtx.RUnlock()
42 46
 	return len(ii.store)
43 47
 }
44 48
 
45 49
 func (ii *ItemIndex) Has(id string) bool {
50
+	ii.mtx.RLock()
51
+	defer ii.mtx.RUnlock()
46 52
 	_, ok := ii.store[id]
47 53
 	return ok
48 54
 }
49 55
 
50 56
 func (ii *ItemIndex) Get(id string) *ItemInfo {
57
+	ii.mtx.RLock()
58
+	defer ii.mtx.RUnlock()
51 59
 	item, _ := ii.store[id]
52 60
 	return item
53 61
 }
@@ -55,6 +63,8 @@ func (ii *ItemIndex) Get(id string) *ItemInfo {
55 63
 func (ii *ItemIndex) Tags(filter index.Filter) map[string]int {
56 64
 	//now get unique tags. with counts.
57 65
 	tagCounts := map[string]int{}
66
+	ii.mtx.RLock()
67
+	defer ii.mtx.RUnlock()
58 68
 	for _, id := range ii.index.Search(filter) {
59 69
 		for _, tag := range ii.store[id].Tags {
60 70
 			tagCounts[tag] += 1
@@ -67,6 +77,8 @@ func (ii *ItemIndex) Insert(item *ItemInfo) error {
67 77
 	if err := ii.index.Add(item); err != nil {
68 78
 		return err
69 79
 	}
80
+	ii.mtx.Lock()
81
+	defer ii.mtx.Unlock()
70 82
 	ii.store[item.Id] = item
71 83
 	return nil
72 84
 }
@@ -79,6 +91,8 @@ func (ii *ItemIndex) Update(item *ItemInfo) error {
79 91
 	if err := ii.index.Replace(item); err != nil {
80 92
 		return err
81 93
 	}
94
+	ii.mtx.Lock()
95
+	defer ii.mtx.Unlock()
82 96
 	ii.store[item.Id] = item
83 97
 	return nil
84 98
 }
@@ -91,6 +105,8 @@ func (ii *ItemIndex) Delete(id string) error {
91 105
 	if err := ii.index.Remove(id); err != nil {
92 106
 		return err
93 107
 	}
108
+	ii.mtx.Lock()
109
+	defer ii.mtx.Unlock()
94 110
 	delete(ii.store, id)
95 111
 	return nil
96 112
 }
@@ -120,6 +136,10 @@ func (ii *ItemIndex) Query(query *Query) (*QueryResult, error) {
120 136
 		Items: make([]*ItemInfo, limit),
121 137
 	}
122 138
 
139
+	//now we need the mtx.
140
+	ii.mtx.RLock()
141
+	defer ii.mtx.RUnlock()
142
+
123 143
 	//sort and slice
124 144
 	resultSorter{
125 145
 		ids:   hits,

+ 7
- 82
opfs/permission_layer.go View File

@@ -3,90 +3,15 @@ package opfs
3 3
 import (
4 4
 	"net/http"
5 5
 
6
-	"github.com/thechriswalker/opfs-server/index"
7
-
8 6
 	"github.com/gorilla/mux"
9 7
 )
10 8
 
11
-// All of the access control is done via this layer
12
-// Different implementations can be used with the API
9
+//The permissions layer is simple.
10
+//Identify takes a http request and identitfies the user.
11
+//It uses routes to setup the necessary routes for authentication.
13 12
 type PermissionLayer interface {
14
-	SearchFilter(*Api, Identity, index.Filter) index.Filter   //wraps a given filter with one that will restrict results to those allowed by the policy
15
-	ShouldAllow(*Api, Identity, PolicyAction, *ItemInfo) bool //can the Identity perform the action on the item?
16
-	Routes(*mux.Router)                                       //the handler for /auth* http requests (if needed for login/etc...)
17
-	IdentifyRequest(*http.Request) Identity                   //work out the identity from an http Request.
18
-}
19
-
20
-//this is what the UAC hands the actual auth off to. I will probably only implement Google OAuth
21
-//of course it can be used to have a "fixed" user access control, where everyone is fixed user
22
-type Authenticator interface {
23
-	//authenticate user somehow.
24
-	Identify(*http.Request) Identity
25
-	Routes(*mux.Router)
26
-}
27
-
28
-// This restricts by owner/shared/public collections
29
-// The backend can be swapped out, so different systems can be used.
30
-type UserPermissionLayer struct {
31
-	Auth Authenticator
32
-}
33
-
34
-var _ PermissionLayer = (*UserPermissionLayer)(nil)
35
-
36
-//The search filter here uses the api to find shared/public collections that the
37
-func (u *UserPermissionLayer) SearchFilter(a *Api, id Identity, f index.Filter) index.Filter {
38
-	if id.IsAdmin() {
39
-		//Admin can do anything
40
-		return f
41
-	}
42
-
43
-	//this is a filter that will match collections this user can access
44
-	//in the anonymous case, just public, in the user case, it will be collections
45
-	//they can access
46
-	shared := a.Collections.CreateAccessFilter(id)
47
-
48
-	if id.IsAnonymous() {
49
-		//only public items for anonymous users
50
-		return index.NewLogicalAndFilter(shared, f)
51
-	}
52
-
53
-	owns := index.NewStringTermFilter("owner", id.String())
54
-	//other wise, they must own it or it's tagged with a collection ref this user has access to.
55
-	userAccessFilter := index.NewLogicalOrFilter(owns, shared)
56
-
57
-	return index.NewLogicalAndFilter(userAccessFilter, f)
58
-}
59
-
60
-func (u *UserPermissionLayer) ShouldAllow(a *Api, id Identity, action PolicyAction, item *ItemInfo) bool {
61
-	switch action {
62
-	case PolicyActionImport:
63
-		//We need external configuration here, who should be allowed to import?
64
-		//only select users or there could be a free for all. So for now, no-one
65
-		//we will import on the command-line only...
66
-		return false
67
-	case PolicyActionSearch:
68
-		//everyone can search! results are limited seperately
69
-		return true
70
-	case PolicyActionSoftDelete, PolicyActionUpdateMeta:
71
-		//these are write operations. so only on your own stuff.
72
-		if item != nil {
73
-			return item.Owner == id.String()
74
-		}
75
-	case PolicyActionView:
76
-		//you can view if public, shared or owned
77
-		if item != nil {
78
-			return item.Owner == id.String() || a.Collections.HasSharedAccess(id, item)
79
-		}
80
-	}
81
-
82
-	return false
83
-}
84
-
85
-//this is kinda implementation dependant.
86
-func (u *UserPermissionLayer) IdentifyRequest(r *http.Request) Identity {
87
-	return u.Auth.Identify(r)
88
-}
89
-
90
-func (u *UserPermissionLayer) Routes(router *mux.Router) {
91
-	u.Auth.Routes(router)
13
+	Identify(*http.Request) Identity //get an identity for the request, could be anything.
14
+	LoginHandler() http.Handler      //handler for the /api/login route.
15
+	LogoutHandler() http.Handler     //handler for the /api/logout route
16
+	AuthRoutes(*mux.Router)          //allows for attaching routes to the /auth/* endpoints in case there are callbacks/or other stuff
92 17
 }

+ 0
- 29
opfs/permission_layer_action.go View File

@@ -1,29 +0,0 @@
1
-package opfs
2
-
3
-//this is something that someone can do. or can't.
4
-type PolicyAction uint8
5
-
6
-const (
7
-	PolicyActionUnknown PolicyAction = iota
8
-	PolicyActionImport
9
-	PolicyActionSearch
10
-	PolicyActionSoftDelete
11
-	PolicyActionView
12
-	PolicyActionUpdateMeta
13
-)
14
-
15
-func (p PolicyAction) String() string {
16
-	switch p {
17
-	case PolicyActionImport:
18
-		return "ImportItem"
19
-	case PolicyActionSearch:
20
-		return "SearchItems"
21
-	case PolicyActionView:
22
-		return "ViewItem"
23
-	case PolicyActionSoftDelete:
24
-		return "SoftDeleteItem"
25
-	case PolicyActionUpdateMeta:
26
-		return "UpdateItemMetadata"
27
-	}
28
-	return "<ActionUnknown>"
29
-}

+ 4
- 11
opfs/permission_layer_allow_all.go View File

@@ -3,9 +3,6 @@ package opfs
3 3
 import (
4 4
 	"net/http"
5 5
 
6
-	//internal
7
-	"github.com/thechriswalker/opfs-server/index"
8
-
9 6
 	//vendor
10 7
 	"github.com/gorilla/mux"
11 8
 )
@@ -16,13 +13,7 @@ type AllowAllAccess struct{}
16 13
 
17 14
 var _ PermissionLayer = AllowAllAccess{}
18 15
 
19
-func (aaa AllowAllAccess) SearchFilter(a *Api, id Identity, f index.Filter) index.Filter {
20
-	return f //do nothing
21
-}
22
-func (aaa AllowAllAccess) ShouldAllow(a *Api, id Identity, action PolicyAction, item *ItemInfo) bool {
23
-	return true
24
-}
25
-func (aaa AllowAllAccess) IdentifyRequest(r *http.Request) Identity {
16
+func (aaa AllowAllAccess) Identify(r *http.Request) Identity {
26 17
 	return Identity{
27 18
 		admin: true,
28 19
 		Name:  "",
@@ -30,4 +21,6 @@ func (aaa AllowAllAccess) IdentifyRequest(r *http.Request) Identity {
30 21
 }
31 22
 
32 23
 //no login needed.
33
-func (a AllowAllAccess) Routes(router *mux.Router) {}
24
+func (a AllowAllAccess) AuthRoutes(router *mux.Router) {}
25
+func (a AllowAllAccess) LoginHandler() http.Handler    { return nil }
26
+func (a AllowAllAccess) LogoutHandler() http.Handler   { return nil }

+ 6
- 6
opfs/permission_layer_auth_google.go View File

@@ -27,7 +27,7 @@ type GoogleAuth struct {
27 27
 	IsValidUser, IsAdminUser UserTest
28 28
 }
29 29
 
30
-var _ Authenticator = (*GoogleAuth)(nil)
30
+var _ PermissionLayer = (*GoogleAuth)(nil)
31 31
 
32 32
 type UserTest func(string) bool
33 33
 
@@ -61,14 +61,14 @@ func NewGoogleAuth(clientId, clientSecret, redirectionOrigin string, validUsers
61 61
 	}
62 62
 }
63 63
 
64
-func (ga *GoogleAuth) Routes(r *mux.Router) {
65
-	r.Handle("/login", ga.Login()).Methods("GET", "HEAD")
66
-	r.Handle("/logout", ga.Logout()).Methods("GET", "HEAD")
64
+func (ga *GoogleAuth) AuthRoutes(r *mux.Router) {
65
+	// r.Handle("/login", ga.Login()).Methods("GET", "HEAD")
66
+	// r.Handle("/logout", ga.Logout()).Methods("GET", "HEAD")
67 67
 	r.Handle("/callback", ga.Callback()).Methods("GET", "HEAD")
68 68
 	r.Handle("/status", ga.Status()).Methods("GET", "HEAD")
69 69
 }
70 70
 
71
-func (ga *GoogleAuth) Login() http.Handler {
71
+func (ga *GoogleAuth) LoginHandler() http.Handler {
72 72
 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
73 73
 		session, _ := ga.SessionStore.Get(r, SESSION_NAME)
74 74
 		state := getState()
@@ -115,7 +115,7 @@ func (ga *GoogleAuth) Callback() http.Handler {
115 115
 	})
116 116
 }
117 117
 
118
-func (ga *GoogleAuth) Logout() http.Handler {
118
+func (ga *GoogleAuth) LogoutHandler() http.Handler {
119 119
 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
120 120
 		session, _ := ga.SessionStore.Get(r, SESSION_NAME)
121 121
 		session.Options.MaxAge = -1 //delete session

+ 98
- 0
opfs/policy.go View File

@@ -0,0 +1,98 @@
1
+package opfs
2
+
3
+import (
4
+	"github.com/thechriswalker/opfs-server/index"
5
+)
6
+
7
+//this is something that someone can do. or can't.
8
+type PolicyAction uint8
9
+
10
+const (
11
+	PolicyActionUnknown PolicyAction = iota
12
+	PolicyActionImport
13
+	PolicyActionSearch
14
+	PolicyActionSoftDelete
15
+	PolicyActionView
16
+	PolicyActionUpdateMeta
17
+)
18
+
19
+func (p PolicyAction) String() string {
20
+	switch p {
21
+	case PolicyActionImport:
22
+		return "ImportItem"
23
+	case PolicyActionSearch:
24
+		return "SearchItems"
25
+	case PolicyActionView:
26
+		return "ViewItem"
27
+	case PolicyActionSoftDelete:
28
+		return "SoftDeleteItem"
29
+	case PolicyActionUpdateMeta:
30
+		return "UpdateItemMetadata"
31
+	}
32
+	return "<ActionUnknown>"
33
+}
34
+
35
+//here we define how our policies are implemented
36
+func (a *Api) ShouldAllow(id Identity, action PolicyAction, item *ItemInfo) bool {
37
+	if id.IsAdmin() {
38
+		return true
39
+	}
40
+	switch action {
41
+	case PolicyActionImport:
42
+		//We need external configuration here, who should be allowed to import?
43
+		//only select users or there could be a free for all. So for now, no-one
44
+		//we will import on the command-line only...
45
+		return false
46
+	case PolicyActionSearch:
47
+		//everyone can search! results are limited seperately
48
+		return true
49
+	case PolicyActionSoftDelete, PolicyActionUpdateMeta:
50
+		//these are write operations. so only on your own stuff.
51
+		if item != nil {
52
+			return item.Owner == id.String()
53
+		}
54
+	case PolicyActionView:
55
+		//you can view if public, shared or owned
56
+		if item != nil {
57
+			return item.Owner == id.String() || a.Collections.HasSharedAccess(id, item)
58
+		}
59
+	}
60
+
61
+	return false
62
+}
63
+
64
+//lets not mutate the query in place..
65
+func (a *Api) GetPolicyFilter(filter index.Filter, ident Identity, mine, shared, public, no_deleted bool) index.Filter {
66
+	//we know at least one of query.Mine/Public/Shared is true.
67
+	//so we need an AND filter.
68
+
69
+	if !mine && !shared && !public {
70
+		//no extra filters. either nothing, or everything.
71
+		parserDebug("no filters!")
72
+		if ident.IsAdmin() {
73
+			return filter
74
+		} else {
75
+			//no filter matches nothing.
76
+			return nil
77
+		}
78
+	}
79
+	// one of them is true.
80
+	orFilters := []index.Filter{}
81
+	if mine {
82
+		orFilters = append(orFilters, index.NewStringTermFilter("owner", ident.String()))
83
+	}
84
+	if shared {
85
+		orFilters = append(orFilters, a.Collections.GetSharedItemFilter(ident))
86
+	}
87
+	if public {
88
+		orFilters = append(orFilters, a.Collections.GetPublicItemFilter())
89
+	}
90
+	andFilters := []index.Filter{}
91
+	if no_deleted {
92
+		andFilters = append(andFilters, index.NewFieldMissingFilter("deleted"))
93
+	}
94
+	andFilters = append(andFilters, index.NewLogicalOrFilter(orFilters...))
95
+	andFilters = append(andFilters, filter)
96
+
97
+	return index.NewLogicalAndFilter(andFilters...)
98
+}

+ 45
- 29
opfs/query.go View File

@@ -12,7 +12,7 @@ var parserDebug = debug.Logger("query")
12 12
 
13 13
 // The generic view of the query. It contains the index Filter and
14 14
 type Query struct {
15
-	Filter index.Filter
15
+	Filter index.Filter //the base filter for the query
16 16
 	Order  []*SortOrder
17 17
 	Page   *Pagination
18 18
 }
@@ -25,6 +25,12 @@ type QueryResult struct {
25 25
 	TimeNano int64       `json:"time_nano"` //as above, but raw nanoseconds
26 26
 }
27 27
 
28
+//we only fill in the bits that won't zero correctly.
29
+var zeroResults = &QueryResult{
30
+	Items: []*ItemInfo{},
31
+	Time:  "0",
32
+}
33
+
28 34
 type SortOrder struct {
29 35
 	Field     string
30 36
 	Direction SortDirection
@@ -44,51 +50,61 @@ type Pagination struct {
44 50
 
45 51
 //This is our query parser, which accepts JSON
46 52
 type InputQuery struct {
47
-	Types        []ItemType `json:"types"` //Photo,Video,Tag
48
-	QueryString  string     `json:"query"` // going to be like Lucene Query Syntax
49
-	SortOrder    string     `json:"order"` // like "field desc, field asc"
50
-	Offset       uint64     `json:"from"`
51
-	Limit        uint64     `json:"size"`
52
-	AllowDeleted bool       `json:"allowDeleted"`
53
+	QueryString                      string   `json:"query"` // going to be like Lucene Query Syntax
54
+	SortOrder                        string   `json:"order"` // like "field desc, field asc"
55
+	Offset                           uint64   `json:"from"`
56
+	Limit                            uint64   `json:"size"`
57
+	Restrictions                     []string `json:"restrictions"` //mine|shared|public|deleted
58
+	parseError                       error    //in case we a re-called, we can re-error...
59
+	baseQuery                        *Query   //this the parsed filter.
60
+	mine, shared, no_deleted, public bool     //this is the parsed restrictions.
53 61
 }
54 62
 
55 63
 var mustNotBeDeletedFilter = index.NewFieldMissingFilter("deleted")
56 64
 
57
-func (i *InputQuery) Parse() (*Query, error) {
65
+func (i *InputQuery) Parse() error {
66
+	if i.baseQuery != nil {
67
+		return i.parseError
68
+	}
58 69
 	//first parse the filters.
59 70
 	filter, err := index.ParseQueryString(i.QueryString)
60 71
 	if err != nil {
61
-		return nil, err
72
+		i.parseError = err
73
+		return err
62 74
 	}
63 75
 	sorting, err := ParseSortOrder(i.SortOrder)
64 76
 	if err != nil {
65
-		return nil, err
77
+		i.parseError = err
78
+		return err
66 79
 	}
67 80
 
68
-	hasTypes := i.Types != nil && len(i.Types) > 0
69
-
70
-	if !i.AllowDeleted || hasTypes {
71
-		//we need to create a compound filter.
72
-		andFilters := make([]index.Filter, 0, 3)
73
-		if !i.AllowDeleted {
74
-			andFilters = append(andFilters, mustNotBeDeletedFilter) //must be missing the deleted field
75
-		}
76
-		if hasTypes {
77
-			typeFilters := make([]index.Filter, len(i.Types))
78
-			for j, t := range i.Types {
79
-				typeFilters[j] = index.NewIntTermFilter("type", int64(t))
80
-			}
81
-			andFilters = append(andFilters, index.NewLogicalOrFilter(typeFilters...))
82
-		}
83
-		andFilters = append(andFilters, filter)
84
-		filter = index.NewLogicalAndFilter(andFilters...)
81
+	if i.Restrictions != nil {
82
+		i.mine = inSlice(i.Restrictions, "mine")
83
+		i.shared = inSlice(i.Restrictions, "shared")
84
+		i.no_deleted = !inSlice(i.Restrictions, "deleted")
85
+		i.public = inSlice(i.Restrictions, "public")
85 86
 	}
86 87
 
87
-	return &Query{
88
+	i.baseQuery = &Query{
88 89
 		Page:   &Pagination{Offset: i.Offset, Limit: i.Limit},
89 90
 		Order:  sorting,
90 91
 		Filter: filter,
91
-	}, nil
92
+	}
93
+	parserDebug("InputQuery %#v", i)
94
+	return nil
95
+}
96
+
97
+//this doesn't return an error. If the query doesn't parse, then this will panic.
98
+func (iq *InputQuery) Query(api *Api, ident Identity) *Query {
99
+	if err := iq.Parse(); err != nil {
100
+		panic(err)
101
+	}
102
+	return &Query{
103
+		Page:   iq.baseQuery.Page,
104
+		Order:  iq.baseQuery.Order,
105
+		Filter: api.GetPolicyFilter(iq.baseQuery.Filter, ident, iq.mine, iq.shared, iq.public, iq.no_deleted),
106
+	}
107
+
92 108
 }
93 109
 
94 110
 func ParseFilter(input string) (index.Filter, error) {

+ 23
- 0
vendor.yml View File

@@ -0,0 +1,23 @@
1
+vendors:
2
+- path: github.com/disintegration/imaging
3
+  rev: d8bbae1de109b518dabc98c6c1633eb358c148a4
4
+- path: github.com/golang/protobuf
5
+  rev: 8d92cf5fc15a4382f8964b08e1f42a75c0591aa3
6
+- path: github.com/gorilla/context
7
+  rev: 1ea25387ff6f684839d82767c1733ff4d4d15d0a
8
+- path: github.com/gorilla/mux
9
+  rev: 0eeaf8392f5b04950925b8a69fe70f110fa7cbfc
10
+- path: github.com/gorilla/securecookie
11
+  rev: 8dacca26977607e637262eb66b15b7d39f2d3009
12
+- path: github.com/gorilla/sessions
13
+  rev: 8cd570d8b4ed84b18bca9d8c3ae2db55885ccd8b
14
+- path: github.com/rwcarlsen/goexif
15
+  rev: 709fab3d192d7c62f86043caff1e7e3fb0f42bd8
16
+- path: golang.org/x/image
17
+  rev: 7c492694a6443a92fd349fda0c4c7b587040f161
18
+- path: golang.org/x/net
19
+  rev: 4876518f9e71663000c348837735820161a42df7
20
+- path: golang.org/x/oauth2
21
+  rev: 045497edb6234273d67dbc25da3f2ddbc4c4cacf
22
+- path: google.golang.org/appengine
23
+  rev: 12d5545dc1cfa6047a286d5e853841b6471f4c19

+ 19
- 0
vendor/github.com/disintegration/imaging/.travis.yml View File

@@ -0,0 +1,19 @@
1
+language: go
2
+
3
+sudo: false
4
+
5
+go:
6
+  - 1.2
7
+  - 1.3
8
+  - 1.4
9
+  - 1.5
10
+  - 1.6
11
+  - tip
12
+
13
+before_install:
14
+  - go get golang.org/x/tools/cmd/cover
15
+  - go get github.com/mattn/goveralls
16
+
17
+script:
18
+  - go test -v -covermode=count -coverprofile=coverage.out
19
+  - $HOME/gopath/bin/goveralls -service=travis-ci -coverprofile=coverage.out 

+ 21
- 0
vendor/github.com/disintegration/imaging/LICENSE View File

@@ -0,0 +1,21 @@
1
+The MIT License (MIT)
2
+
3
+Copyright (c) 2012-2014 Grigory Dryapak
4
+
5
+Permission is hereby granted, free of charge, to any person obtaining a copy
6
+of this software and associated documentation files (the "Software"), to deal
7
+in the Software without restriction, including without limitation the rights
8
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+copies of the Software, and to permit persons to whom the Software is
10
+furnished to do so, 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,
17
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+SOFTWARE.

+ 198
- 0
vendor/github.com/disintegration/imaging/README.md View File

@@ -0,0 +1,198 @@
1
+# Imaging
2
+
3
+[![GoDoc](https://godoc.org/github.com/disintegration/imaging?status.svg)](https://godoc.org/github.com/disintegration/imaging)
4
+[![Build Status](https://travis-ci.org/disintegration/imaging.svg?branch=master)](https://travis-ci.org/disintegration/imaging)
5
+[![Coverage Status](https://coveralls.io/repos/github/disintegration/imaging/badge.svg?branch=master)](https://coveralls.io/github/disintegration/imaging?branch=master)
6
+
7
+Package imaging provides basic image manipulation functions (resize, rotate, flip, crop, etc.). 
8
+This package is based on the standard Go image package and works best along with it. 
9
+
10
+Image manipulation functions provided by the package take any image type 
11
+that implements `image.Image` interface as an input, and return a new image of 
12
+`*image.NRGBA` type (32bit RGBA colors, not premultiplied by alpha).
13
+
14
+## Installation
15
+
16
+Imaging requires Go version 1.2 or greater.
17
+
18
+    go get -u github.com/disintegration/imaging
19
+    
20
+## Documentation
21
+
22
+http://godoc.org/github.com/disintegration/imaging
23
+
24
+## Usage examples
25
+
26
+A few usage examples can be found below. See the documentation for the full list of supported functions. 
27
+
28
+### Image resizing
29
+```go
30
+// resize srcImage to size = 128x128px using the Lanczos filter
31
+dstImage128 := imaging.Resize(srcImage, 128, 128, imaging.Lanczos)
32
+
33
+// resize srcImage to width = 800px preserving the aspect ratio
34
+dstImage800 := imaging.Resize(srcImage, 800, 0, imaging.Lanczos)
35
+
36
+// scale down srcImage to fit the 800x600px bounding box
37
+dstImageFit := imaging.Fit(srcImage, 800, 600, imaging.Lanczos)
38
+
39
+// resize and crop the srcImage to fill the 100x100px area
40
+dstImageFill := imaging.Fill(srcImage, 100, 100, imaging.Center, imaging.Lanczos)
41
+```
42
+
43
+Imaging supports image resizing using various resampling filters. The most notable ones:
44
+- `NearestNeighbor` - Fastest resampling filter, no antialiasing.
45
+- `Box` - Simple and fast averaging filter appropriate for downscaling. When upscaling it's similar to NearestNeighbor.
46
+- `Linear` - Bilinear filter, smooth and reasonably fast.
47
+- `MitchellNetravali` - А smooth bicubic filter.
48
+- `CatmullRom` - A sharp bicubic filter. 
49
+- `Gaussian` - Blurring filter that uses gaussian function, useful for noise removal.
50
+- `Lanczos` - High-quality resampling filter for photographic images yielding sharp results, but it's slower than cubic filters.
51
+
52
+The full list of supported filters:  NearestNeighbor, Box, Linear, Hermite, MitchellNetravali, CatmullRom, BSpline, Gaussian, Lanczos, Hann, Hamming, Blackman, Bartlett, Welch, Cosine. Custom filters can be created using ResampleFilter struct.
53
+
54
+**Resampling filters comparison**
55
+
56
+Original image. Will be resized from 512x512px to 128x128px. 
57
+
58
+![srcImage](http://disintegration.github.io/imaging/in_lena_bw_512.png)
59
+
60
+Filter | Resize result
61
+---|---
62
+`imaging.NearestNeighbor` | ![dstImage](http://disintegration.github.io/imaging/out_resize_down_nearest.png) 
63
+`imaging.Box` | ![dstImage](http://disintegration.github.io/imaging/out_resize_down_box.png)
64
+`imaging.Linear` | ![dstImage](http://disintegration.github.io/imaging/out_resize_down_linear.png)
65
+`imaging.MitchellNetravali` | ![dstImage](http://disintegration.github.io/imaging/out_resize_down_mitchell.png)
66
+`imaging.CatmullRom` | ![dstImage](http://disintegration.github.io/imaging/out_resize_down_catrom.png)
67
+`imaging.Gaussian` | ![dstImage](http://disintegration.github.io/imaging/out_resize_down_gaussian.png)
68
+`imaging.Lanczos` | ![dstImage](http://disintegration.github.io/imaging/out_resize_down_lanczos.png)
69
+
70
+**Resize functions comparison**
71
+
72
+Original image:
73
+
74
+![srcImage](http://disintegration.github.io/imaging/in.jpg)
75
+
76
+Resize the image to width=100px and height=100px:
77
+
78
+```go
79
+dstImage := imaging.Resize(srcImage, 100, 100, imaging.Lanczos)
80
+```
81
+![dstImage](http://disintegration.github.io/imaging/out-comp-resize.jpg) 
82
+
83
+Resize the image to width=100px preserving the aspect ratio:
84
+
85
+```go
86
+dstImage := imaging.Resize(srcImage, 100, 0, imaging.Lanczos)
87
+```
88
+![dstImage](http://disintegration.github.io/imaging/out-comp-fit.jpg) 
89
+
90
+Resize the image to fit the 100x100px boundng box preserving the aspect ratio:
91
+
92
+```go
93
+dstImage := imaging.Fit(srcImage, 100, 100, imaging.Lanczos)
94
+```
95
+![dstImage](http://disintegration.github.io/imaging/out-comp-fit.jpg) 
96
+
97
+Resize and crop the image with a center anchor point to fill the 100x100px area:
98
+
99
+```go
100
+dstImage := imaging.Fill(srcImage, 100, 100, imaging.Center, imaging.Lanczos)
101
+```
102
+![dstImage](http://disintegration.github.io/imaging/out-comp-fill.jpg) 
103
+
104
+### Gaussian Blur
105
+```go
106
+dstImage := imaging.Blur(srcImage, 0.5)
107
+```
108
+
109
+Sigma parameter allows to control the strength of the blurring effect.
110
+
111
+Original image | Sigma = 0.5 | Sigma = 1.5
112
+---|---|---
113
+![srcImage](http://disintegration.github.io/imaging/in_lena_bw_128.png) | ![dstImage](http://disintegration.github.io/imaging/out_blur_0.5.png) | ![dstImage](http://disintegration.github.io/imaging/out_blur_1.5.png)
114
+
115
+### Sharpening
116
+```go
117
+dstImage := imaging.Sharpen(srcImage, 0.5)
118
+```
119
+
120
+Uses gaussian function internally. Sigma parameter allows to control the strength of the sharpening effect.
121
+
122
+Original image | Sigma = 0.5 | Sigma = 1.5
123
+---|---|---
124
+![srcImage](http://disintegration.github.io/imaging/in_lena_bw_128.png) | ![dstImage](http://disintegration.github.io/imaging/out_sharpen_0.5.png) | ![dstImage](http://disintegration.github.io/imaging/out_sharpen_1.5.png)
125
+
126
+### Gamma correction
127
+```go
128
+dstImage := imaging.AdjustGamma(srcImage, 0.75)
129
+```
130
+
131
+Original image | Gamma = 0.75 | Gamma = 1.25
132
+---|---|---
133
+![srcImage](http://disintegration.github.io/imaging/in_lena_bw_128.png) | ![dstImage](http://disintegration.github.io/imaging/out_gamma_0.75.png) | ![dstImage](http://disintegration.github.io/imaging/out_gamma_1.25.png)
134
+
135
+### Contrast adjustment
136
+```go
137
+dstImage := imaging.AdjustContrast(srcImage, 20)
138
+```
139
+
140
+Original image | Contrast = 20 | Contrast = -20
141
+---|---|---
142
+![srcImage](http://disintegration.github.io/imaging/in_lena_bw_128.png) | ![dstImage](http://disintegration.github.io/imaging/out_contrast_p20.png) | ![dstImage](http://disintegration.github.io/imaging/out_contrast_m20.png)
143
+
144
+### Brightness adjustment
145
+```go
146
+dstImage := imaging.AdjustBrightness(srcImage, 20)
147
+```
148
+
149
+Original image | Brightness = 20 | Brightness = -20
150
+---|---|---
151
+![srcImage](http://disintegration.github.io/imaging/in_lena_bw_128.png) | ![dstImage](http://disintegration.github.io/imaging/out_brightness_p20.png) | ![dstImage](http://disintegration.github.io/imaging/out_brightness_m20.png)
152
+
153
+
154
+### Complete code example
155
+Here is the code example that loads several images, makes thumbnails of them
156
+and combines them together side-by-side.
157
+
158
+```go
159
+package main
160
+
161
+import (
162
+    "image"
163
+    "image/color"
164
+    
165
+    "github.com/disintegration/imaging"
166
+)
167
+
168
+func main() {
169
+
170
+    // input files
171
+    files := []string{"01.jpg", "02.jpg", "03.jpg"}
172
+
173
+    // load images and make 100x100 thumbnails of them
174
+    var thumbnails []image.Image
175
+    for _, file := range files {
176
+        img, err := imaging.Open(file)
177
+        if err != nil {
178
+            panic(err)
179
+        }
180
+        thumb := imaging.Thumbnail(img, 100, 100, imaging.CatmullRom)
181
+        thumbnails = append(thumbnails, thumb)
182
+    }
183
+
184
+    // create a new blank image
185
+    dst := imaging.New(100*len(thumbnails), 100, color.NRGBA{0, 0, 0, 0})
186
+
187
+    // paste thumbnails into the new image side by side
188
+    for i, thumb := range thumbnails {
189
+        dst = imaging.Paste(dst, thumb, image.Pt(i*100, 0))
190
+    }
191
+
192
+    // save the combined image to file
193
+    err := imaging.Save(dst, "dst.jpg")
194
+    if err != nil {
195
+        panic(err)
196
+    }
197
+}
198
+```

+ 200
- 0
vendor/github.com/disintegration/imaging/adjust.go View File

@@ -0,0 +1,200 @@
1
+package imaging
2
+
3
+import (
4
+	"image"
5
+	"image/color"
6
+	"math"
7
+)
8
+
9
+// AdjustFunc applies the fn function to each pixel of the img image and returns the adjusted image.
10
+//
11
+// Example:
12
+//
13
+// 	dstImage = imaging.AdjustFunc(
14
+// 		srcImage,
15
+// 		func(c color.NRGBA) color.NRGBA {
16
+// 			// shift the red channel by 16
17
+//			r := int(c.R) + 16
18
+//			if r > 255 {
19
+// 				r = 255
20
+// 			}
21
+// 			return color.NRGBA{uint8(r), c.G, c.B, c.A}
22
+// 		}
23
+// 	)
24
+//
25
+func AdjustFunc(img image.Image, fn func(c color.NRGBA) color.NRGBA) *image.NRGBA {
26
+	src := toNRGBA(img)
27
+	width := src.Bounds().Max.X
28
+	height := src.Bounds().Max.Y
29
+	dst := image.NewNRGBA(image.Rect(0, 0, width, height))
30
+
31
+	parallel(height, func(partStart, partEnd int) {
32
+		for y := partStart; y < partEnd; y++ {
33
+			for x := 0; x < width; x++ {
34
+				i := y*src.Stride + x*4
35
+				j := y*dst.Stride + x*4
36
+
37
+				r := src.Pix[i+0]
38
+				g := src.Pix[i+1]
39
+				b := src.Pix[i+2]
40
+				a := src.Pix[i+3]
41
+
42
+				c := fn(color.NRGBA{r, g, b, a})
43
+
44
+				dst.Pix[j+0] = c.R
45
+				dst.Pix[j+1] = c.G
46
+				dst.Pix[j+2] = c.B
47
+				dst.Pix[j+3] = c.A
48
+			}
49
+		}
50
+	})
51
+
52
+	return dst
53
+}
54
+
55
+// AdjustGamma performs a gamma correction on the image and returns the adjusted image.
56
+// Gamma parameter must be positive. Gamma = 1.0 gives the original image.
57
+// Gamma less than 1.0 darkens the image and gamma greater than 1.0 lightens it.
58
+//
59
+// Example:
60
+//
61
+//	dstImage = imaging.AdjustGamma(srcImage, 0.7)
62
+//
63
+func AdjustGamma(img image.Image, gamma float64) *image.NRGBA {
64
+	e := 1.0 / math.Max(gamma, 0.0001)
65
+	lut := make([]uint8, 256)
66
+
67
+	for i := 0; i < 256; i++ {
68
+		lut[i] = clamp(math.Pow(float64(i)/255.0, e) * 255.0)
69
+	}
70
+
71
+	fn := func(c color.NRGBA) color.NRGBA {
72
+		return color.NRGBA{lut[c.R], lut[c.G], lut[c.B], c.A}
73
+	}
74
+
75
+	return AdjustFunc(img, fn)
76
+}
77
+
78
+func sigmoid(a, b, x float64) float64 {
79
+	return 1 / (1 + math.Exp(b*(a-x)))
80
+}
81
+
82
+// AdjustSigmoid changes the contrast of the image using a sigmoidal function and returns the adjusted image.
83
+// It's a non-linear contrast change useful for photo adjustments as it preserves highlight and shadow detail.
84
+// The midpoint parameter is the midpoint of contrast that must be between 0 and 1, typically 0.5.
85
+// The factor parameter indicates how much to increase or decrease the contrast, typically in range (-10, 10).
86
+// If the factor parameter is positive the image contrast is increased otherwise the contrast is decreased.
87
+//
88
+// Examples:
89
+//
90
+//	dstImage = imaging.AdjustSigmoid(srcImage, 0.5, 3.0) // increase the contrast
91
+//	dstImage = imaging.AdjustSigmoid(srcImage, 0.5, -3.0) // decrease the contrast
92
+//
93
+func AdjustSigmoid(img image.Image, midpoint, factor float64) *image.NRGBA {
94
+	if factor == 0 {
95
+		return Clone(img)
96
+	}
97
+
98
+	lut := make([]uint8, 256)
99
+	a := math.Min(math.Max(midpoint, 0.0), 1.0)
100
+	b := math.Abs(factor)
101
+	sig0 := sigmoid(a, b, 0)
102
+	sig1 := sigmoid(a, b, 1)
103
+	e := 1.0e-6
104
+
105
+	if factor > 0 {
106
+		for i := 0; i < 256; i++ {
107
+			x := float64(i) / 255.0
108
+			sigX := sigmoid(a, b, x)
109
+			f := (sigX - sig0) / (sig1 - sig0)
110
+			lut[i] = clamp(f * 255.0)
111
+		}
112
+	} else {
113
+		for i := 0; i < 256; i++ {
114
+			x := float64(i) / 255.0
115
+			arg := math.Min(math.Max((sig1-sig0)*x+sig0, e), 1.0-e)
116
+			f := a - math.Log(1.0/arg-1.0)/b
117
+			lut[i] = clamp(f * 255.0)
118
+		}
119
+	}
120
+
121
+	fn := func(c color.NRGBA) color.NRGBA {
122
+		return color.NRGBA{lut[c.R], lut[c.G], lut[c.B], c.A}
123
+	}
124
+
125
+	return AdjustFunc(img, fn)
126
+}
127
+
128
+// AdjustContrast changes the contrast of the image using the percentage parameter and returns the adjusted image.
129
+// The percentage must be in range (-100, 100). The percentage = 0 gives the original image.
130
+// The percentage = -100 gives solid grey image.
131
+//
132
+// Examples:
133
+//
134
+//	dstImage = imaging.AdjustContrast(srcImage, -10) // decrease image contrast by 10%
135
+//	dstImage = imaging.AdjustContrast(srcImage, 20) // increase image contrast by 20%
136
+//
137
+func AdjustContrast(img image.Image, percentage float64) *image.NRGBA {
138
+	percentage = math.Min(math.Max(percentage, -100.0), 100.0)
139
+	lut := make([]uint8, 256)
140
+
141
+	v := (100.0 + percentage) / 100.0
142
+	for i := 0; i < 256; i++ {
143
+		if 0 <= v && v <= 1 {
144
+			lut[i] = clamp((0.5 + (float64(i)/255.0-0.5)*v) * 255.0)
145
+		} else if 1 < v && v < 2 {
146
+			lut[i] = clamp((0.5 + (float64(i)/255.0-0.5)*(1/(2.0-v))) * 255.0)
147
+		} else {
148
+			lut[i] = uint8(float64(i)/255.0+0.5) * 255
149
+		}
150
+	}
151
+
152
+	fn := func(c color.NRGBA) color.NRGBA {
153
+		return color.NRGBA{lut[c.R], lut[c.G], lut[c.B], c.A}
154
+	}
155
+
156
+	return AdjustFunc(img, fn)
157
+}
158
+
159
+// AdjustBrightness changes the brightness of the image using the percentage parameter and returns the adjusted image.
160
+// The percentage must be in range (-100, 100). The percentage = 0 gives the original image.
161
+// The percentage = -100 gives solid black image. The percentage = 100 gives solid white image.
162
+//
163
+// Examples:
164
+//
165
+//	dstImage = imaging.AdjustBrightness(srcImage, -15) // decrease image brightness by 15%
166
+//	dstImage = imaging.AdjustBrightness(srcImage, 10) // increase image brightness by 10%
167
+//
168
+func AdjustBrightness(img image.Image, percentage float64) *image.NRGBA {
169
+	percentage = math.Min(math.Max(percentage, -100.0), 100.0)
170
+	lut := make([]uint8, 256)
171
+
172
+	shift := 255.0 * percentage / 100.0
173
+	for i := 0; i < 256; i++ {
174
+		lut[i] = clamp(float64(i) + shift)
175
+	}
176
+
177
+	fn := func(c color.NRGBA) color.NRGBA {
178
+		return color.NRGBA{lut[c.R], lut[c.G], lut[c.B], c.A}
179
+	}
180
+
181
+	return AdjustFunc(img, fn)
182
+}
183
+
184
+// Grayscale produces grayscale version of the image.
185
+func Grayscale(img image.Image) *image.NRGBA {
186
+	fn := func(c color.NRGBA) color.NRGBA {
187
+		f := 0.299*float64(c.R) + 0.587*float64(c.G) + 0.114*float64(c.B)
188
+		y := uint8(f + 0.5)
189
+		return color.NRGBA{y, y, y, c.A}
190
+	}
191
+	return AdjustFunc(img, fn)
192
+}
193
+
194
+// Invert produces inverted (negated) version of the image.
195
+func Invert(img image.Image) *image.NRGBA {
196
+	fn := func(c color.NRGBA) color.NRGBA {
197
+		return color.NRGBA{255 - c.R, 255 - c.G, 255 - c.B, c.A}
198
+	}
199
+	return AdjustFunc(img, fn)
200
+}

+ 504
- 0
vendor/github.com/disintegration/imaging/adjust_test.go View File

@@ -0,0 +1,504 @@
1
+package imaging
2
+
3
+import (
4
+	"image"
5
+	"testing"
6
+)
7
+
8
+func TestGrayscale(t *testing.T) {
9
+	td := []struct {
10
+		desc string
11
+		src  image.Image
12
+		want *image.NRGBA
13
+	}{
14
+		{
15
+			"Grayscale 3x3",
16
+			&image.NRGBA{
17
+				Rect:   image.Rect(-1, -1, 2, 2),
18
+				Stride: 3 * 4,
19
+				Pix: []uint8{
20
+					0xcc, 0x00, 0x00, 0x01, 0x00, 0xcc, 0x00, 0x02, 0x00, 0x00, 0xcc, 0x03,
21
+					0x11, 0x22, 0x33, 0xff, 0x33, 0x22, 0x11, 0xff, 0xaa, 0x33, 0xbb, 0xff,
22
+					0x00, 0x00, 0x00, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
23
+				},
24
+			},
25
+			&image.NRGBA{
26
+				Rect:   image.Rect(0, 0, 3, 3),
27
+				Stride: 3 * 4,
28
+				Pix: []uint8{
29
+					0x3d, 0x3d, 0x3d, 0x01, 0x78, 0x78, 0x78, 0x02, 0x17, 0x17, 0x17, 0x03,
30
+					0x1f, 0x1f, 0x1f, 0xff, 0x25, 0x25, 0x25, 0xff, 0x66, 0x66, 0x66, 0xff,
31
+					0x00, 0x00, 0x00, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
32
+				},
33
+			},
34
+		},
35
+	}
36
+	for _, d := range td {
37
+		got := Grayscale(d.src)
38
+		want := d.want
39
+		if !compareNRGBA(got, want, 0) {
40
+			t.Errorf("test [%s] failed: %#v", d.desc, got)
41
+		}
42
+	}
43
+}
44
+
45
+func TestInvert(t *testing.T) {
46
+	td := []struct {
47
+		desc string
48
+		src  image.Image
49
+		want *image.NRGBA
50
+	}{
51
+		{
52
+			"Invert 3x3",
53
+			&image.NRGBA{
54
+				Rect:   image.Rect(-1, -1, 2, 2),
55
+				Stride: 3 * 4,
56
+				Pix: []uint8{
57
+					0xcc, 0x00, 0x00, 0x01, 0x00, 0xcc, 0x00, 0x02, 0x00, 0x00, 0xcc, 0x03,
58
+					0x11, 0x22, 0x33, 0xff, 0x33, 0x22, 0x11, 0xff, 0xaa, 0x33, 0xbb, 0xff,
59
+					0x00, 0x00, 0x00, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
60
+				},
61
+			},
62
+			&image.NRGBA{
63
+				Rect:   image.Rect(0, 0, 3, 3),
64
+				Stride: 3 * 4,
65
+				Pix: []uint8{
66
+					0x33, 0xff, 0xff, 0x01, 0xff, 0x33, 0xff, 0x02, 0xff, 0xff, 0x33, 0x03,
67
+					0xee, 0xdd, 0xcc, 0xff, 0xcc, 0xdd, 0xee, 0xff, 0x55, 0xcc, 0x44, 0xff,
68
+					0xff, 0xff, 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0xff, 0x00, 0x00, 0x00, 0xff,
69
+				},
70
+			},
71
+		},
72
+	}
73
+	for _, d := range td {
74
+		got := Invert(d.src)
75
+		want := d.want
76
+		if !compareNRGBA(got, want, 0) {
77
+			t.Errorf("test [%s] failed: %#v", d.desc, got)
78
+		}
79
+	}
80
+}
81
+
82
+func TestAdjustContrast(t *testing.T) {
83
+	td := []struct {
84
+		desc string
85
+		src  image.Image
86
+		p    float64
87
+		want *image.NRGBA
88
+	}{
89
+		{
90
+			"AdjustContrast 3x3 10",
91
+			&image.NRGBA{
92
+				Rect:   image.Rect(-1, -1, 2, 2),
93
+				Stride: 3 * 4,
94
+				Pix: []uint8{
95
+					0xcc, 0x00, 0x00, 0x01, 0x00, 0xcc, 0x00, 0x02, 0x00, 0x00, 0xcc, 0x03,
96
+					0x11, 0x22, 0x33, 0xff, 0x33, 0x22, 0x11, 0xff, 0xaa, 0x33, 0xbb, 0xff,
97
+					0x00, 0x00, 0x00, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
98
+				},
99
+			},
100
+			10,
101
+			&image.NRGBA{
102
+				Rect:   image.Rect(0, 0, 3, 3),
103
+				Stride: 3 * 4,
104
+				Pix: []uint8{
105
+					0xd5, 0x00, 0x00, 0x01, 0x00, 0xd5, 0x00, 0x02, 0x00, 0x00, 0xd5, 0x03,
106
+					0x05, 0x18, 0x2b, 0xff, 0x2b, 0x18, 0x05, 0xff, 0xaf, 0x2b, 0xc2, 0xff,
107
+					0x00, 0x00, 0x00, 0xff, 0x2b, 0x2b, 0x2b, 0xff, 0xff, 0xff, 0xff, 0xff,
108
+				},
109
+			},
110
+		},
111
+		{
112
+			"AdjustContrast 3x3 100",
113
+			&image.NRGBA{
114
+				Rect:   image.Rect(-1, -1, 2, 2),
115
+				Stride: 3 * 4,
116
+				Pix: []uint8{
117
+					0xcc, 0x00, 0x00, 0x01, 0x00, 0xcc, 0x00, 0x02, 0x00, 0x00, 0xcc, 0x03,
118
+					0x11, 0x22, 0x33, 0xff, 0x33, 0x22, 0x11, 0xff, 0xaa, 0x33, 0xbb, 0xff,
119
+					0x00, 0x00, 0x00, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
120
+				},
121
+			},
122
+			100,
123
+			&image.NRGBA{
124
+				Rect:   image.Rect(0, 0, 3, 3),
125
+				Stride: 3 * 4,
126
+				Pix: []uint8{
127
+					0xff, 0x00, 0x00, 0x01, 0x00, 0xff, 0x00, 0x02, 0x00, 0x00, 0xff, 0x03,
128
+					0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0xff, 0xff,
129
+					0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff,
130
+				},
131
+			},
132
+		},
133
+		{
134
+			"AdjustContrast 3x3 -10",
135
+			&image.NRGBA{
136
+				Rect:   image.Rect(-1, -1, 2, 2),
137
+				Stride: 3 * 4,
138
+				Pix: []uint8{
139
+					0xcc, 0x00, 0x00, 0x01, 0x00, 0xcc, 0x00, 0x02, 0x00, 0x00, 0xcc, 0x03,
140
+					0x11, 0x22, 0x33, 0xff, 0x33, 0x22, 0x11, 0xff, 0xaa, 0x33, 0xbb, 0xff,
141
+					0x00, 0x00, 0x00, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
142
+				},
143
+			},
144
+			-10,
145
+			&image.NRGBA{
146
+				Rect:   image.Rect(0, 0, 3, 3),
147
+				Stride: 3 * 4,
148
+				Pix: []uint8{
149
+					0xc4, 0x0d, 0x0d, 0x01, 0x0d, 0xc4, 0x0d, 0x02, 0x0d, 0x0d, 0xc4, 0x03,
150
+					0x1c, 0x2b, 0x3b, 0xff, 0x3b, 0x2b, 0x1c, 0xff, 0xa6, 0x3b, 0xb5, 0xff,
151
+					0x0d, 0x0d, 0x0d, 0xff, 0x3b, 0x3b, 0x3b, 0xff, 0xf2, 0xf2, 0xf2, 0xff,
152
+				},
153
+			},
154
+		},
155
+		{
156
+			"AdjustContrast 3x3 -100",
157
+			&image.NRGBA{
158
+				Rect:   image.Rect(-1, -1, 2, 2),
159
+				Stride: 3 * 4,
160
+				Pix: []uint8{
161
+					0xcc, 0x00, 0x00, 0x01, 0x00, 0xcc, 0x00, 0x02, 0x00, 0x00, 0xcc, 0x03,
162
+					0x11, 0x22, 0x33, 0xff, 0x33, 0x22, 0x11, 0xff, 0xaa, 0x33, 0xbb, 0xff,
163
+					0x00, 0x00, 0x00, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
164
+				},
165
+			},
166
+			-100,
167
+			&image.NRGBA{
168
+				Rect:   image.Rect(0, 0, 3, 3),
169
+				Stride: 3 * 4,
170
+				Pix: []uint8{
171
+					0x80, 0x80, 0x80, 0x01, 0x80, 0x80, 0x80, 0x02, 0x80, 0x80, 0x80, 0x03,
172
+					0x80, 0x80, 0x80, 0xff, 0x80, 0x80, 0x80, 0xff, 0x80, 0x80, 0x80, 0xff,
173
+					0x80, 0x80, 0x80, 0xff, 0x80, 0x80, 0x80, 0xff, 0x80, 0x80, 0x80, 0xff,
174
+				},
175
+			},
176
+		},
177
+		{
178
+			"AdjustContrast 3x3 0",
179
+			&image.NRGBA{
180
+				Rect:   image.Rect(-1, -1, 2, 2),
181
+				Stride: 3 * 4,
182
+				Pix: []uint8{
183
+					0xcc, 0x00, 0x00, 0x01, 0x00, 0xcc, 0x00, 0x02, 0x00, 0x00, 0xcc, 0x03,
184
+					0x11, 0x22, 0x33, 0xff, 0x33, 0x22, 0x11, 0xff, 0xaa, 0x33, 0xbb, 0xff,
185
+					0x00, 0x00, 0x00, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
186
+				},
187
+			},
188
+			0,
189
+			&image.NRGBA{
190
+				Rect:   image.Rect(0, 0, 3, 3),
191
+				Stride: 3 * 4,
192
+				Pix: []uint8{
193
+					0xcc, 0x00, 0x00, 0x01, 0x00, 0xcc, 0x00, 0x02, 0x00, 0x00, 0xcc, 0x03,
194
+					0x11, 0x22, 0x33, 0xff, 0x33, 0x22, 0x11, 0xff, 0xaa, 0x33, 0xbb, 0xff,
195
+					0x00, 0x00, 0x00, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
196
+				},
197
+			},
198
+		},
199
+	}
200
+	for _, d := range td {
201
+		got := AdjustContrast(d.src, d.p)
202
+		want := d.want
203
+		if !compareNRGBA(got, want, 0) {
204
+			t.Errorf("test [%s] failed: %#v", d.desc, got)
205
+		}
206
+	}
207
+}
208
+
209
+func TestAdjustBrightness(t *testing.T) {
210
+	td := []struct {
211
+		desc string
212
+		src  image.Image
213
+		p    float64
214
+		want *image.NRGBA
215
+	}{
216
+		{
217
+			"AdjustBrightness 3x3 10",
218
+			&image.NRGBA{
219
+				Rect:   image.Rect(-1, -1, 2, 2),
220
+				Stride: 3 * 4,
221
+				Pix: []uint8{
222
+					0xcc, 0x00, 0x00, 0x01, 0x00, 0xcc, 0x00, 0x02, 0x00, 0x00, 0xcc, 0x03,
223
+					0x11, 0x22, 0x33, 0xff, 0x33, 0x22, 0x11, 0xff, 0xaa, 0x33, 0xbb, 0xff,
224
+					0x00, 0x00, 0x00, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
225
+				},
226
+			},
227
+			10,
228
+			&image.NRGBA{
229
+				Rect:   image.Rect(0, 0, 3, 3),
230
+				Stride: 3 * 4,
231
+				Pix: []uint8{
232
+					0xe6, 0x1a, 0x1a, 0x01, 0x1a, 0xe6, 0x1a, 0x02, 0x1a, 0x1a, 0xe6, 0x03,
233
+					0x2b, 0x3c, 0x4d, 0xff, 0x4d, 0x3c, 0x2b, 0xff, 0xc4, 0x4d, 0xd5, 0xff,
234
+					0x1a, 0x1a, 0x1a, 0xff, 0x4d, 0x4d, 0x4d, 0xff, 0xff, 0xff, 0xff, 0xff,
235
+				},
236
+			},
237
+		},
238
+		{
239
+			"AdjustBrightness 3x3 100",
240
+			&image.NRGBA{
241
+				Rect:   image.Rect(-1, -1, 2, 2),
242
+				Stride: 3 * 4,
243
+				Pix: []uint8{
244
+					0xcc, 0x00, 0x00, 0x01, 0x00, 0xcc, 0x00, 0x02, 0x00, 0x00, 0xcc, 0x03,
245
+					0x11, 0x22, 0x33, 0xff, 0x33, 0x22, 0x11, 0xff, 0xaa, 0x33, 0xbb, 0xff,
246
+					0x00, 0x00, 0x00, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
247
+				},
248
+			},
249
+			100,
250
+			&image.NRGBA{
251
+				Rect:   image.Rect(0, 0, 3, 3),
252
+				Stride: 3 * 4,
253
+				Pix: []uint8{
254
+					0xff, 0xff, 0xff, 0x01, 0xff, 0xff, 0xff, 0x02, 0xff, 0xff, 0xff, 0x03,
255
+					0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
256
+					0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
257
+				},
258
+			},
259
+		},
260
+		{
261
+			"AdjustBrightness 3x3 -10",
262
+			&image.NRGBA{
263
+				Rect:   image.Rect(-1, -1, 2, 2),
264
+				Stride: 3 * 4,
265
+				Pix: []uint8{
266
+					0xcc, 0x00, 0x00, 0x01, 0x00, 0xcc, 0x00, 0x02, 0x00, 0x00, 0xcc, 0x03,
267
+					0x11, 0x22, 0x33, 0xff, 0x33, 0x22, 0x11, 0xff, 0xaa, 0x33, 0xbb, 0xff,
268
+					0x00, 0x00, 0x00, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
269
+				},
270
+			},
271
+			-10,
272
+			&image.NRGBA{
273
+				Rect:   image.Rect(0, 0, 3, 3),
274
+				Stride: 3 * 4,
275
+				Pix: []uint8{
276
+					0xb3, 0x00, 0x00, 0x01, 0x00, 0xb3, 0x00, 0x02, 0x00, 0x00, 0xb3, 0x03,
277
+					0x00, 0x09, 0x1a, 0xff, 0x1a, 0x09, 0x00, 0xff, 0x91, 0x1a, 0xa2, 0xff,
278
+					0x00, 0x00, 0x00, 0xff, 0x1a, 0x1a, 0x1a, 0xff, 0xe6, 0xe6, 0xe6, 0xff,
279
+				},
280
+			},
281
+		},
282
+		{
283
+			"AdjustBrightness 3x3 -100",
284
+			&image.NRGBA{
285
+				Rect:   image.Rect(-1, -1, 2, 2),
286
+				Stride: 3 * 4,
287
+				Pix: []uint8{
288
+					0xcc, 0x00, 0x00, 0x01, 0x00, 0xcc, 0x00, 0x02, 0x00, 0x00, 0xcc, 0x03,
289
+					0x11, 0x22, 0x33, 0xff, 0x33, 0x22, 0x11, 0xff, 0xaa, 0x33, 0xbb, 0xff,
290
+					0x00, 0x00, 0x00, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
291
+				},
292
+			},
293
+			-100,
294
+			&image.NRGBA{
295
+				Rect:   image.Rect(0, 0, 3, 3),
296
+				Stride: 3 * 4,
297
+				Pix: []uint8{
298
+					0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03,
299
+					0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff,
300
+					0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff,
301
+				},
302
+			},
303
+		},
304
+		{
305
+			"AdjustBrightness 3x3 0",
306
+			&image.NRGBA{
307
+				Rect:   image.Rect(-1, -1, 2, 2),
308
+				Stride: 3 * 4,
309
+				Pix: []uint8{
310
+					0xcc, 0x00, 0x00, 0x01, 0x00, 0xcc, 0x00, 0x02, 0x00, 0x00, 0xcc, 0x03,
311
+					0x11, 0x22, 0x33, 0xff, 0x33, 0x22, 0x11, 0xff, 0xaa, 0x33, 0xbb, 0xff,
312
+					0x00, 0x00, 0x00, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
313
+				},
314
+			},
315
+			0,
316
+			&image.NRGBA{
317
+				Rect:   image.Rect(0, 0, 3, 3),
318
+				Stride: 3 * 4,
319
+				Pix: []uint8{
320
+					0xcc, 0x00, 0x00, 0x01, 0x00, 0xcc, 0x00, 0x02, 0x00, 0x00, 0xcc, 0x03,
321
+					0x11, 0x22, 0x33, 0xff, 0x33, 0x22, 0x11, 0xff, 0xaa, 0x33, 0xbb, 0xff,
322
+					0x00, 0x00, 0x00, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
323
+				},
324
+			},
325
+		},
326
+	}
327
+	for _, d := range td {
328
+		got := AdjustBrightness(d.src, d.p)
329
+		want := d.want
330
+		if !compareNRGBA(got, want, 0) {
331
+			t.Errorf("test [%s] failed: %#v", d.desc, got)
332
+		}
333
+	}
334
+}
335
+
336
+func TestAdjustGamma(t *testing.T) {
337
+	td := []struct {
338
+		desc string
339
+		src  image.Image
340
+		p    float64
341
+		want *image.NRGBA
342
+	}{
343
+		{
344
+			"AdjustGamma 3x3 0.75",
345
+			&image.NRGBA{
346
+				Rect:   image.Rect(-1, -1, 2, 2),
347
+				Stride: 3 * 4,
348
+				Pix: []uint8{
349
+					0xcc, 0x00, 0x00, 0x01, 0x00, 0xcc, 0x00, 0x02, 0x00, 0x00, 0xcc, 0x03,
350
+					0x11, 0x22, 0x33, 0xff, 0x33, 0x22, 0x11, 0xff, 0xaa, 0x33, 0xbb, 0xff,
351
+					0x00, 0x00, 0x00, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
352
+				},
353
+			},
354
+			0.75,
355
+			&image.NRGBA{
356
+				Rect:   image.Rect(0, 0, 3, 3),
357
+				Stride: 3 * 4,
358
+				Pix: []uint8{
359
+					0xbd, 0x00, 0x00, 0x01, 0x00, 0xbd, 0x00, 0x02, 0x00, 0x00, 0xbd, 0x03,
360
+					0x07, 0x11, 0x1e, 0xff, 0x1e, 0x11, 0x07, 0xff, 0x95, 0x1e, 0xa9, 0xff,
361
+					0x00, 0x00, 0x00, 0xff, 0x1e, 0x1e, 0x1e, 0xff, 0xff, 0xff, 0xff, 0xff,
362
+				},
363
+			},
364
+		},
365
+		{
366
+			"AdjustGamma 3x3 1.5",
367
+			&image.NRGBA{
368
+				Rect:   image.Rect(-1, -1, 2, 2),
369
+				Stride: 3 * 4,
370
+				Pix: []uint8{
371
+					0xcc, 0x00, 0x00, 0x01, 0x00, 0xcc, 0x00, 0x02, 0x00, 0x00, 0xcc, 0x03,
372
+					0x11, 0x22, 0x33, 0xff, 0x33, 0x22, 0x11, 0xff, 0xaa, 0x33, 0xbb, 0xff,
373
+					0x00, 0x00, 0x00, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
374
+				},
375
+			},
376
+			1.5,
377
+			&image.NRGBA{
378
+				Rect:   image.Rect(0, 0, 3, 3),
379
+				Stride: 3 * 4,
380
+				Pix: []uint8{
381
+					0xdc, 0x00, 0x00, 0x01, 0x00, 0xdc, 0x00, 0x02, 0x00, 0x00, 0xdc, 0x03,
382
+					0x2a, 0x43, 0x57, 0xff, 0x57, 0x43, 0x2a, 0xff, 0xc3, 0x57, 0xcf, 0xff,
383
+					0x00, 0x00, 0x00, 0xff, 0x57, 0x57, 0x57, 0xff, 0xff, 0xff, 0xff, 0xff,
384
+				},
385
+			},
386
+		},
387
+		{
388
+			"AdjustGamma 3x3 1.0",
389
+			&image.NRGBA{
390
+				Rect:   image.Rect(-1, -1, 2, 2),
391
+				Stride: 3 * 4,
392
+				Pix: []uint8{
393
+					0xcc, 0x00, 0x00, 0x01, 0x00, 0xcc, 0x00, 0x02, 0x00, 0x00, 0xcc, 0x03,
394
+					0x11, 0x22, 0x33, 0xff, 0x33, 0x22, 0x11, 0xff, 0xaa, 0x33, 0xbb, 0xff,
395
+					0x00, 0x00, 0x00, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
396
+				},
397
+			},
398
+			1.0,
399
+			&image.NRGBA{
400
+				Rect:   image.Rect(0, 0, 3, 3),
401
+				Stride: 3 * 4,
402
+				Pix: []uint8{
403
+					0xcc, 0x00, 0x00, 0x01, 0x00, 0xcc, 0x00, 0x02, 0x00, 0x00, 0xcc, 0x03,
404
+					0x11, 0x22, 0x33, 0xff, 0x33, 0x22, 0x11, 0xff, 0xaa, 0x33, 0xbb, 0xff,
405
+					0x00, 0x00, 0x00, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
406
+				},
407
+			},
408
+		},
409
+	}
410
+	for _, d := range td {
411
+		got := AdjustGamma(d.src, d.p)
412
+		want := d.want
413
+		if !compareNRGBA(got, want, 0) {
414
+			t.Errorf("test [%s] failed: %#v", d.desc, got)
415
+		}
416
+	}
417
+}
418
+
419
+func TestAdjustSigmoid(t *testing.T) {
420
+	td := []struct {
421
+		desc string
422
+		src  image.Image
423
+		m    float64
424
+		p    float64
425
+		want *image.NRGBA
426
+	}{
427
+		{
428
+			"AdjustSigmoid 3x3 0.5 3.0",
429
+			&image.NRGBA{
430
+				Rect:   image.Rect(-1, -1, 2, 2),
431
+				Stride: 3 * 4,
432
+				Pix: []uint8{
433
+					0xcc, 0x00, 0x00, 0x01, 0x00, 0xcc, 0x00, 0x02, 0x00, 0x00, 0xcc, 0x03,
434
+					0x11, 0x22, 0x33, 0xff, 0x33, 0x22, 0x11, 0xff, 0xaa, 0x33, 0xbb, 0xff,
435
+					0x00, 0x00, 0x00, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
436
+				},
437
+			},
438
+			0.5,
439
+			3.0,
440
+			&image.NRGBA{
441
+				Rect:   image.Rect(0, 0, 3, 3),
442
+				Stride: 3 * 4,
443
+				Pix: []uint8{
444
+					0xd4, 0x00, 0x00, 0x01, 0x00, 0xd4, 0x00, 0x02, 0x00, 0x00, 0xd4, 0x03,
445
+					0x0d, 0x1b, 0x2b, 0xff, 0x2b, 0x1b, 0x0d, 0xff, 0xb1, 0x2b, 0xc3, 0xff,
446
+					0x00, 0x00, 0x00, 0xff, 0x2b, 0x2b, 0x2b, 0xff, 0xff, 0xff, 0xff, 0xff,
447
+				},
448
+			},
449
+		},
450
+		{
451
+			"AdjustSigmoid 3x3 0.5 -3.0",
452
+			&image.NRGBA{
453
+				Rect:   image.Rect(-1, -1, 2, 2),
454
+				Stride: 3 * 4,
455
+				Pix: []uint8{
456
+					0xcc, 0x00, 0x00, 0x01, 0x00, 0xcc, 0x00, 0x02, 0x00, 0x00, 0xcc, 0x03,
457
+					0x11, 0x22, 0x33, 0xff, 0x33, 0x22, 0x11, 0xff, 0xaa, 0x33, 0xbb, 0xff,
458
+					0x00, 0x00, 0x00, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
459
+				},
460
+			},
461
+			0.5,
462
+			-3.0,
463
+			&image.NRGBA{
464
+				Rect:   image.Rect(0, 0, 3, 3),
465
+				Stride: 3 * 4,
466
+				Pix: []uint8{
467
+					0xc4, 0x00, 0x00, 0x01, 0x00, 0xc4, 0x00, 0x02, 0x00, 0x00, 0xc4, 0x03,
468
+					0x16, 0x2a, 0x3b, 0xff, 0x3b, 0x2a, 0x16, 0xff, 0xa4, 0x3b, 0xb3, 0xff,
469
+					0x00, 0x00, 0x00, 0xff, 0x3b, 0x3b, 0x3b, 0xff, 0xff, 0xff, 0xff, 0xff,
470
+				},
471
+			},
472
+		},
473
+		{
474
+			"AdjustSigmoid 3x3 0.5 0.0",
475
+			&image.NRGBA{
476
+				Rect:   image.Rect(-1, -1, 2, 2),
477
+				Stride: 3 * 4,
478
+				Pix: []uint8{
479
+					0xcc, 0x00, 0x00, 0x01, 0x00, 0xcc, 0x00, 0x02, 0x00, 0x00, 0xcc, 0x03,
480
+					0x11, 0x22, 0x33, 0xff, 0x33, 0x22, 0x11, 0xff, 0xaa, 0x33, 0xbb, 0xff,
481
+					0x00, 0x00, 0x00, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
482
+				},
483
+			},
484
+			0.5,
485
+			0.0,
486
+			&image.NRGBA{
487
+				Rect:   image.Rect(0, 0, 3, 3),
488
+				Stride: 3 * 4,
489
+				Pix: []uint8{
490
+					0xcc, 0x00, 0x00, 0x01, 0x00, 0xcc, 0x00, 0x02, 0x00, 0x00, 0xcc, 0x03,
491
+					0x11, 0x22, 0x33, 0xff, 0x33, 0x22, 0x11, 0xff, 0xaa, 0x33, 0xbb, 0xff,
492
+					0x00, 0x00, 0x00, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
493
+				},
494
+			},
495
+		},
496
+	}
497
+	for _, d := range td {
498
+		got := AdjustSigmoid(d.src, d.m, d.p)
499
+		want := d.want
500
+		if !compareNRGBA(got, want, 0) {
501
+			t.Errorf("test [%s] failed: %#v", d.desc, got)
502
+		}
503
+	}
504
+}

+ 187
- 0
vendor/github.com/disintegration/imaging/effects.go View File

@@ -0,0 +1,187 @@
1
+package imaging
2
+
3
+import (
4
+	"image"
5
+	"math"
6
+)
7
+
8
+func gaussianBlurKernel(x, sigma float64) float64 {
9
+	return math.Exp(-(x*x)/(2*sigma*sigma)) / (sigma * math.Sqrt(2*math.Pi))
10
+}
11
+
12
+// Blur produces a blurred version of the image using a Gaussian function.
13
+// Sigma parameter must be positive and indicates how much the image will be blurred.
14
+//
15
+// Usage example:
16
+//
17
+//		dstImage := imaging.Blur(srcImage, 3.5)
18
+//
19
+func Blur(img image.Image, sigma float64) *image.NRGBA {
20
+	if sigma <= 0 {
21
+		// sigma parameter must be positive!
22
+		return Clone(img)
23
+	}
24
+
25
+	src := toNRGBA(img)
26
+	radius := int(math.Ceil(sigma * 3.0))
27
+	kernel := make([]float64, radius+1)
28
+
29
+	for i := 0; i <= radius; i++ {
30
+		kernel[i] = gaussianBlurKernel(float64(i), sigma)
31
+	}
32
+
33
+	var dst *image.NRGBA
34
+	dst = blurHorizontal(src, kernel)
35
+	dst = blurVertical(dst, kernel)
36
+
37
+	return dst
38
+}
39
+
40
+func blurHorizontal(src *image.NRGBA, kernel []float64) *image.NRGBA {
41
+	radius := len(kernel) - 1
42
+	width := src.Bounds().Max.X
43
+	height := src.Bounds().Max.Y
44
+
45
+	dst := image.NewNRGBA(image.Rect(0, 0, width, height))
46
+
47
+	parallel(width, func(partStart, partEnd int) {
48
+		for x := partStart; x < partEnd; x++ {
49
+			start := x - radius
50
+			if start < 0 {
51
+				start = 0
52
+			}
53
+
54
+			end := x + radius
55
+			if end > width-1 {
56
+				end = width - 1
57
+			}
58
+
59
+			weightSum := 0.0
60
+			for ix := start; ix <= end; ix++ {
61
+				weightSum += kernel[absint(x-ix)]
62
+			}
63
+
64
+			for y := 0; y < height; y++ {
65
+
66
+				r, g, b, a := 0.0, 0.0, 0.0, 0.0
67
+				for ix := start; ix <= end; ix++ {
68
+					weight := kernel[absint(x-ix)]
69
+					i := y*src.Stride + ix*4
70
+					r += float64(src.Pix[i+0]) * weight
71
+					g += float64(src.Pix[i+1]) * weight
72
+					b += float64(src.Pix[i+2]) * weight
73
+					a += float64(src.Pix[i+3]) * weight
74
+				}
75
+
76
+				r = math.Min(math.Max(r/weightSum, 0.0), 255.0)
77
+				g = math.Min(math.Max(g/weightSum, 0.0), 255.0)
78
+				b = math.Min(math.Max(b/weightSum, 0.0), 255.0)
79
+				a = math.Min(math.Max(a/weightSum, 0.0), 255.0)
80
+
81
+				j := y*dst.Stride + x*4
82
+				dst.Pix[j+0] = uint8(r + 0.5)
83
+				dst.Pix[j+1] = uint8(g + 0.5)
84
+				dst.Pix[j+2] = uint8(b + 0.5)
85
+				dst.Pix[j+3] = uint8(a + 0.5)
86
+
87
+			}
88
+		}
89
+	})
90
+
91
+	return dst
92
+}
93
+
94
+func blurVertical(src *image.NRGBA, kernel []float64) *image.NRGBA {
95
+	radius := len(kernel) - 1
96
+	width := src.Bounds().Max.X
97
+	height := src.Bounds().Max.Y
98
+
99
+	dst := image.NewNRGBA(image.Rect(0, 0, width, height))
100
+
101
+	parallel(height, func(partStart, partEnd int) {
102
+		for y := partStart; y < partEnd; y++ {
103
+			start := y - radius
104
+			if start < 0 {
105
+				start = 0
106
+			}
107
+
108
+			end := y + radius
109
+			if end > height-1 {
110
+				end = height - 1
111
+			}
112
+
113
+			weightSum := 0.0
114
+			for iy := start; iy <= end; iy++ {
115
+				weightSum += kernel[absint(y-iy)]
116
+			}
117
+
118
+			for x := 0; x < width; x++ {
119
+
120
+				r, g, b, a := 0.0, 0.0, 0.0, 0.0
121
+				for iy := start; iy <= end; iy++ {
122
+					weight := kernel[absint(y-iy)]
123
+					i := iy*src.Stride + x*4
124
+					r += float64(src.Pix[i+0]) * weight
125
+					g += float64(src.Pix[i+1]) * weight
126
+					b += float64(src.Pix[i+2]) * weight
127
+					a += float64(src.Pix[i+3]) * weight
128
+				}
129
+
130
+				r = math.Min(math.Max(r/weightSum, 0.0), 255.0)
131
+				g = math.Min(math.Max(g/weightSum, 0.0), 255.0)
132
+				b = math.Min(math.Max(b/weightSum, 0.0), 255.0)
133
+				a = math.Min(math.Max(a/weightSum, 0.0), 255.0)
134
+
135
+				j := y*dst.Stride + x*4
136
+				dst.Pix[j+0] = uint8(r + 0.5)
137
+				dst.Pix[j+1] = uint8(g + 0.5)
138
+				dst.Pix[j+2] = uint8(b + 0.5)
139
+				dst.Pix[j+3] = uint8(a + 0.5)
140
+
141
+			}
142
+		}
143
+	})
144
+
145
+	return dst
146
+}
147
+
148
+// Sharpen produces a sharpened version of the image.
149
+// Sigma parameter must be positive and indicates how much the image will be sharpened.
150
+//
151
+// Usage example:
152
+//
153
+//		dstImage := imaging.Sharpen(srcImage, 3.5)
154
+//
155
+func Sharpen(img image.Image, sigma float64) *image.NRGBA {
156
+	if sigma <= 0 {
157
+		// sigma parameter must be positive!
158
+		return Clone(img)
159
+	}
160
+
161
+	src := toNRGBA(img)
162
+	blurred := Blur(img, sigma)
163
+
164
+	width := src.Bounds().Max.X
165
+	height := src.Bounds().Max.Y
166
+	dst := image.NewNRGBA(image.Rect(0, 0, width, height))
167
+
168
+	parallel(height, func(partStart, partEnd int) {
169
+		for y := partStart; y < partEnd; y++ {
170
+			for x := 0; x < width; x++ {
171
+				i := y*src.Stride + x*4
172
+				for j := 0; j < 4; j++ {
173
+					k := i + j
174
+					val := int(src.Pix[k]) + (int(src.Pix[k]) - int(blurred.Pix[k]))
175
+					if val < 0 {
176
+						val = 0
177
+					} else if val > 255 {
178
+						val = 255
179
+					}
180
+					dst.Pix[k] = uint8(val)
181
+				}
182
+			}
183
+		}
184
+	})
185
+
186
+	return dst
187
+}

+ 190
- 0
vendor/github.com/disintegration/imaging/effects_test.go View File

@@ -0,0 +1,190 @@
1
+package imaging
2
+
3
+import (
4
+	"image"
5
+	"testing"
6
+)
7
+
8
+func TestBlur(t *testing.T) {
9
+	td := []struct {
10
+		desc  string
11
+		src   image.Image
12
+		sigma float64
13
+		want  *image.NRGBA
14
+	}{
15
+		{
16
+			"Blur 3x3 0",
17
+			&image.NRGBA{
18
+				Rect:   image.Rect(-1, -1, 2, 2),
19
+				Stride: 3 * 4,
20
+				Pix: []uint8{
21
+					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
22
+					0x00, 0x00, 0x00, 0x00, 0x66, 0xaa, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
23
+					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
24
+				},
25
+			},
26
+			0.0,
27
+			&image.NRGBA{
28
+				Rect:   image.Rect(0, 0, 3, 3),
29
+				Stride: 3 * 4,
30
+				Pix: []uint8{
31
+					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
32
+					0x00, 0x00, 0x00, 0x00, 0x66, 0xaa, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
33
+					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
34
+				},
35
+			},
36
+		},
37
+		{
38
+			"Blur 3x3 0.5",
39
+			&image.NRGBA{
40
+				Rect:   image.Rect(-1, -1, 2, 2),
41
+				Stride: 3 * 4,
42
+				Pix: []uint8{
43
+					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
44
+					0x00, 0x00, 0x00, 0x00, 0x66, 0xaa, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
45
+					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
46
+				},
47
+			},
48
+			0.5,
49
+			&image.NRGBA{
50
+				Rect:   image.Rect(0, 0, 3, 3),
51
+				Stride: 3 * 4,
52
+				Pix: []uint8{
53
+					0x01, 0x02, 0x04, 0x04, 0x0a, 0x10, 0x18, 0x18, 0x01, 0x02, 0x04, 0x04,
54
+					0x09, 0x10, 0x18, 0x18, 0x3f, 0x69, 0x9e, 0x9e, 0x09, 0x10, 0x18, 0x18,
55
+					0x01, 0x02, 0x04, 0x04, 0x0a, 0x10, 0x18, 0x18, 0x01, 0x02, 0x04, 0x04,
56
+				},
57
+			},
58
+		},
59
+		{
60
+			"Blur 3x3 10",
61
+			&image.NRGBA{
62
+				Rect:   image.Rect(-1, -1, 2, 2),
63
+				Stride: 3 * 4,
64
+				Pix: []uint8{
65
+					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
66
+					0x00, 0x00, 0x00, 0x00, 0x66, 0xaa, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
67
+					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
68
+				},
69
+			},
70
+			10,
71
+			&image.NRGBA{
72
+				Rect:   image.Rect(0, 0, 3, 3),
73
+				Stride: 3 * 4,
74
+				Pix: []uint8{
75
+					0x0b, 0x13, 0x1c, 0x1c, 0x0b, 0x13, 0x1c, 0x1c, 0x0b, 0x13, 0x1c, 0x1c,
76
+					0x0b, 0x13, 0x1c, 0x1c, 0x0b, 0x13, 0x1c, 0x1c, 0x0b, 0x13, 0x1c, 0x1c,
77
+					0x0b, 0x13, 0x1c, 0x1c, 0x0b, 0x13, 0x1c, 0x1c, 0x0b, 0x13, 0x1c, 0x1c,
78
+				},
79
+			},
80
+		},
81
+	}
82
+	for _, d := range td {
83
+		got := Blur(d.src, d.sigma)
84
+		want := d.want
85
+		if !compareNRGBA(got, want, 0) {
86
+			t.Errorf("test [%s] failed: %#v", d.desc, got)
87
+		}
88
+	}
89
+}
90
+
91
+func TestSharpen(t *testing.T) {
92
+	td := []struct {
93
+		desc  string
94
+		src   image.Image
95
+		sigma float64
96
+		want  *image.NRGBA
97
+	}{
98
+		{
99
+			"Sharpen 3x3 0",
100
+			&image.NRGBA{
101
+				Rect:   image.Rect(-1, -1, 2, 2),
102
+				Stride: 3 * 4,
103
+				Pix: []uint8{
104
+					0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
105
+					0x66, 0x66, 0x66, 0x66, 0x77, 0x77, 0x77, 0x77, 0x66, 0x66, 0x66, 0x66,
106
+					0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
107
+				},
108
+			},
109
+			0,
110
+			&image.NRGBA{
111
+				Rect:   image.Rect(0, 0, 3, 3),
112
+				Stride: 3 * 4,
113
+				Pix: []uint8{
114
+					0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
115
+					0x66, 0x66, 0x66, 0x66, 0x77, 0x77, 0x77, 0x77, 0x66, 0x66, 0x66, 0x66,
116
+					0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
117
+				},
118
+			},
119
+		},
120
+		{
121
+			"Sharpen 3x3 0.5",
122
+			&image.NRGBA{
123
+				Rect:   image.Rect(-1, -1, 2, 2),
124
+				Stride: 3 * 4,
125
+				Pix: []uint8{
126
+					0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
127
+					0x66, 0x66, 0x66, 0x66, 0x77, 0x77, 0x77, 0x77, 0x66, 0x66, 0x66, 0x66,
128
+					0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
129
+				},
130
+			},
131
+			0.5,
132
+			&image.NRGBA{
133
+				Rect:   image.Rect(0, 0, 3, 3),
134
+				Stride: 3 * 4,
135
+				Pix: []uint8{
136
+					0x66, 0x66, 0x66, 0x66, 0x64, 0x64, 0x64, 0x64, 0x66, 0x66, 0x66, 0x66,
137
+					0x64, 0x64, 0x64, 0x64, 0x7e, 0x7e, 0x7e, 0x7e, 0x64, 0x64, 0x64, 0x64,
138
+					0x66, 0x66, 0x66, 0x66, 0x64, 0x64, 0x64, 0x64, 0x66, 0x66, 0x66, 0x66,
139
+				},
140
+			},
141
+		},
142
+		{
143
+			"Sharpen 3x3 100",
144
+			&image.NRGBA{
145
+				Rect:   image.Rect(-1, -1, 2, 2),
146
+				Stride: 3 * 4,
147
+				Pix: []uint8{
148
+					0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
149
+					0x66, 0x66, 0x66, 0x66, 0x77, 0x77, 0x77, 0x77, 0x66, 0x66, 0x66, 0x66,
150
+					0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
151
+				},
152
+			},
153
+			100,
154
+			&image.NRGBA{
155
+				Rect:   image.Rect(0, 0, 3, 3),
156
+				Stride: 3 * 4,
157
+				Pix: []uint8{
158
+					0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64,
159
+					0x64, 0x64, 0x64, 0x64, 0x86, 0x86, 0x86, 0x86, 0x64, 0x64, 0x64, 0x64,
160
+					0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64,
161
+				},
162
+			},
163
+		},
164
+		{
165
+			"Sharpen 3x1 10",
166
+			&image.NRGBA{
167
+				Rect:   image.Rect(-1, -1, 2, 0),
168
+				Stride: 3 * 4,
169
+				Pix: []uint8{
170
+					0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
171
+				},
172
+			},
173
+			10,
174
+			&image.NRGBA{
175
+				Rect:   image.Rect(0, 0, 3, 1),
176
+				Stride: 3 * 4,
177
+				Pix: []uint8{
178
+					0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
179
+				},
180
+			},
181
+		},
182
+	}
183
+	for _, d := range td {
184
+		got := Sharpen(d.src, d.sigma)
185
+		want := d.want
186
+		if !compareNRGBA(got, want, 0) {
187
+			t.Errorf("test [%s] failed: %#v", d.desc, got)
188
+		}
189
+	}
190
+}

+ 400
- 0
vendor/github.com/disintegration/imaging/helpers.go View File

@@ -0,0 +1,400 @@
1
+/*
2
+Package imaging provides basic image manipulation functions (resize, rotate, flip, crop, etc.).
3
+This package is based on the standard Go image package and works best along with it.
4
+
5
+Image manipulation functions provided by the package take any image type
6
+that implements `image.Image` interface as an input, and return a new image of
7
+`*image.NRGBA` type (32bit RGBA colors, not premultiplied by alpha).
8
+*/
9
+package imaging
10
+
11
+import (
12
+	"errors"
13
+	"image"
14
+	"image/color"
15
+	"image/gif"
16
+	"image/jpeg"
17
+	"image/png"
18
+	"io"
19
+	"os"
20
+	"path/filepath"
21
+	"strings"
22
+
23
+	"golang.org/x/image/bmp"
24
+	"golang.org/x/image/tiff"
25
+)
26
+
27
+type Format int
28
+
29
+const (
30
+	JPEG Format = iota
31
+	PNG
32
+	GIF
33
+	TIFF
34
+	BMP
35
+)
36
+
37
+func (f Format) String() string {
38
+	switch f {
39
+	case JPEG:
40
+		return "JPEG"
41
+	case PNG:
42
+		return "PNG"
43
+	case GIF:
44
+		return "GIF"
45
+	case TIFF:
46
+		return "TIFF"
47
+	case BMP:
48
+		return "BMP"
49
+	default:
50
+		return "Unsupported"
51
+	}
52
+}
53
+
54
+var (
55
+	ErrUnsupportedFormat = errors.New("imaging: unsupported image format")
56
+)
57
+
58
+// Decode reads an image from r.
59
+func Decode(r io.Reader) (image.Image, error) {
60
+	img, _, err := image.Decode(r)
61
+	if err != nil {
62
+		return nil, err
63
+	}
64
+	return toNRGBA(img), nil
65
+}
66
+
67
+// Open loads an image from file
68
+func Open(filename string) (image.Image, error) {
69
+	file, err := os.Open(filename)
70
+	if err != nil {
71
+		return nil, err
72
+	}
73
+	defer file.Close()
74
+	img, err := Decode(file)
75
+	return img, err
76
+}
77
+
78
+// Encode writes the image img to w in the specified format (JPEG, PNG, GIF, TIFF or BMP).
79
+func Encode(w io.Writer, img image.Image, format Format) error {
80
+	var err error
81
+	switch format {
82
+	case JPEG:
83
+		var rgba *image.RGBA
84
+		if nrgba, ok := img.(*image.NRGBA); ok {
85
+			if nrgba.Opaque() {
86
+				rgba = &image.RGBA{
87
+					Pix:    nrgba.Pix,
88
+					Stride: nrgba.Stride,
89
+					Rect:   nrgba.Rect,
90
+				}
91
+			}
92
+		}
93
+		if rgba != nil {
94
+			err = jpeg.Encode(w, rgba, &jpeg.Options{Quality: 95})
95
+		} else {
96
+			err = jpeg.Encode(w, img, &jpeg.Options{Quality: 95})
97
+		}
98
+
99
+	case PNG:
100
+		err = png.Encode(w, img)
101
+	case GIF:
102
+		err = gif.Encode(w, img, &gif.Options{NumColors: 256})
103
+	case TIFF:
104
+		err = tiff.Encode(w, img, &tiff.Options{Compression: tiff.Deflate, Predictor: true})
105
+	case BMP:
106
+		err = bmp.Encode(w, img)
107
+	default:
108
+		err = ErrUnsupportedFormat
109
+	}
110
+	return err
111
+}
112
+
113
+// Save saves the image to file with the specified filename.
114
+// The format is determined from the filename extension: "jpg" (or "jpeg"), "png", "gif", "tif" (or "tiff") and "bmp" are supported.
115
+func Save(img image.Image, filename string) (err error) {
116
+	formats := map[string]Format{
117
+		".jpg":  JPEG,
118
+		".jpeg": JPEG,
119
+		".png":  PNG,
120
+		".tif":  TIFF,
121
+		".tiff": TIFF,
122
+		".bmp":  BMP,
123
+		".gif":  GIF,
124
+	}
125
+
126
+	ext := strings.ToLower(filepath.Ext(filename))
127
+	f, ok := formats[ext]
128
+	if !ok {
129
+		return ErrUnsupportedFormat
130
+	}
131
+
132
+	file, err := os.Create(filename)
133
+	if err != nil {
134
+		return err
135
+	}
136
+	defer file.Close()
137
+
138
+	return Encode(file, img, f)
139
+}
140
+
141
+// New creates a new image with the specified width and height, and fills it with the specified color.
142
+func New(width, height int, fillColor color.Color) *image.NRGBA {
143
+	if width <= 0 || height <= 0 {
144
+		return &image.NRGBA{}
145
+	}
146
+
147
+	dst := image.NewNRGBA(image.Rect(0, 0, width, height))
148
+	c := color.NRGBAModel.Convert(fillColor).(color.NRGBA)
149
+
150
+	if c.R == 0 && c.G == 0 && c.B == 0 && c.A == 0 {
151
+		return dst
152
+	}
153
+
154
+	cs := []uint8{c.R, c.G, c.B, c.A}
155
+
156
+	// fill the first row
157
+	for x := 0; x < width; x++ {
158
+		copy(dst.Pix[x*4:(x+1)*4], cs)
159
+	}
160
+	// copy the first row to other rows
161
+	for y := 1; y < height; y++ {
162
+		copy(dst.Pix[y*dst.Stride:y*dst.Stride+width*4], dst.Pix[0:width*4])
163
+	}
164
+
165
+	return dst
166
+}
167
+
168
+// Clone returns a copy of the given image.
169
+func Clone(img image.Image) *image.NRGBA {
170
+	srcBounds := img.Bounds()
171
+	srcMinX := srcBounds.Min.X
172
+	srcMinY := srcBounds.Min.Y
173
+
174
+	dstBounds := srcBounds.Sub(srcBounds.Min)
175
+	dstW := dstBounds.Dx()
176
+	dstH := dstBounds.Dy()
177
+	dst := image.NewNRGBA(dstBounds)
178
+
179
+	switch src := img.(type) {
180
+
181
+	case *image.NRGBA:
182
+		rowSize := srcBounds.Dx() * 4
183
+		parallel(dstH, func(partStart, partEnd int) {
184
+			for dstY := partStart; dstY < partEnd; dstY++ {
185
+				di := dst.PixOffset(0, dstY)
186
+				si := src.PixOffset(srcMinX, srcMinY+dstY)
187
+				copy(dst.Pix[di:di+rowSize], src.Pix[si:si+rowSize])
188
+			}
189
+		})
190
+
191
+	case *image.NRGBA64:
192
+		parallel(dstH, func(partStart, partEnd int) {
193
+			for dstY := partStart; dstY < partEnd; dstY++ {
194
+				di := dst.PixOffset(0, dstY)
195
+				si := src.PixOffset(srcMinX, srcMinY+dstY)
196
+				for dstX := 0; dstX < dstW; dstX++ {
197
+
198
+					dst.Pix[di+0] = src.Pix[si+0]
199
+					dst.Pix[di+1] = src.Pix[si+2]
200
+					dst.Pix[di+2] = src.Pix[si+4]
201
+					dst.Pix[di+3] = src.Pix[si+6]
202
+
203
+					di += 4
204
+					si += 8
205
+
206
+				}
207
+			}
208
+		})
209
+
210
+	case *image.RGBA:
211
+		parallel(dstH, func(partStart, partEnd int) {
212
+			for dstY := partStart; dstY < partEnd; dstY++ {
213
+				di := dst.PixOffset(0, dstY)
214
+				si := src.PixOffset(srcMinX, srcMinY+dstY)
215
+				for dstX := 0; dstX < dstW; dstX++ {
216
+
217
+					a := src.Pix[si+3]
218
+					dst.Pix[di+3] = a
219
+					switch a {
220
+					case 0:
221
+						dst.Pix[di+0] = 0
222
+						dst.Pix[di+1] = 0
223
+						dst.Pix[di+2] = 0
224
+					case 0xff:
225
+						dst.Pix[di+0] = src.Pix[si+0]
226
+						dst.Pix[di+1] = src.Pix[si+1]
227
+						dst.Pix[di+2] = src.Pix[si+2]
228
+					default:
229
+						var tmp uint16
230
+						tmp = uint16(src.Pix[si+0]) * 0xff / uint16(a)
231
+						dst.Pix[di+0] = uint8(tmp)
232
+						tmp = uint16(src.Pix[si+1]) * 0xff / uint16(a)
233
+						dst.Pix[di+1] = uint8(tmp)
234
+						tmp = uint16(src.Pix[si+2]) * 0xff / uint16(a)
235
+						dst.Pix[di+2] = uint8(tmp)
236
+					}
237
+
238
+					di += 4
239
+					si += 4
240
+
241
+				}
242
+			}
243
+		})
244
+
245
+	case *image.RGBA64:
246
+		parallel(dstH, func(partStart, partEnd int) {
247
+			for dstY := partStart; dstY < partEnd; dstY++ {
248
+				di := dst.PixOffset(0, dstY)
249
+				si := src.PixOffset(srcMinX, srcMinY+dstY)
250
+				for dstX := 0; dstX < dstW; dstX++ {
251
+
252
+					a := src.Pix[si+6]
253
+					dst.Pix[di+3] = a
254
+					switch a {
255
+					case 0:
256
+						dst.Pix[di+0] = 0
257
+						dst.Pix[di+1] = 0
258
+						dst.Pix[di+2] = 0
259
+					case 0xff:
260
+						dst.Pix[di+0] = src.Pix[si+0]
261
+						dst.Pix[di+1] = src.Pix[si+2]
262
+						dst.Pix[di+2] = src.Pix[si+4]
263
+					default:
264
+						var tmp uint16
265
+						tmp = uint16(src.Pix[si+0]) * 0xff / uint16(a)
266
+						dst.Pix[di+0] = uint8(tmp)
267
+						tmp = uint16(src.Pix[si+2]) * 0xff / uint16(a)
268
+						dst.Pix[di+1] = uint8(tmp)
269
+						tmp = uint16(src.Pix[si+4]) * 0xff / uint16(a)
270
+						dst.Pix[di+2] = uint8(tmp)
271
+					}
272
+
273
+					di += 4
274
+					si += 8
275
+
276
+				}
277
+			}
278
+		})