rails_cropit 0.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.
@@ -0,0 +1,415 @@
1
+ class Cropit
2
+
3
+ @_DEFAULTS:
4
+ exportZoom: 1
5
+ imageBackground: false
6
+ imageBackgroundBorderWidth: 0
7
+ imageState: null
8
+ allowCrossOrigin: false
9
+ allowDragNDrop: true
10
+ freeMove: false
11
+ minZoom: 'fill'
12
+
13
+ @PREVIEW_EVENTS: do ->
14
+ [
15
+ 'mousedown', 'mouseup', 'mouseleave'
16
+ 'touchstart', 'touchend', 'touchcancel', 'touchleave'
17
+ ]
18
+ .map (type) -> "#{type}.cropit"
19
+ .join ' '
20
+ @PREVIEW_MOVE_EVENTS: 'mousemove.cropit touchmove.cropit'
21
+ @ZOOM_INPUT_EVENTS: do ->
22
+ [
23
+ 'mousemove', 'touchmove', 'change'
24
+ ]
25
+ .map (type) -> "#{type}.cropit"
26
+ .join ' '
27
+
28
+ constructor: (@element, options) ->
29
+ @$el = $ @element
30
+
31
+ dynamicDefaults =
32
+ $fileInput: @$ 'input.cropit-image-input'
33
+ $preview: @$ '.cropit-image-preview'
34
+ $zoomSlider: @$ 'input.cropit-image-zoom-input'
35
+ $previewContainer: @$ '.cropit-image-preview-container'
36
+
37
+ @options = $.extend {}, Cropit._DEFAULTS, dynamicDefaults, options
38
+ @init()
39
+
40
+ init: ->
41
+ @image = new Image
42
+ @image.crossOrigin = 'Anonymous' if @options.allowCrossOrigin
43
+
44
+ @$fileInput = @options.$fileInput
45
+ .attr
46
+ accept: 'image/*'
47
+ @$preview = @options.$preview
48
+ .css
49
+ backgroundRepeat: 'no-repeat'
50
+ @$zoomSlider = @options.$zoomSlider
51
+ .attr
52
+ min: 0
53
+ max: 1
54
+ step: .01
55
+
56
+ @previewSize =
57
+ w: @options.width or @$preview.width()
58
+ h: @options.height or @$preview.height()
59
+ @$preview.width @previewSize.w if @options.width
60
+ @$preview.height @previewSize.h if @options.height
61
+
62
+ if @options.imageBackground
63
+ if $.isArray @options.imageBackgroundBorderWidth
64
+ @imageBgBorderWidthArray = @options.imageBackgroundBorderWidth
65
+ else
66
+ @imageBgBorderWidthArray = []
67
+ [0..3].forEach (i) =>
68
+ @imageBgBorderWidthArray[i] = @options.imageBackgroundBorderWidth
69
+
70
+ $previewContainer = @options.$previewContainer
71
+ @$imageBg = $ '<img />'
72
+ .addClass 'cropit-image-background'
73
+ .attr 'alt', ''
74
+ .css 'position', 'absolute'
75
+ @$imageBgContainer = $ '<div />'
76
+ .addClass 'cropit-image-background-container'
77
+ .css
78
+ position: 'absolute'
79
+ zIndex: 0
80
+ left: -@imageBgBorderWidthArray[3] + window.parseInt @$preview.css 'border-left-width'
81
+ top: -@imageBgBorderWidthArray[0] + window.parseInt @$preview.css 'border-top-width'
82
+ width: @previewSize.w + @imageBgBorderWidthArray[1] + @imageBgBorderWidthArray[3]
83
+ height: @previewSize.h + @imageBgBorderWidthArray[0] + @imageBgBorderWidthArray[2]
84
+ .append @$imageBg
85
+ @$imageBgContainer.css overflow: 'hidden' if @imageBgBorderWidthArray[0] > 0
86
+ $previewContainer
87
+ .css 'position', 'relative'
88
+ .prepend @$imageBgContainer
89
+ @$preview.css 'position', 'relative'
90
+
91
+ @$preview.hover =>
92
+ @$imageBg.addClass 'cropit-preview-hovered'
93
+ , =>
94
+ @$imageBg.removeClass 'cropit-preview-hovered'
95
+
96
+ @initialOffset = x: 0, y: 0
97
+ @initialZoom = 0
98
+ @initialZoomSliderPos = 0
99
+ @imageLoaded = false
100
+
101
+ @moveContinue = false
102
+
103
+ @zoomer = new Zoomer
104
+
105
+ jQuery.event.props.push 'dataTransfer' if @options.allowDragNDrop
106
+ @bindListeners()
107
+
108
+ @$zoomSlider.val @initialZoomSliderPos
109
+ @setOffset @options.imageState?.offset or @initialOffset
110
+ @zoom = @options.imageState?.zoom or @initialZoom
111
+ @loadImage @options.imageState?.src or null
112
+
113
+ bindListeners: ->
114
+ @$fileInput.on 'change.cropit', @onFileChange.bind @
115
+ @$preview.on Cropit.PREVIEW_EVENTS, @onPreviewEvent.bind @
116
+ @$zoomSlider.on Cropit.ZOOM_INPUT_EVENTS, @onZoomSliderChange.bind @
117
+
118
+ if @options.allowDragNDrop
119
+ @$preview.on 'dragover.cropit dragleave.cropit', @onDragOver.bind @
120
+ @$preview.on 'drop.cropit', @onDrop.bind @
121
+
122
+ unbindListeners: ->
123
+ @$fileInput.off 'change.cropit'
124
+ @$preview.off Cropit.PREVIEW_EVENTS
125
+ @$preview.off 'dragover.cropit dragleave.cropit drop.cropit'
126
+ @$zoomSlider.off Cropit.ZOOM_INPUT_EVENTS
127
+
128
+ reset: ->
129
+ @zoom = @initialZoom
130
+ @offset = @initialOffset
131
+
132
+ onFileChange: ->
133
+ @options.onFileChange?()
134
+
135
+ @loadFileReader @$fileInput.get(0).files[0]
136
+
137
+ loadFileReader: (file) ->
138
+ fileReader = new FileReader()
139
+ if file?.type.match 'image'
140
+ @setImageLoadingClass()
141
+
142
+ fileReader.readAsDataURL file
143
+ fileReader.onload = @onFileReaderLoaded.bind @
144
+ fileReader.onerror = @onFileReaderError.bind @
145
+ else if file?
146
+ @onFileReaderError()
147
+
148
+ onFileReaderLoaded: (e) ->
149
+ @reset()
150
+ @loadImage e.target.result
151
+
152
+ onFileReaderError: ->
153
+ @options.onFileReaderError?()
154
+
155
+ onDragOver: (e) ->
156
+ e.preventDefault()
157
+ e.dataTransfer.dropEffect = 'copy'
158
+ @$preview.toggleClass 'cropit-drag-hovered', e.type is 'dragover'
159
+
160
+ onDrop: (e) ->
161
+ e.preventDefault()
162
+ e.stopPropagation()
163
+
164
+ files = Array.prototype.slice.call e.dataTransfer.files, 0
165
+ files.some (file) =>
166
+ if file.type.match 'image'
167
+ @loadFileReader file
168
+ return true
169
+
170
+ @$preview.removeClass 'cropit-drag-hovered'
171
+
172
+ loadImage: (imageSrc) ->
173
+ @imageSrc = imageSrc
174
+ return unless @imageSrc
175
+
176
+ @options.onImageLoading?()
177
+ @setImageLoadingClass()
178
+
179
+ @image.onload = @onImageLoaded.bind @
180
+ @image.onerror = @onImageError.bind @
181
+
182
+ @image.src = @imageSrc
183
+
184
+ onImageLoaded: ->
185
+ @setImageLoadedClass()
186
+
187
+ @setOffset @offset
188
+ @$preview.css 'background-image', "url(#{@imageSrc})"
189
+ @$imageBg.attr 'src', @imageSrc if @options.imageBackground
190
+
191
+ @imageSize =
192
+ w: @image.width
193
+ h: @image.height
194
+
195
+ @setupZoomer()
196
+
197
+ @imageLoaded = true
198
+
199
+ @options.onImageLoaded?()
200
+
201
+ onImageError: ->
202
+ @options.onImageError?()
203
+
204
+ setImageLoadingClass: ->
205
+ @$preview
206
+ .removeClass 'cropit-image-loaded'
207
+ .addClass 'cropit-image-loading'
208
+
209
+ setImageLoadedClass: ->
210
+ @$preview
211
+ .removeClass 'cropit-image-loading'
212
+ .addClass 'cropit-image-loaded'
213
+
214
+ getEventPosition: (e) ->
215
+ e = e.originalEvent?.touches?[0] if e.originalEvent?.touches?[0]
216
+ return x: e.clientX, y: e.clientY if e.clientX and e.clientY
217
+
218
+ onPreviewEvent: (e) ->
219
+ return unless @imageLoaded
220
+ @moveContinue = false
221
+ @$preview.off Cropit.PREVIEW_MOVE_EVENTS
222
+
223
+ if e.type is 'mousedown' or e.type is 'touchstart'
224
+ @origin = @getEventPosition e
225
+ @moveContinue = true
226
+ @$preview.on Cropit.PREVIEW_MOVE_EVENTS, @onMove.bind @
227
+ else
228
+ $(document.body).focus()
229
+ e.stopPropagation()
230
+ false
231
+
232
+ onMove: (e) ->
233
+ eventPosition = @getEventPosition e
234
+
235
+ if @moveContinue and eventPosition
236
+ @setOffset
237
+ x: @offset.x + eventPosition.x - @origin.x
238
+ y: @offset.y + eventPosition.y - @origin.y
239
+
240
+ @origin = eventPosition
241
+
242
+ e.stopPropagation()
243
+ false
244
+
245
+ setOffset: (position) ->
246
+ @offset = @fixOffset position
247
+ @$preview.css 'background-position', "#{@offset.x}px #{@offset.y}px"
248
+ if @options.imageBackground
249
+ @$imageBg.css
250
+ left: @offset.x + @imageBgBorderWidthArray[3]
251
+ top: @offset.y + @imageBgBorderWidthArray[0]
252
+
253
+ fixOffset: (offset) ->
254
+ return offset unless @imageLoaded
255
+
256
+ ret = x: offset.x, y: offset.y
257
+
258
+ unless @options.freeMove
259
+ if @imageSize.w * @zoom >= @previewSize.w
260
+ ret.x = Math.min 0, Math.max ret.x, @previewSize.w - @imageSize.w * @zoom
261
+ else
262
+ ret.x = Math.max 0, Math.min ret.x, @previewSize.w - @imageSize.w * @zoom
263
+
264
+ if @imageSize.h * @zoom >= @previewSize.h
265
+ ret.y = Math.min 0, Math.max ret.y, @previewSize.h - @imageSize.h * @zoom
266
+ else
267
+ ret.y = Math.max 0, Math.min ret.y, @previewSize.h - @imageSize.h * @zoom
268
+
269
+ ret.x = @round ret.x
270
+ ret.y = @round ret.y
271
+
272
+ ret
273
+
274
+ onZoomSliderChange: ->
275
+ return unless @imageLoaded
276
+
277
+ @zoomSliderPos = Number @$zoomSlider.val()
278
+ newZoom = @zoomer.getZoom @zoomSliderPos
279
+ @setZoom newZoom
280
+
281
+ enableZoomSlider: ->
282
+ @$zoomSlider.removeAttr 'disabled'
283
+ @options.onZoomEnabled?()
284
+
285
+ disableZoomSlider: ->
286
+ @$zoomSlider.attr 'disabled', true
287
+ @options.onZoomDisabled?()
288
+
289
+ setupZoomer: ->
290
+ @zoomer.setup @imageSize, @previewSize, @options.exportZoom, @options
291
+ @zoom = @fixZoom @zoom
292
+ @setZoom @zoom
293
+
294
+ if @isZoomable() then @enableZoomSlider() else @disableZoomSlider()
295
+
296
+ setZoom: (newZoom) ->
297
+ newZoom = @fixZoom newZoom
298
+
299
+ updatedWidth = @round @imageSize.w * newZoom
300
+ updatedHeight = @round @imageSize.h * newZoom
301
+
302
+ oldZoom = @zoom
303
+
304
+ newX = @previewSize.w / 2 - (@previewSize.w / 2 - @offset.x) * newZoom / oldZoom
305
+ newY = @previewSize.h / 2 - (@previewSize.h / 2 - @offset.y) * newZoom / oldZoom
306
+
307
+ @zoom = newZoom
308
+ @setOffset x: newX, y: newY
309
+
310
+ @zoomSliderPos = @zoomer.getSliderPos @zoom
311
+ @$zoomSlider.val @zoomSliderPos
312
+
313
+ @$preview.css 'background-size', "#{updatedWidth}px #{updatedHeight}px"
314
+ if @options.imageBackground
315
+ @$imageBg.css
316
+ width: updatedWidth
317
+ height: updatedHeight
318
+
319
+ fixZoom: (zoom) ->
320
+ @zoomer.fixZoom zoom
321
+
322
+ isZoomable: ->
323
+ @zoomer.isZoomable()
324
+
325
+ getCroppedImageData: (exportOptions) ->
326
+ return null unless @imageSrc
327
+
328
+ exportDefaults =
329
+ type: 'image/png'
330
+ quality: .75
331
+ originalSize: false
332
+ fillBg: '#fff'
333
+ exportOptions = $.extend {}, exportDefaults, exportOptions
334
+
335
+ croppedSize =
336
+ w: @previewSize.w
337
+ h: @previewSize.h
338
+
339
+ exportZoom = if exportOptions.originalSize then 1 / @zoom else @options.exportZoom
340
+
341
+ canvas = $ '<canvas />'
342
+ .attr
343
+ width: croppedSize.w * exportZoom
344
+ height: croppedSize.h * exportZoom
345
+ .get 0
346
+ canvasContext = canvas.getContext '2d'
347
+
348
+ if exportOptions.type is 'image/jpeg'
349
+ canvasContext.fillStyle = exportOptions.fillBg
350
+ canvasContext.fillRect 0, 0, canvas.width, canvas.height
351
+
352
+ canvasContext.drawImage @image,
353
+ @offset.x * exportZoom,
354
+ @offset.y * exportZoom,
355
+ @zoom * exportZoom * @imageSize.w,
356
+ @zoom * exportZoom * @imageSize.h
357
+
358
+ canvas.toDataURL exportOptions.type, exportOptions.quality
359
+
360
+ getImageState: ->
361
+ src: @imageSrc
362
+ offset: @offset
363
+ zoom: @zoom
364
+
365
+ getImageSrc: ->
366
+ @imageSrc
367
+
368
+ getOffset: ->
369
+ @offset
370
+
371
+ getZoom: ->
372
+ @zoom
373
+
374
+ getImageSize: ->
375
+ return null unless @imageSize
376
+ width: @imageSize.w
377
+ height: @imageSize.h
378
+
379
+ getPreviewSize: ->
380
+ width: @previewSize.w
381
+ height: @previewSize.h
382
+
383
+ setPreviewSize: (size) ->
384
+ return unless size?.width > 0 and size?.height > 0
385
+
386
+ @previewSize =
387
+ w: size.width
388
+ h: size.height
389
+ @$preview.css
390
+ width: @previewSize.w
391
+ height: @previewSize.h
392
+
393
+ if @options.imageBackground
394
+ @$imageBgContainer.css
395
+ width: @previewSize.w + @imageBgBorderWidthArray[1] + @imageBgBorderWidthArray[3]
396
+ height: @previewSize.h + @imageBgBorderWidthArray[0] + @imageBgBorderWidthArray[2]
397
+
398
+ if @imageLoaded
399
+ @setupZoomer()
400
+
401
+ disable: ->
402
+ @unbindListeners()
403
+ @disableZoomSlider()
404
+ @$el.addClass 'cropit-disabled'
405
+
406
+ reenable: ->
407
+ @bindListeners()
408
+ @enableZoomSlider()
409
+ @$el.removeClass 'cropit-disabled'
410
+
411
+ round: (x) -> +(Math.round(x * 1e2) + 'e-2')
412
+
413
+ $: (selector) ->
414
+ return null unless @$el
415
+ @$el.find selector
@@ -0,0 +1,83 @@
1
+ dataKey = 'cropit'
2
+
3
+ methods =
4
+
5
+ init: (options) ->
6
+ @each ->
7
+ # Only instantiate once per element
8
+ unless $.data @, dataKey
9
+ cropit = new Cropit @, options
10
+ $.data @, dataKey, cropit
11
+
12
+ destroy: ->
13
+ @each ->
14
+ $.removeData @, dataKey
15
+
16
+ isZoomable: ->
17
+ cropit = @first().data dataKey
18
+ cropit?.isZoomable()
19
+
20
+ export: (options) ->
21
+ cropit = @first().data dataKey
22
+ cropit?.getCroppedImageData options
23
+
24
+ imageState: ->
25
+ cropit = @first().data dataKey
26
+ cropit?.getImageState()
27
+
28
+ imageSrc: (newImageSrc) ->
29
+ if newImageSrc?
30
+ @each ->
31
+ cropit = $.data @, dataKey
32
+ cropit?.reset()
33
+ cropit?.loadImage newImageSrc
34
+ else
35
+ cropit = @first().data dataKey
36
+ cropit?.getImageSrc()
37
+
38
+ offset: (newOffset) ->
39
+ if newOffset? and newOffset.x? and newOffset.y?
40
+ @each ->
41
+ cropit = $.data @, dataKey
42
+ cropit?.setOffset newOffset
43
+ else
44
+ cropit = @first().data dataKey
45
+ cropit?.getOffset()
46
+
47
+ zoom: (newZoom) ->
48
+ if newZoom?
49
+ @each ->
50
+ cropit = $.data @, dataKey
51
+ cropit?.setZoom newZoom
52
+ else
53
+ cropit = @first().data dataKey
54
+ cropit?.getZoom()
55
+
56
+ imageSize: ->
57
+ cropit = @first().data dataKey
58
+ cropit?.getImageSize()
59
+
60
+ previewSize: (newSize) ->
61
+ if newSize?
62
+ @each ->
63
+ cropit = $.data @, dataKey
64
+ cropit?.setPreviewSize newSize
65
+ else
66
+ cropit = @first().data dataKey
67
+ cropit?.getPreviewSize()
68
+
69
+ disable: ->
70
+ @each ->
71
+ cropit = $.data @, dataKey
72
+ cropit.disable()
73
+
74
+ reenable: ->
75
+ @each ->
76
+ cropit = $.data @, dataKey
77
+ cropit.reenable()
78
+
79
+ $.fn.cropit = (method) ->
80
+ if methods[method]
81
+ methods[method].apply @, [].slice.call arguments, 1
82
+ else
83
+ methods.init.apply @, arguments
@@ -0,0 +1,31 @@
1
+ class Zoomer
2
+ setup: (imageSize, previewSize, exportZoom = 1, options) ->
3
+ widthRatio = previewSize.w / imageSize.w
4
+ heightRatio = previewSize.h / imageSize.h
5
+
6
+ if options?.minZoom is 'fit'
7
+ @minZoom = if widthRatio < heightRatio then widthRatio else heightRatio
8
+ else
9
+ @minZoom = if widthRatio < heightRatio then heightRatio else widthRatio
10
+
11
+ @maxZoom = if @minZoom < 1 / exportZoom then 1 / exportZoom else @minZoom
12
+
13
+ getZoom: (sliderPos) ->
14
+ return null unless @minZoom and @maxZoom
15
+ sliderPos * (@maxZoom - @minZoom) + @minZoom
16
+
17
+ getSliderPos: (zoom) ->
18
+ return null unless @minZoom and @maxZoom
19
+ if @minZoom is @maxZoom
20
+ 0
21
+ else
22
+ (zoom - @minZoom) / (@maxZoom - @minZoom)
23
+
24
+ isZoomable: ->
25
+ return null unless @minZoom and @maxZoom
26
+ @minZoom isnt @maxZoom
27
+
28
+ fixZoom: (zoom) ->
29
+ return @minZoom if zoom < @minZoom
30
+ return @maxZoom if zoom > @maxZoom
31
+ zoom
metadata ADDED
@@ -0,0 +1,112 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails_cropit
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Kaushal Kishor
14
+ autorequire:
15
+ bindir: exe
16
+ cert_chain: []
17
+
18
+ date: 2015-09-27 00:00:00 +05:30
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ version_requirements: &id001 !ruby/object:Gem::Requirement
23
+ none: false
24
+ requirements:
25
+ - - ~>
26
+ - !ruby/object:Gem::Version
27
+ hash: 31
28
+ segments:
29
+ - 1
30
+ - 8
31
+ version: "1.8"
32
+ prerelease: false
33
+ requirement: *id001
34
+ type: :development
35
+ name: bundler
36
+ - !ruby/object:Gem::Dependency
37
+ version_requirements: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ~>
41
+ - !ruby/object:Gem::Version
42
+ hash: 35
43
+ segments:
44
+ - 10
45
+ - 0
46
+ version: "10.0"
47
+ prerelease: false
48
+ requirement: *id002
49
+ type: :development
50
+ name: rake
51
+ description: It is jQuery gem for "customizable crop and zoom" on rails application platform
52
+ email:
53
+ - kaushalk16@gmail.com
54
+ executables: []
55
+
56
+ extensions: []
57
+
58
+ extra_rdoc_files: []
59
+
60
+ files:
61
+ - .gitignore
62
+ - CODE_OF_CONDUCT.md
63
+ - Gemfile
64
+ - LICENSE.txt
65
+ - README.md
66
+ - Rakefile
67
+ - bin/console
68
+ - bin/setup
69
+ - lib/rails_cropit.rb
70
+ - lib/rails_cropit/version.rb
71
+ - rails_cropit.gemspec
72
+ - vendor/assets/javascripts/jquery.cropit.js
73
+ - vendor/assets/javascripts/jquery.cropit.min.js
74
+ - vendor/assets/stylesheets/cropit.coffee
75
+ - vendor/assets/stylesheets/plugin.coffee
76
+ - vendor/assets/stylesheets/zoomer.coffee
77
+ has_rdoc: true
78
+ homepage: https://github.com/kaushal-xx/rails_cropit
79
+ licenses:
80
+ - MIT
81
+ post_install_message:
82
+ rdoc_options: []
83
+
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ hash: 3
92
+ segments:
93
+ - 0
94
+ version: "0"
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ hash: 3
101
+ segments:
102
+ - 0
103
+ version: "0"
104
+ requirements: []
105
+
106
+ rubyforge_project:
107
+ rubygems_version: 1.4.2
108
+ signing_key:
109
+ specification_version: 3
110
+ summary: Crop image run time
111
+ test_files: []
112
+