smartcrop-rails 0.1.12 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/smartcrop/rails/version.rb +1 -1
- data/vendor/assets/javascripts/smartcrop.js +496 -337
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: af7d9f17f5b2d5c660ec32c3d4383ec3f92773e7
|
4
|
+
data.tar.gz: b9fd2c1551321cfc99adee8da45c71601c5103f8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 430abe02c079fd9d1a2f78ab9c4dd7f2f1bbf5ad5cedeb1634c883928c3c68fee1ac11b6b21ca78d16b6c1da05e02dfc102b2c7f715d1f96858ca99119bc0784
|
7
|
+
data.tar.gz: c0a8abae042233c194064153f226c6a7225c339a5dae6829723465f0bca1a749c161f7d20491e96847b9f217ad77df960ed2e5970c3d4722558ee769b4a03099
|
@@ -1,7 +1,8 @@
|
|
1
|
-
/**
|
1
|
+
/**
|
2
|
+
* smartcrop.js
|
2
3
|
* A javascript library implementing content aware image cropping
|
3
4
|
*
|
4
|
-
* Copyright (C)
|
5
|
+
* Copyright (C) 2016 Jonas Wagner
|
5
6
|
*
|
6
7
|
* Permission is hereby granted, free of charge, to any person obtaining
|
7
8
|
* a copy of this software and associated documentation files (the
|
@@ -23,369 +24,527 @@
|
|
23
24
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
24
25
|
*/
|
25
26
|
|
26
|
-
(function(){
|
27
|
-
|
27
|
+
(function() {
|
28
|
+
'use strict';
|
28
29
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
30
|
+
var smartcrop = {};
|
31
|
+
// Promise implementation to use
|
32
|
+
smartcrop.Promise = typeof Promise !== 'undefined' ? Promise : function() {
|
33
|
+
throw new Error('No native promises and smartcrop.Promise not set.');
|
34
|
+
};
|
35
|
+
|
36
|
+
smartcrop.DEFAULTS = {
|
37
|
+
width: 0,
|
38
|
+
height: 0,
|
39
|
+
aspect: 0,
|
40
|
+
cropWidth: 0,
|
41
|
+
cropHeight: 0,
|
42
|
+
detailWeight: 0.2,
|
43
|
+
skinColor: [0.78, 0.57, 0.44],
|
44
|
+
skinBias: 0.01,
|
45
|
+
skinBrightnessMin: 0.2,
|
46
|
+
skinBrightnessMax: 1.0,
|
47
|
+
skinThreshold: 0.8,
|
48
|
+
skinWeight: 1.8,
|
49
|
+
saturationBrightnessMin: 0.05,
|
50
|
+
saturationBrightnessMax: 0.9,
|
51
|
+
saturationThreshold: 0.4,
|
52
|
+
saturationBias: 0.2,
|
53
|
+
saturationWeight: 0.3,
|
54
|
+
// Step * minscale rounded down to the next power of two should be good
|
55
|
+
scoreDownSample: 8,
|
56
|
+
step: 8,
|
57
|
+
scaleStep: 0.1,
|
58
|
+
minScale: 1.0,
|
59
|
+
maxScale: 1.0,
|
60
|
+
edgeRadius: 0.4,
|
61
|
+
edgeWeight: -20.0,
|
62
|
+
outsideImportance: -0.5,
|
63
|
+
boostWeight: 100.0,
|
64
|
+
ruleOfThirds: true,
|
65
|
+
prescale: true,
|
66
|
+
imageOperations: null,
|
67
|
+
canvasFactory: defaultCanvasFactory,
|
68
|
+
// Factory: defaultFactories,
|
69
|
+
debug: false,
|
63
70
|
};
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
71
|
+
|
72
|
+
|
73
|
+
|
74
|
+
smartcrop.crop = function(inputImage, options_, callback) {
|
75
|
+
var options = extend({}, smartcrop.DEFAULTS, options_);
|
76
|
+
|
77
|
+
if (options.aspect) {
|
78
|
+
options.width = options.aspect;
|
79
|
+
options.height = 1;
|
80
|
+
}
|
81
|
+
|
82
|
+
if (options.imageOperations === null) {
|
83
|
+
options.imageOperations = canvasImageOperations(options.canvasFactory);
|
84
|
+
}
|
85
|
+
|
86
|
+
var iop = options.imageOperations;
|
87
|
+
|
88
|
+
var scale = 1;
|
89
|
+
var prescale = 1;
|
90
|
+
|
91
|
+
return iop.open(inputImage, options.input).then(function(image) {
|
92
|
+
|
93
|
+
if (options.width && options.height) {
|
94
|
+
scale = min(image.width / options.width, image.height / options.height);
|
95
|
+
options.cropWidth = ~~(options.width * scale);
|
96
|
+
options.cropHeight = ~~(options.height * scale);
|
97
|
+
// Img = 100x100, width = 95x95, scale = 100/95, 1/scale > min
|
98
|
+
// don't set minscale smaller than 1/scale
|
99
|
+
// -> don't pick crops that need upscaling
|
100
|
+
options.minScale = min(options.maxScale, max(1 / scale, options.minScale));
|
101
|
+
|
102
|
+
if (options.prescale !== false) {
|
103
|
+
prescale = 1 / scale / options.minScale;
|
104
|
+
if (prescale < 1) {
|
105
|
+
image = iop.resample(image, image.width * prescale, image.height * prescale);
|
106
|
+
options.cropWidth = ~~(options.cropWidth * prescale);
|
107
|
+
options.cropHeight = ~~(options.cropHeight * prescale);
|
108
|
+
if (options.boost) {
|
109
|
+
options.boost = options.boost.map(function(boost) {
|
110
|
+
return {
|
111
|
+
x: ~~(boost.x * prescale),
|
112
|
+
y: ~~(boost.y * prescale),
|
113
|
+
width: ~~(boost.width * prescale),
|
114
|
+
height: ~~(boost.height * prescale),
|
115
|
+
weight: boost.weight
|
116
|
+
};
|
117
|
+
});
|
118
|
+
}
|
119
|
+
}
|
120
|
+
else {
|
121
|
+
prescale = 1;
|
122
|
+
}
|
123
|
+
}
|
68
124
|
}
|
125
|
+
return image;
|
126
|
+
})
|
127
|
+
.then(function(image) {
|
128
|
+
return iop.getData(image).then(function(data) {
|
129
|
+
var result = analyse(options, data);
|
69
130
|
|
70
|
-
|
71
|
-
|
72
|
-
var
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
131
|
+
var crops = result.crops || [result.topCrop];
|
132
|
+
for (var i = 0, iLen = crops.length; i < iLen; i++) {
|
133
|
+
var crop = crops[i];
|
134
|
+
crop.x = ~~(crop.x / prescale);
|
135
|
+
crop.y = ~~(crop.y / prescale);
|
136
|
+
crop.width = ~~(crop.width / prescale);
|
137
|
+
crop.height = ~~(crop.height / prescale);
|
138
|
+
}
|
139
|
+
if (callback) callback(result);
|
140
|
+
return result;
|
141
|
+
});
|
142
|
+
});
|
143
|
+
};
|
144
|
+
|
145
|
+
|
146
|
+
// Check if all the dependencies are there
|
147
|
+
// todo:
|
148
|
+
smartcrop.isAvailable = function(options) {
|
149
|
+
if (!smartcrop.Promise) return false;
|
150
|
+
|
151
|
+
var canvasFactory = options ? options.canvasFactory : defaultCanvasFactory;
|
152
|
+
|
153
|
+
if (canvasFactory === defaultCanvasFactory) {
|
154
|
+
var c = document.createElement('canvas');
|
155
|
+
if (!c.getContext('2d')) {
|
156
|
+
return false;
|
78
157
|
}
|
158
|
+
}
|
159
|
+
|
160
|
+
return true;
|
161
|
+
};
|
162
|
+
|
163
|
+
function edgeDetect(i, o) {
|
164
|
+
var id = i.data;
|
165
|
+
var od = o.data;
|
166
|
+
var w = i.width;
|
167
|
+
var h = i.height;
|
79
168
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
169
|
+
for (var y = 0; y < h; y++) {
|
170
|
+
for (var x = 0; x < w; x++) {
|
171
|
+
var p = (y * w + x) * 4;
|
172
|
+
var lightness;
|
173
|
+
|
174
|
+
if (x === 0 || x >= w - 1 || y === 0 || y >= h - 1) {
|
175
|
+
lightness = sample(id, p);
|
176
|
+
}
|
177
|
+
else {
|
178
|
+
lightness = sample(id, p) * 4 -
|
179
|
+
sample(id, p - w * 4) -
|
180
|
+
sample(id, p - 4) -
|
181
|
+
sample(id, p + 4) -
|
182
|
+
sample(id, p + w * 4);
|
183
|
+
}
|
184
|
+
|
185
|
+
od[p + 1] = lightness;
|
90
186
|
}
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
187
|
+
}
|
188
|
+
}
|
189
|
+
|
190
|
+
function skinDetect(options, i, o) {
|
191
|
+
var id = i.data;
|
192
|
+
var od = o.data;
|
193
|
+
var w = i.width;
|
194
|
+
var h = i.height;
|
195
|
+
|
196
|
+
for (var y = 0; y < h; y++) {
|
197
|
+
for (var x = 0; x < w; x++) {
|
198
|
+
var p = (y * w + x) * 4;
|
199
|
+
var lightness = cie(id[p], id[p + 1], id[p + 2]) / 255;
|
200
|
+
var skin = skinColor(options, id[p], id[p + 1], id[p + 2]);
|
201
|
+
var isSkinColor = skin > options.skinThreshold;
|
202
|
+
var isSkinBrightness = lightness >= options.skinBrightnessMin && lightness <= options.skinBrightnessMax;
|
203
|
+
if (isSkinColor && isSkinBrightness) {
|
204
|
+
od[p] = (skin - options.skinThreshold) * (255 / (1 - options.skinThreshold));
|
205
|
+
}
|
206
|
+
else {
|
207
|
+
od[p] = 0;
|
208
|
+
}
|
107
209
|
}
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
210
|
+
}
|
211
|
+
}
|
212
|
+
|
213
|
+
function saturationDetect(options, i, o) {
|
214
|
+
var id = i.data;
|
215
|
+
var od = o.data;
|
216
|
+
var w = i.width;
|
217
|
+
var h = i.height;
|
218
|
+
for (var y = 0; y < h; y++) {
|
219
|
+
for (var x = 0; x < w; x++) {
|
220
|
+
var p = (y * w + x) * 4;
|
221
|
+
|
222
|
+
var lightness = cie(id[p], id[p + 1], id[p + 2]) / 255;
|
223
|
+
var sat = saturation(id[p], id[p + 1], id[p + 2]);
|
224
|
+
|
225
|
+
var acceptableSaturation = sat > options.saturationThreshold;
|
226
|
+
var acceptableLightness = lightness >= options.saturationBrightnessMin &&
|
227
|
+
lightness <= options.saturationBrightnessMax;
|
228
|
+
if (acceptableLightness && acceptableLightness) {
|
229
|
+
od[p + 2] = (sat - options.saturationThreshold) * (255 / (1 - options.saturationThreshold));
|
230
|
+
}
|
231
|
+
else {
|
232
|
+
od[p + 2] = 0;
|
233
|
+
}
|
115
234
|
}
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
235
|
+
}
|
236
|
+
}
|
237
|
+
|
238
|
+
function applyBoosts(options, output) {
|
239
|
+
if (!options.boost) return;
|
240
|
+
var od = output.data;
|
241
|
+
for (var i = 0; i < output.width; i += 4) {
|
242
|
+
od[i + 3] = 0;
|
243
|
+
}
|
244
|
+
for (i = 0; i < options.boost.length; i++) {
|
245
|
+
applyBoost(options.boost[i], options, output);
|
246
|
+
}
|
247
|
+
}
|
248
|
+
|
249
|
+
function applyBoost(boost, options, output) {
|
250
|
+
var od = output.data;
|
251
|
+
var w = output.width;
|
252
|
+
var x0 = ~~boost.x;
|
253
|
+
var x1 = ~~(boost.x + boost.width);
|
254
|
+
var y0 = ~~boost.y;
|
255
|
+
var y1 = ~~(boost.y + boost.height);
|
256
|
+
var weight = boost.weight * 255;
|
257
|
+
for (var y = y0; y < y1; y++) {
|
258
|
+
for (var x = x0; x < x1; x++) {
|
259
|
+
var i = (y * w + x) * 4;
|
260
|
+
od[i + 3] += weight;
|
125
261
|
}
|
126
|
-
|
127
|
-
|
262
|
+
}
|
263
|
+
}
|
264
|
+
|
265
|
+
function generateCrops(options, width, height) {
|
266
|
+
var results = [];
|
267
|
+
var minDimension = min(width, height);
|
268
|
+
var cropWidth = options.cropWidth || minDimension;
|
269
|
+
var cropHeight = options.cropHeight || minDimension;
|
270
|
+
for (var scale = options.maxScale; scale >= options.minScale; scale -= options.scaleStep) {
|
271
|
+
for (var y = 0; y + cropHeight * scale <= height; y += options.step) {
|
272
|
+
for (var x = 0; x + cropWidth * scale <= width; x += options.step) {
|
273
|
+
results.push({
|
274
|
+
x: x,
|
275
|
+
y: y,
|
276
|
+
width: cropWidth * scale,
|
277
|
+
height: cropHeight * scale,
|
278
|
+
});
|
279
|
+
}
|
128
280
|
}
|
129
|
-
}
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
outputWidth = output.width;
|
235
|
-
for(var y = 0; y < outputHeightDownSample; y+=downSample) {
|
236
|
-
for(var x = 0; x < outputWidthDownSample; x+=downSample) {
|
237
|
-
var p = (~~(y*invDownSample)*outputWidth+~~(x*invDownSample))*4,
|
238
|
-
importance = this.importance(crop, x, y),
|
239
|
-
detail = od[p+1]/255;
|
240
|
-
score.skin += od[p]/255*(detail+options.skinBias)*importance;
|
241
|
-
score.detail += detail*importance;
|
242
|
-
score.saturation += od[p+2]/255*(detail+options.saturationBias)*importance;
|
243
|
-
}
|
281
|
+
}
|
282
|
+
return results;
|
283
|
+
}
|
284
|
+
|
285
|
+
function score(options, output, crop) {
|
286
|
+
var result = {
|
287
|
+
detail: 0,
|
288
|
+
saturation: 0,
|
289
|
+
skin: 0,
|
290
|
+
boost: 0,
|
291
|
+
total: 0,
|
292
|
+
};
|
293
|
+
|
294
|
+
var od = output.data;
|
295
|
+
var downSample = options.scoreDownSample;
|
296
|
+
var invDownSample = 1 / downSample;
|
297
|
+
var outputHeightDownSample = output.height * downSample;
|
298
|
+
var outputWidthDownSample = output.width * downSample;
|
299
|
+
var outputWidth = output.width;
|
300
|
+
|
301
|
+
for (var y = 0; y < outputHeightDownSample; y += downSample) {
|
302
|
+
for (var x = 0; x < outputWidthDownSample; x += downSample) {
|
303
|
+
var p = (~~(y * invDownSample) * outputWidth + ~~(x * invDownSample)) * 4;
|
304
|
+
var i = importance(options, crop, x, y);
|
305
|
+
var detail = od[p + 1] / 255;
|
306
|
+
|
307
|
+
result.skin += od[p] / 255 * (detail + options.skinBias) * i;
|
308
|
+
result.detail += detail * i;
|
309
|
+
result.saturation += od[p + 2] / 255 * (detail + options.saturationBias) * i;
|
310
|
+
result.boost += od[p + 3] / 255 * i;
|
311
|
+
}
|
312
|
+
}
|
313
|
+
|
314
|
+
result.total = (result.detail * options.detailWeight +
|
315
|
+
result.skin * options.skinWeight +
|
316
|
+
result.saturation * options.saturationWeight +
|
317
|
+
result.boost * options.boostWeight) / (crop.width * crop.height);
|
318
|
+
return result;
|
319
|
+
}
|
320
|
+
|
321
|
+
function importance(options, crop, x, y) {
|
322
|
+
if (crop.x > x || x >= crop.x + crop.width || crop.y > y || y >= crop.y + crop.height) {
|
323
|
+
return options.outsideImportance;
|
324
|
+
}
|
325
|
+
x = (x - crop.x) / crop.width;
|
326
|
+
y = (y - crop.y) / crop.height;
|
327
|
+
var px = abs(0.5 - x) * 2;
|
328
|
+
var py = abs(0.5 - y) * 2;
|
329
|
+
// Distance from edge
|
330
|
+
var dx = Math.max(px - 1.0 + options.edgeRadius, 0);
|
331
|
+
var dy = Math.max(py - 1.0 + options.edgeRadius, 0);
|
332
|
+
var d = (dx * dx + dy * dy) * options.edgeWeight;
|
333
|
+
var s = 1.41 - sqrt(px * px + py * py);
|
334
|
+
if (options.ruleOfThirds) {
|
335
|
+
s += (Math.max(0, s + d + 0.5) * 1.2) * (thirds(px) + thirds(py));
|
336
|
+
}
|
337
|
+
return s + d;
|
338
|
+
}
|
339
|
+
smartcrop.importance = importance;
|
340
|
+
|
341
|
+
function skinColor(options, r, g, b) {
|
342
|
+
var mag = sqrt(r * r + g * g + b * b);
|
343
|
+
var rd = (r / mag - options.skinColor[0]);
|
344
|
+
var gd = (g / mag - options.skinColor[1]);
|
345
|
+
var bd = (b / mag - options.skinColor[2]);
|
346
|
+
var d = sqrt(rd * rd + gd * gd + bd * bd);
|
347
|
+
return 1 - d;
|
348
|
+
}
|
349
|
+
|
350
|
+
function analyse(options, input) {
|
351
|
+
var result = {};
|
352
|
+
var output = new ImgData(input.width, input.height);
|
353
|
+
|
354
|
+
edgeDetect(input, output);
|
355
|
+
skinDetect(options, input, output);
|
356
|
+
saturationDetect(options, input, output);
|
357
|
+
applyBoosts(options, output);
|
358
|
+
|
359
|
+
var scoreOutput = downSample(output, options.scoreDownSample);
|
360
|
+
|
361
|
+
var topScore = -Infinity;
|
362
|
+
var topCrop = null;
|
363
|
+
var crops = generateCrops(options, input.width, input.height);
|
364
|
+
|
365
|
+
for (var i = 0, iLen = crops.length; i < iLen; i++) {
|
366
|
+
var crop = crops[i];
|
367
|
+
crop.score = score(options, scoreOutput, crop);
|
368
|
+
if (crop.score.total > topScore) {
|
369
|
+
topCrop = crop;
|
370
|
+
topScore = crop.score.total;
|
371
|
+
}
|
372
|
+
|
373
|
+
}
|
374
|
+
|
375
|
+
result.topCrop = topCrop;
|
376
|
+
|
377
|
+
if (options.debug && topCrop) {
|
378
|
+
result.crops = crops;
|
379
|
+
result.debugOutput = output;
|
380
|
+
result.debugOptions = options;
|
381
|
+
// Create a copy which will not be adjusted by the post scaling of smartcrop.crop
|
382
|
+
result.debugTopCrop = extend({}, result.topCrop);
|
383
|
+
}
|
384
|
+
return result;
|
385
|
+
}
|
244
386
|
|
387
|
+
function ImgData(width, height, data) {
|
388
|
+
this.width = width;
|
389
|
+
this.height = height;
|
390
|
+
if (data) {
|
391
|
+
this.data = new Uint8ClampedArray(data);
|
392
|
+
}
|
393
|
+
else {
|
394
|
+
this.data = new Uint8ClampedArray(width * height * 4);
|
395
|
+
}
|
396
|
+
}
|
397
|
+
smartcrop.ImgData = ImgData;
|
398
|
+
|
399
|
+
function downSample(input, factor) {
|
400
|
+
var idata = input.data;
|
401
|
+
var iwidth = input.width;
|
402
|
+
var width = Math.floor(input.width / factor);
|
403
|
+
var height = Math.floor(input.height / factor);
|
404
|
+
var output = new ImgData(width, height);
|
405
|
+
var data = output.data;
|
406
|
+
var ifactor2 = 1 / (factor * factor);
|
407
|
+
for (var y = 0; y < height; y++) {
|
408
|
+
for (var x = 0; x < width; x++) {
|
409
|
+
var i = (y * width + x) * 4;
|
410
|
+
|
411
|
+
var r = 0;
|
412
|
+
var g = 0;
|
413
|
+
var b = 0;
|
414
|
+
var a = 0;
|
415
|
+
|
416
|
+
var mr = 0;
|
417
|
+
var mg = 0;
|
418
|
+
var mb = 0;
|
419
|
+
|
420
|
+
for (var v = 0; v < factor; v++) {
|
421
|
+
for (var u = 0; u < factor; u++) {
|
422
|
+
var j = ((y * factor + v) * iwidth + (x * factor + u)) * 4;
|
423
|
+
r += idata[j];
|
424
|
+
g += idata[j + 1];
|
425
|
+
b += idata[j + 2];
|
426
|
+
a += idata[j + 3];
|
427
|
+
mr = Math.max(mr, idata[j]);
|
428
|
+
mg = Math.max(mg, idata[j + 1]);
|
429
|
+
mb = Math.max(mb, idata[j + 2]);
|
245
430
|
}
|
246
|
-
|
247
|
-
|
431
|
+
}
|
432
|
+
// this is some funky magic to preserve detail a bit more for
|
433
|
+
// skin (r) and detail (g). Saturation (b) does not get this boost.
|
434
|
+
data[i] = r * ifactor2 * 0.5 + mr * 0.5;
|
435
|
+
data[i + 1] = g * ifactor2 * 0.7 + mg * 0.3;
|
436
|
+
data[i + 2] = b * ifactor2;
|
437
|
+
data[i + 3] = a * ifactor2;
|
438
|
+
}
|
439
|
+
}
|
440
|
+
return output;
|
441
|
+
}
|
442
|
+
smartcrop._downSample = downSample;
|
443
|
+
|
444
|
+
function defaultCanvasFactory(w, h) {
|
445
|
+
var c = document.createElement('canvas');
|
446
|
+
c.width = w;
|
447
|
+
c.height = h;
|
448
|
+
return c;
|
449
|
+
}
|
450
|
+
|
451
|
+
function canvasImageOperations(canvasFactory) {
|
452
|
+
return {
|
453
|
+
// Takes imageInput as argument
|
454
|
+
// returns an object which has at least
|
455
|
+
// {width: n, height: n}
|
456
|
+
open: function(image) {
|
457
|
+
// Work around images scaled in css by drawing them onto a canvas
|
458
|
+
var w = image.naturalWidth || image.width;
|
459
|
+
var h = image.naturalHeight || image.height;
|
460
|
+
var c = canvasFactory(w, h);
|
461
|
+
var ctx = c.getContext('2d');
|
462
|
+
if (image.naturalWidth && (image.naturalWidth != image.width || image.naturalHeight != image.height)) {
|
463
|
+
c.width = image.naturalWidth;
|
464
|
+
c.height = image.naturalHeight;
|
465
|
+
}
|
466
|
+
else {
|
467
|
+
c.width = image.width;
|
468
|
+
c.height = image.height;
|
469
|
+
}
|
470
|
+
ctx.drawImage(image, 0, 0);
|
471
|
+
return smartcrop.Promise.resolve(c);
|
248
472
|
},
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
dx = Math.max(px-1.0+options.edgeRadius, 0),
|
259
|
-
dy = Math.max(py-1.0+options.edgeRadius, 0),
|
260
|
-
d = (dx*dx+dy*dy)*options.edgeWeight;
|
261
|
-
var s = 1.41-sqrt(px*px+py*py);
|
262
|
-
if(options.ruleOfThirds){
|
263
|
-
s += (Math.max(0, s+d+0.5)*1.2)*(thirds(px)+thirds(py));
|
264
|
-
}
|
265
|
-
return s+d;
|
473
|
+
// Takes an image (as returned by open), and changes it's size by resampling
|
474
|
+
resample: function(image, width, height) {
|
475
|
+
return Promise.resolve(image).then(function(image) {
|
476
|
+
var c = canvasFactory(~~width, ~~height);
|
477
|
+
var ctx = c.getContext('2d');
|
478
|
+
|
479
|
+
ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, c.width, c.height);
|
480
|
+
return smartcrop.Promise.resolve(c);
|
481
|
+
});
|
266
482
|
},
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
d = sqrt(rd*rd+gd*gd+bd*bd);
|
274
|
-
return 1-d;
|
483
|
+
getData: function(image) {
|
484
|
+
return Promise.resolve(image).then(function(c) {
|
485
|
+
var ctx = c.getContext('2d');
|
486
|
+
var id = ctx.getImageData(0, 0, c.width, c.height);
|
487
|
+
return new ImgData(c.width, c.height, id.data);
|
488
|
+
});
|
275
489
|
},
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
canvas = this.canvas(image.width, image.height),
|
280
|
-
ctx = canvas.getContext('2d');
|
281
|
-
ctx.drawImage(image, 0, 0);
|
282
|
-
var input = ctx.getImageData(0, 0, canvas.width, canvas.height),
|
283
|
-
output = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
284
|
-
this.edgeDetect(input, output);
|
285
|
-
this.skinDetect(input, output);
|
286
|
-
this.saturationDetect(input, output);
|
287
|
-
|
288
|
-
var scoreCanvas = this.canvas(ceil(image.width/options.scoreDownSample), ceil(image.height/options.scoreDownSample)),
|
289
|
-
scoreCtx = scoreCanvas.getContext('2d');
|
290
|
-
|
291
|
-
ctx.putImageData(output, 0, 0);
|
292
|
-
scoreCtx.drawImage(canvas, 0, 0, canvas.width, canvas.height, 0, 0, scoreCanvas.width, scoreCanvas.height);
|
293
|
-
|
294
|
-
var scoreOutput = scoreCtx.getImageData(0, 0, scoreCanvas.width, scoreCanvas.height);
|
295
|
-
|
296
|
-
var topScore = -Infinity,
|
297
|
-
topCrop = null,
|
298
|
-
crops = this.crops(image);
|
299
|
-
|
300
|
-
for(var i = 0, i_len = crops.length; i < i_len; i++) {
|
301
|
-
var crop = crops[i];
|
302
|
-
crop.score = this.score(scoreOutput, crop);
|
303
|
-
if(crop.score.total > topScore){
|
304
|
-
topCrop = crop;
|
305
|
-
topScore = crop.score.total;
|
306
|
-
}
|
307
|
-
|
308
|
-
}
|
490
|
+
};
|
491
|
+
}
|
492
|
+
smartcrop._canvasImageOperations = canvasImageOperations;
|
309
493
|
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
for (var y = 0; y < output.height; y++) {
|
317
|
-
for (var x = 0; x < output.width; x++) {
|
318
|
-
var p = (y * output.width + x) * 4;
|
319
|
-
var importance = this.importance(topCrop, x, y);
|
320
|
-
if (importance > 0) {
|
321
|
-
output.data[p + 1] += importance * 32;
|
322
|
-
}
|
323
|
-
|
324
|
-
if (importance < 0) {
|
325
|
-
output.data[p] += importance * -64;
|
326
|
-
}
|
327
|
-
output.data[p + 3] = 255;
|
328
|
-
}
|
329
|
-
}
|
330
|
-
ctx.putImageData(output, 0, 0);
|
331
|
-
ctx.strokeStyle = 'rgba(255, 0, 0, 0.8)';
|
332
|
-
ctx.strokeRect(topCrop.x, topCrop.y, topCrop.width, topCrop.height);
|
333
|
-
result.debugCanvas = canvas;
|
334
|
-
}
|
335
|
-
return result;
|
336
|
-
}
|
337
|
-
};
|
494
|
+
// Aliases and helpers
|
495
|
+
var min = Math.min;
|
496
|
+
var max = Math.max;
|
497
|
+
var abs = Math.abs;
|
498
|
+
var ceil = Math.ceil;
|
499
|
+
var sqrt = Math.sqrt;
|
338
500
|
|
339
|
-
|
340
|
-
var
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
function extend(o){
|
347
|
-
for(var i = 1, i_len = arguments.length; i < i_len; i++) {
|
348
|
-
var arg = arguments[i];
|
349
|
-
if(arg){
|
350
|
-
for(var name in arg){
|
351
|
-
o[name] = arg[name];
|
352
|
-
}
|
353
|
-
}
|
501
|
+
function extend(o) {
|
502
|
+
for (var i = 1, iLen = arguments.length; i < iLen; i++) {
|
503
|
+
var arg = arguments[i];
|
504
|
+
if (arg) {
|
505
|
+
for (var name in arg) {
|
506
|
+
o[name] = arg[name];
|
507
|
+
}
|
354
508
|
}
|
355
|
-
|
509
|
+
}
|
510
|
+
return o;
|
356
511
|
}
|
357
512
|
|
358
|
-
//
|
513
|
+
// Gets value in the range of [0, 1] where 0 is the center of the pictures
|
359
514
|
// returns weight of rule of thirds [0, 1]
|
360
|
-
function thirds(x){
|
361
|
-
|
362
|
-
|
515
|
+
function thirds(x) {
|
516
|
+
x = ((x - (1 / 3) + 1.0) % 2.0 * 0.5 - 0.5) * 16;
|
517
|
+
return Math.max(1.0 - x * x, 0.0);
|
363
518
|
}
|
364
519
|
|
365
|
-
function cie(r, g, b){
|
366
|
-
|
520
|
+
function cie(r, g, b) {
|
521
|
+
return 0.5126 * b + 0.7152 * g + 0.0722 * r;
|
367
522
|
}
|
368
523
|
function sample(id, p) {
|
369
|
-
|
524
|
+
return cie(id[p], id[p + 1], id[p + 2]);
|
370
525
|
}
|
371
|
-
function saturation(r, g, b){
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
526
|
+
function saturation(r, g, b) {
|
527
|
+
var maximum = max(r / 255, g / 255, b / 255);
|
528
|
+
var minumum = min(r / 255, g / 255, b / 255);
|
529
|
+
|
530
|
+
if (maximum === minumum) {
|
531
|
+
return 0;
|
532
|
+
}
|
533
|
+
|
534
|
+
var l = (maximum + minumum) / 2;
|
535
|
+
var d = maximum - minumum;
|
536
|
+
|
537
|
+
return l > 0.5 ? d / (2 - maximum - minumum) : d / (maximum + minumum);
|
379
538
|
}
|
380
539
|
|
381
|
-
//
|
382
|
-
if (typeof define !== 'undefined' && define.amd) define(function(){return
|
383
|
-
//
|
384
|
-
if (typeof exports !== 'undefined') exports.
|
385
|
-
//
|
386
|
-
else if (typeof navigator !== 'undefined') window.SmartCrop =
|
387
|
-
//
|
540
|
+
// Amd
|
541
|
+
if (typeof define !== 'undefined' && define.amd) define(function() {return smartcrop;});
|
542
|
+
// Common js
|
543
|
+
if (typeof exports !== 'undefined') exports.smartcrop = smartcrop;
|
544
|
+
// Browser
|
545
|
+
else if (typeof navigator !== 'undefined') window.SmartCrop = window.smartcrop = smartcrop;
|
546
|
+
// Nodejs
|
388
547
|
if (typeof module !== 'undefined') {
|
389
|
-
|
548
|
+
module.exports = smartcrop;
|
390
549
|
}
|
391
|
-
})();
|
550
|
+
})();
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: smartcrop-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mohammed Sadiq
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-10-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|