smartcrop-rails 0.1.12 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 587e817f6248849e1897dc152f0785e9e0eb0bf8
4
- data.tar.gz: f67399f4ee4c4fdf430c5c6f2e4a12a4a4e86b77
3
+ metadata.gz: af7d9f17f5b2d5c660ec32c3d4383ec3f92773e7
4
+ data.tar.gz: b9fd2c1551321cfc99adee8da45c71601c5103f8
5
5
  SHA512:
6
- metadata.gz: 2a9f0de4b55fdc8cc68928d4e4aec6c8a82e34ecbb1eab6c7a5dbde1de1ca465b039b407269167afe5b9396ab593ad8cb77270ec1f93c114126f95c1dc88655e
7
- data.tar.gz: 0cd4beb44a5aa523e4477b3d700ff0b37e1b7c9cff57ad7a0d91dfa690830a2a080d30ffc123c258d4b41dc6021a5eeeec71353ea1e6985852bf22cdcae28f04
6
+ metadata.gz: 430abe02c079fd9d1a2f78ab9c4dd7f2f1bbf5ad5cedeb1634c883928c3c68fee1ac11b6b21ca78d16b6c1da05e02dfc102b2c7f715d1f96858ca99119bc0784
7
+ data.tar.gz: c0a8abae042233c194064153f226c6a7225c339a5dae6829723465f0bca1a749c161f7d20491e96847b9f217ad77df960ed2e5970c3d4722558ee769b4a03099
@@ -1,5 +1,5 @@
1
1
  module Smartcrop
2
2
  module Rails
3
- VERSION = "0.1.12"
3
+ VERSION = "1.1.0"
4
4
  end
5
5
  end
