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