smartcrop-rails 0.1.0 → 0.1.10

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: 909ac0984f4b1f12e4ea7a6132f91821086d6a6c
4
- data.tar.gz: 20c5fe4d44af3d71a1b30945257e0625aa883756
3
+ metadata.gz: 3feef6e3d9d7400965c579b8266b55ad14efb079
4
+ data.tar.gz: 81ee2c32312e8c2468e6c6c554aacc6b7d18c33b
5
5
  SHA512:
6
- metadata.gz: 450779f113cff835316dd31cfc223a3c3053260f7b7fa2d84fd3f54af5e1cacf021755dee455d4bcc4ab29691ca7942e3754f586a12a51789022740295d267c3
7
- data.tar.gz: 2f370f278aaba73fe8af7e80fa899226c56792f7856ebb87813a8294a1d8289dcf5ca949328290f5a4dde3eb0dff52b84eb90debf56c389c313c1fc7ed1c52bc
6
+ metadata.gz: 9764f127d6b297037a3e691c8182ee75912c8548549951fd428d2fe48dc4886936d024f095abb25086d458e47a3be20e078c78d42fc5a1f5d8e799dd23ff2067
7
+ data.tar.gz: f8ed1bee62a2d85943d875dde41baa46e9acc46f840249bf3b4ce9b8a408542759ed576de6b9eed148b11bf385d639377f1cb914c0e9acb74587a29d8fa720e6
@@ -1,5 +1,5 @@
1
1
  module Smartcrop
2
2
  module Rails
3
- VERSION = "0.1.0"
3
+ VERSION = "0.1.10"
4
4
  end
5
5
  end
File without changes
@@ -0,0 +1,391 @@
1
+ /** smart-crop.js
2
+ * A javascript library implementing content aware image cropping
3
+ *
4
+ * Copyright (C) 2014 Jonas Wagner
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining
7
+ * a copy of this software and associated documentation files (the
8
+ * "Software"), to deal in the Software without restriction, including
9
+ * without limitation the rights to use, copy, modify, merge, publish,
10
+ * distribute, sublicense, and/or sell copies of the Software, and to
11
+ * permit persons to whom the Software is furnished to do so, subject to
12
+ * the following conditions:
13
+ *
14
+ * The above copyright notice and this permission notice shall be
15
+ * included in all copies or substantial portions of the Software.
16
+ *
17
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
+ */
25
+
26
+ (function(){
27
+ "use strict";
28
+
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
63
+ };
64
+ SmartCrop.crop = function(image, options, callback){
65
+ if(options.aspect){
66
+ options.width = options.aspect;
67
+ options.height = 1;
68
+ }
69
+
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;
78
+ }
79
+
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)));
90
+ }
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
+ }
107
+ }
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);
115
+ }
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';
125
+ }
126
+ catch(e){
127
+ return false;
128
+ }
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
+ }
244
+
245
+ }
246
+ score.total = (score.detail*options.detailWeight + score.skin*options.skinWeight + score.saturation*options.saturationWeight)/crop.width/crop.height;
247
+ return score;
248
+ },
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;
266
+ },
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;
275
+ },
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
+ }
309
+
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
+ };
338
+
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
+ }
354
+ }
355
+ return o;
356
+ }
357
+
358
+ // gets value in the range of [0, 1] where 0 is the center of the pictures
359
+ // 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);
363
+ }
364
+
365
+ function cie(r, g, b){
366
+ return 0.5126*b + 0.7152*g + 0.0722*r;
367
+ }
368
+ function sample(id, p) {
369
+ return cie(id[p], id[p+1], id[p+2]);
370
+ }
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);
379
+ }
380
+
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
388
+ if (typeof module !== 'undefined') {
389
+ module.exports = SmartCrop;
390
+ }
391
+ })();
File without changes
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smartcrop-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mohammed Sadiq
@@ -57,6 +57,9 @@ files:
57
57
  - lib/smartcrop/rails.rb
58
58
  - lib/smartcrop/rails/version.rb
59
59
  - smartcrop-rails.gemspec
60
+ - vendor/assets/javascripts/.keep
61
+ - vendor/assets/javascripts/smartcrop.js
62
+ - vendor/assets/stylesheets/.keep
60
63
  homepage: https://github.com/sadiqmmm/smartcrop-rails
61
64
  licenses:
62
65
  - MIT