@@ -1,7 +1,8 @@
1
- /** smart-crop.js
1
+ /**
2
+ * smartcrop.js
2
3
  * A javascript library implementing content aware image cropping
3
4
  *
4
- * Copyright (C) 2014 Jonas Wagner
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
- "use strict";
27
+ (function() {
28
+ 'use strict';
28
29
 
29
- function SmartCrop(options){
30
- this.options = extend({}, SmartCrop.DEFAULTS, options);
31
- }
32
- SmartCrop.DEFAULTS = {
33
- width: 0,
34
- height: 0,
35
- aspect: 0,
36
- cropWidth: 0,
37
- cropHeight: 0,
38
- detailWeight: 0.2,
39
- skinColor: [0.78, 0.57, 0.44],
40
- skinBias: 0.01,
41
- skinBrightnessMin: 0.2,
42
- skinBrightnessMax: 1.0,
43
- skinThreshold: 0.8,
44
- skinWeight: 1.8,
45
- saturationBrightnessMin: 0.05,
46
- saturationBrightnessMax: 0.9,
47
- saturationThreshold: 0.4,
48
- saturationBias: 0.2,
49
- saturationWeight: 0.3,
50
- // step * minscale rounded down to the next power of two should be good
51
- scoreDownSample: 8,
52
- step: 8,
53
- scaleStep: 0.1,
54
- minScale: 0.9,
55
- maxScale: 1.0,
56
- edgeRadius: 0.4,
57
- edgeWeight: -20.0,
58
- outsideImportance: -0.5,
59
- ruleOfThirds: true,
60
- prescale: true,
61
- canvasFactory: null,
62
- debug: false
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
- SmartCrop.crop = function(image, options, callback){
65
- if(options.aspect){
66
- options.width = options.aspect;
67
- options.height = 1;
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
- // work around images scaled in css by drawing them onto a canvas
71
- if(image.naturalWidth && (image.naturalWidth != image.width || image.naturalHeight != image.height)){
72
- var c = new SmartCrop(options).canvas(image.naturalWidth, image.naturalHeight),
73
- cctx = c.getContext('2d');
74
- c.width = image.naturalWidth;
75
- c.height = image.naturalHeight;
76
- cctx.drawImage(image, 0, 0);
77
- image = c;
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
- var scale = 1,
81
- prescale = 1;
82
- if(options.width && options.height) {
83
- scale = min(image.width/options.width, image.height/options.height);
84
- options.cropWidth = ~~(options.width * scale);
85
- options.cropHeight = ~~(options.height * scale);
86
- // img = 100x100, width = 95x95, scale = 100/95, 1/scale > min
87
- // don't set minscale smaller than 1/scale
88
- // -> don't pick crops that need upscaling
89
- options.minScale = min(options.maxScale || SmartCrop.DEFAULTS.maxScale, max(1/scale, (options.minScale||SmartCrop.DEFAULTS.minScale)));
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
- var smartCrop = new SmartCrop(options);
92
- if(options.width && options.height) {
93
- if(options.prescale !== false){
94
- prescale = 1/scale/options.minScale;
95
- if(prescale < 1) {
96
- var prescaledCanvas = smartCrop.canvas(image.width*prescale, image.height*prescale),
97
- ctx = prescaledCanvas.getContext('2d');
98
- ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, prescaledCanvas.width, prescaledCanvas.height);
99
- image = prescaledCanvas;
100
- smartCrop.options.cropWidth = ~~(options.cropWidth*prescale);
101
- smartCrop.options.cropHeight = ~~(options.cropHeight*prescale);
102
- }
103
- else {
104
- prescale = 1;
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
- var result = smartCrop.analyse(image);
109
- for(var i = 0, i_len = result.crops.length; i < i_len; i++) {
110
- var crop = result.crops[i];
111
- crop.x = ~~(crop.x/prescale);
112
- crop.y = ~~(crop.y/prescale);
113
- crop.width = ~~(crop.width/prescale);
114
- crop.height = ~~(crop.height/prescale);
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
- callback(result);
117
- return result;
118
- };
119
- // check if all the dependencies are there
120
- SmartCrop.isAvailable = function(options){
121
- try {
122
- var s = new this(options),
123
- c = s.canvas(16, 16);
124
- return typeof c.getContext === 'function';
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
- catch(e){
127
- return false;
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
- SmartCrop.prototype = {
131
- canvas: function(w, h){
132
- if(this.options.canvasFactory !== null){
133
- return this.options.canvasFactory(w, h);
134
- }
135
- var c = document.createElement('canvas');
136
- c.width = w;
137
- c.height = h;
138
- return c;
139
- },
140
- edgeDetect: function(i, o){
141
- var id = i.data,
142
- od = o.data,
143
- w = i.width,
144
- h = i.height;
145
- for(var y = 0; y < h; y++) {
146
- for(var x = 0; x < w; x++) {
147
- var p = (y*w+x)*4,
148
- lightness;
149
- if(x === 0 || x >= w-1 || y === 0 || y >= h-1){
150
- lightness = sample(id, p);
151
- }
152
- else {
153
- lightness = sample(id, p)*4 - sample(id, p-w*4) - sample(id, p-4) - sample(id, p+4) - sample(id, p+w*4);
154
- }
155
- od[p+1] = lightness;
156
- }
157
- }
158
- },
159
- skinDetect: function(i, o){
160
- var id = i.data,
161
- od = o.data,
162
- w = i.width,
163
- h = i.height,
164
- options = this.options;
165
- for(var y = 0; y < h; y++) {
166
- for(var x = 0; x < w; x++) {
167
- var p = (y*w+x)*4,
168
- lightness = cie(id[p], id[p+1], id[p+2])/255,
169
- skin = this.skinColor(id[p], id[p+1], id[p+2]);
170
- if(skin > options.skinThreshold && lightness >= options.skinBrightnessMin && lightness <= options.skinBrightnessMax){
171
- od[p] = (skin-options.skinThreshold)*(255/(1-options.skinThreshold));
172
- }
173
- else {
174
- od[p] = 0;
175
- }
176
- }
177
- }
178
- },
179
- saturationDetect: function(i, o){
180
- var id = i.data,
181
- od = o.data,
182
- w = i.width,
183
- h = i.height,
184
- options = this.options;
185
- for(var y = 0; y < h; y++) {
186
- for(var x = 0; x < w; x++) {
187
- var p = (y*w+x)*4,
188
- lightness = cie(id[p], id[p+1], id[p+2])/255,
189
- sat = saturation(id[p], id[p+1], id[p+2]);
190
- if(sat > options.saturationThreshold && lightness >= options.saturationBrightnessMin && lightness <= options.saturationBrightnessMax){
191
- od[p+2] = (sat-options.saturationThreshold)*(255/(1-options.saturationThreshold));
192
- }
193
- else {
194
- od[p+2] = 0;
195
- }
196
- }
197
- }
198
- },
199
- crops: function(image){
200
- var crops = [],
201
- width = image.width,
202
- height = image.height,
203
- options = this.options,
204
- minDimension = min(width, height),
205
- cropWidth = options.cropWidth || minDimension,
206
- cropHeight = options.cropHeight || minDimension;
207
- for(var scale = options.maxScale; scale >= options.minScale; scale -= options.scaleStep){
208
- for(var y = 0; y+cropHeight*scale <= height; y+=options.step) {
209
- for(var x = 0; x+cropWidth*scale <= width; x+=options.step) {
210
- crops.push({
211
- x: x,
212
- y: y,
213
- width: cropWidth*scale,
214
- height: cropHeight*scale
215
- });
216
- }
217
- }
218
- }
219
- return crops;
220
- },
221
- score: function(output, crop){
222
- var score = {
223
- detail: 0,
224
- saturation: 0,
225
- skin: 0,
226
- total: 0
227
- },
228
- options = this.options,
229
- od = output.data,
230
- downSample = options.scoreDownSample,
231
- invDownSample = 1/downSample,
232
- outputHeightDownSample = output.height*downSample,
233
- outputWidthDownSample = output.width*downSample,
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
- score.total = (score.detail*options.detailWeight + score.skin*options.skinWeight + score.saturation*options.saturationWeight)/crop.width/crop.height;
247
- return score;
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
- importance: function(crop, x, y){
250
- var options = this.options;
251
-
252
- if (crop.x > x || x >= crop.x+crop.width || crop.y > y || y >= crop.y+crop.height) return options.outsideImportance;
253
- x = (x-crop.x)/crop.width;
254
- y = (y-crop.y)/crop.height;
255
- var px = abs(0.5-x)*2,
256
- py = abs(0.5-y)*2,
257
- // distance from edge
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
- skinColor: function(r, g, b){
268
- var mag = sqrt(r*r+g*g+b*b),
269
- options = this.options,
270
- rd = (r/mag-options.skinColor[0]),
271
- gd = (g/mag-options.skinColor[1]),
272
- bd = (b/mag-options.skinColor[2]),
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
- analyse: function(image){
277
- var result = {},
278
- options = this.options,
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
- result.crops = crops;
311
- result.topCrop = topCrop;
312
-
313
- if(options.debug && topCrop){
314
- ctx.fillStyle = 'rgba(255, 0, 0, 0.1)';
315
- ctx.fillRect(topCrop.x, topCrop.y, topCrop.width, topCrop.height);
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
- // aliases and helpers
340
- var min = Math.min,
341
- max = Math.max,
342
- abs = Math.abs,
343
- ceil = Math.ceil,
344
- sqrt = Math.sqrt;
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
- return o;
509
+ }
510
+ return o;
356
511
  }
357
512
 
358
- // gets value in the range of [0, 1] where 0 is the center of the pictures
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
- x = ((x-(1/3)+1.0)%2.0*0.5-0.5)*16;
362
- return Math.max(1.0-x*x, 0.0);
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
- return 0.5126*b + 0.7152*g + 0.0722*r;
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
- return cie(id[p], id[p+1], id[p+2]);
524
+ return cie(id[p], id[p + 1], id[p + 2]);
370
525
  }
371
- function saturation(r, g, b){
372
- var maximum = max(r/255, g/255, b/255), minumum = min(r/255, g/255, b/255);
373
- if(maximum === minumum){
374
- return 0;
375
- }
376
- var l = (maximum + minumum) / 2,
377
- d = maximum-minumum;
378
- return l > 0.5 ? d/(2-maximum-minumum) : d/(maximum+minumum);
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
- // amd
382
- if (typeof define !== 'undefined' && define.amd) define(function(){return SmartCrop;});
383
- //common js
384
- if (typeof exports !== 'undefined') exports.SmartCrop = SmartCrop;
385
- // browser
386
- else if (typeof navigator !== 'undefined') window.SmartCrop = SmartCrop;
387
- // nodejs
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
- module.exports = SmartCrop;
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: 0.1.12
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-02-03 00:00:00.000000000 Z
11
+ date: 2016-10-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler