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 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