motion-image-editor 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +29 -0
- data/lib/motion-image-editor.rb +11 -0
- data/lib/project/controllers/image_editor_controller.rb +361 -0
- data/lib/project/views/image_editor_view.rb +55 -0
- metadata +76 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1bf6b38e31764220c4bb4d6f140089e3bdb50297
|
4
|
+
data.tar.gz: cb67aebfe59a677c3965522e83ce44151ff9af61
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7b4f42115362f4ff5b14d2948949fe31db972b7d5d5fbbb608229778a3ad533468dfd724d48ec5fc8999842ff1fe0a20d4ba2b126b6b501ebef8e33d7a2f3f6f
|
7
|
+
data.tar.gz: ce32cb9001e626e9425cb70d421643ab5621e56c1e80cbe477e3a6f69a852517800cf1ddf89136fd95d94557934d3d885685f9357efa3435b277a07ae5b6b892
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# motion-image-editor
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'motion-image-editor'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install motion-image-editor
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
@@ -0,0 +1,11 @@
|
|
1
|
+
unless defined?(Motion::Project::Config)
|
2
|
+
raise "This file must be required within a RubyMotion project Rakefile."
|
3
|
+
end
|
4
|
+
|
5
|
+
lib_dir_path = File.dirname(File.expand_path(__FILE__))
|
6
|
+
Motion::Project::App.setup do |app|
|
7
|
+
app.files.unshift(Dir.glob(File.join(lib_dir_path, "project/**/*.rb")))
|
8
|
+
|
9
|
+
app.pods ||= Motion::Project::CocoaPods.new(app)
|
10
|
+
app.pods.pod 'UIImage-Resize', '~> 1.0.1'
|
11
|
+
end
|
@@ -0,0 +1,361 @@
|
|
1
|
+
class Motion; class ImageEditorController < UIViewController
|
2
|
+
ANIMATION_DURATION = 0.2
|
3
|
+
MINIMUM_SCALE = 1
|
4
|
+
MAXIMUM_SCALE = 3
|
5
|
+
|
6
|
+
attr_reader :touch_center,
|
7
|
+
:scale_center,
|
8
|
+
:scale,
|
9
|
+
:output_width,
|
10
|
+
:source_image,
|
11
|
+
:crop_rect
|
12
|
+
|
13
|
+
def viewDidLoad
|
14
|
+
super
|
15
|
+
|
16
|
+
add_subviews
|
17
|
+
|
18
|
+
add_gesture_recognizers
|
19
|
+
|
20
|
+
setup_constraints
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_subviews
|
24
|
+
view.addSubview(crop_view)
|
25
|
+
view.insertSubview(image_view, belowSubview: crop_view)
|
26
|
+
end
|
27
|
+
|
28
|
+
def add_gesture_recognizers
|
29
|
+
crop_view.addGestureRecognizer(pan_recognizer)
|
30
|
+
crop_view.addGestureRecognizer(pinch_recognizer)
|
31
|
+
crop_view.addGestureRecognizer(rotation_recognizer)
|
32
|
+
crop_view.addGestureRecognizer(tap_recognizer)
|
33
|
+
end
|
34
|
+
|
35
|
+
def setup_constraints
|
36
|
+
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(
|
37
|
+
'H:|[crop]|',
|
38
|
+
options: 0,
|
39
|
+
metrics: nil,
|
40
|
+
views: { 'crop' => crop_view }))
|
41
|
+
|
42
|
+
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(
|
43
|
+
'V:|[crop]|',
|
44
|
+
options: 0,
|
45
|
+
metrics: nil,
|
46
|
+
views: { 'crop' => crop_view }))
|
47
|
+
end
|
48
|
+
|
49
|
+
def process(&block)
|
50
|
+
view.userInteractionEnabled = false
|
51
|
+
|
52
|
+
Dispatch::Queue.concurrent.async do
|
53
|
+
@result_ref = transform_image(
|
54
|
+
image_view.transform,
|
55
|
+
source_image: source_image.CGImage,
|
56
|
+
source_size: source_image.size,
|
57
|
+
source_orientation: source_image.imageOrientation,
|
58
|
+
output_width: output_width || source_image.size.width,
|
59
|
+
crop_rect: crop_rect,
|
60
|
+
image_view_size: image_view.bounds.size)
|
61
|
+
|
62
|
+
Dispatch::Queue.main.async do
|
63
|
+
transformed_image = UIImage.imageWithCGImage(
|
64
|
+
@result_ref,
|
65
|
+
scale: 1.0,
|
66
|
+
orientation: UIImageOrientationUp)
|
67
|
+
|
68
|
+
@result_ref = nil
|
69
|
+
|
70
|
+
view.userInteractionEnabled = true
|
71
|
+
|
72
|
+
block.call(transformed_image)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def crop_rect=(rect)
|
78
|
+
@crop_rect = rect
|
79
|
+
|
80
|
+
crop_view.crop_rect = rect
|
81
|
+
end
|
82
|
+
|
83
|
+
def scale_reset_orientations
|
84
|
+
[ UIImageOrientationUpMirrored,
|
85
|
+
UIImageOrientationDownMirrored,
|
86
|
+
UIImageOrientationLeftMirrored,
|
87
|
+
UIImageOrientationRightMirrored ]
|
88
|
+
end
|
89
|
+
|
90
|
+
def transform_image(transform, source_image: source_image, source_size: source_size, source_orientation: source_orientation, output_width: output_width, crop_rect: crop_rect, image_view_size: image_view_size)
|
91
|
+
aspect = crop_rect.size.height / crop_rect.size.width
|
92
|
+
output_size = CGSizeMake(output_width, output_width * aspect)
|
93
|
+
|
94
|
+
transpose = false
|
95
|
+
orientation_transform = CGAffineTransformIdentity
|
96
|
+
|
97
|
+
case source_orientation
|
98
|
+
when UIImageOrientationDown || UIImageOrientationDownMirrored
|
99
|
+
orientation_transform = CGAffineTransformMakeRotation(Math::PI)
|
100
|
+
when UIImageOrientationLeft || UIImageOrientationLeftMirrored
|
101
|
+
orientation_transform = CGAffineTransformMakeRotation(MATH::PI / 2.0)
|
102
|
+
transpose = true
|
103
|
+
when UIImageOrientationRight || UIImageOrientationRightMirrored
|
104
|
+
orientation_transform = CGAffineTransformMakeRotation(-(Math::PI / 2.0))
|
105
|
+
transpose = true
|
106
|
+
end
|
107
|
+
|
108
|
+
if scale_reset_orientations.include? source_orientation
|
109
|
+
orientation_transform = CGAffineTransformScale(transform, -1, 1)
|
110
|
+
end
|
111
|
+
|
112
|
+
if transpose
|
113
|
+
image_view_size = CGSizeMake(image_view_size.height, image_view_size.width)
|
114
|
+
end
|
115
|
+
|
116
|
+
context = CGBitmapContextCreate(
|
117
|
+
nil, # data
|
118
|
+
output_size.width, # width
|
119
|
+
output_size.height, # height
|
120
|
+
CGImageGetBitsPerComponent(source_image), # bits per component
|
121
|
+
0, # bytes per row
|
122
|
+
CGImageGetColorSpace(source_image), # color space
|
123
|
+
CGImageGetBitmapInfo(source_image)) # bitmap info
|
124
|
+
|
125
|
+
CGContextSetFillColorWithColor(context, UIColor.clearColor.CGColor)
|
126
|
+
CGContextFillRect(context, CGRectMake(0, 0, output_size.width, output_size.height))
|
127
|
+
|
128
|
+
ui_coords = CGAffineTransformMakeScale(output_size.width / crop_rect.size.width,
|
129
|
+
output_size.height / crop_rect.size.height)
|
130
|
+
|
131
|
+
ui_coords = CGAffineTransformTranslate(ui_coords, crop_rect.size.width / 2.0,
|
132
|
+
crop_rect.size.height / 2.0)
|
133
|
+
|
134
|
+
ui_coords = CGAffineTransformScale(ui_coords, 1.0, -1.0)
|
135
|
+
CGContextConcatCTM(context, ui_coords)
|
136
|
+
|
137
|
+
CGContextConcatCTM(context, transform)
|
138
|
+
CGContextScaleCTM(context, 1.0, -1.0)
|
139
|
+
CGContextConcatCTM(context, orientation_transform)
|
140
|
+
|
141
|
+
drawing_rect = CGRectMake(-image_view_size.width / 2.0, -image_view_size.height / 2.0, image_view_size.width, image_view_size.height)
|
142
|
+
|
143
|
+
CGContextDrawImage(context, drawing_rect, source_image)
|
144
|
+
|
145
|
+
CGBitmapContextCreateImage(context)
|
146
|
+
end
|
147
|
+
|
148
|
+
def viewDidAppear(animated)
|
149
|
+
super
|
150
|
+
|
151
|
+
image_view.image = preview_image
|
152
|
+
|
153
|
+
reset
|
154
|
+
end
|
155
|
+
|
156
|
+
def reset(options = {})
|
157
|
+
animated = options.fetch(:animated, false)
|
158
|
+
|
159
|
+
w = 0.0
|
160
|
+
h = 0.0
|
161
|
+
|
162
|
+
source_aspect = source_image.size.height / source_image.size.width
|
163
|
+
crop_aspect = crop_rect.size.height / crop_rect.size.width
|
164
|
+
|
165
|
+
if source_aspect > crop_aspect
|
166
|
+
w = crop_rect.size.width
|
167
|
+
h = source_aspect * w
|
168
|
+
else
|
169
|
+
h = crop_rect.size.height
|
170
|
+
w = h / source_aspect
|
171
|
+
end
|
172
|
+
|
173
|
+
@scale = 1
|
174
|
+
|
175
|
+
reset_block = -> {
|
176
|
+
image_view.transform = CGAffineTransformIdentity
|
177
|
+
image_view.frame = CGRectMake(CGRectGetMidX(crop_rect) - w / 2, CGRectGetMidY(crop_rect) - h / 2, w, h)
|
178
|
+
image_view.transform = CGAffineTransformMakeScale(scale, scale)
|
179
|
+
}
|
180
|
+
|
181
|
+
if animated
|
182
|
+
view.userInteractionEnabled = false
|
183
|
+
|
184
|
+
UIView.animateWithDuration(ANIMATION_DURATION, animations: reset_block, completion: -> (finished) {
|
185
|
+
view.userInteractionEnabled = true
|
186
|
+
})
|
187
|
+
else
|
188
|
+
reset_block.call
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# Touch handling
|
193
|
+
|
194
|
+
def touchesBegan(touches, withEvent: event)
|
195
|
+
handle_touches(event.allTouches)
|
196
|
+
end
|
197
|
+
|
198
|
+
def touchesMoved(touches, withEvent: event)
|
199
|
+
handle_touches(event.allTouches)
|
200
|
+
end
|
201
|
+
|
202
|
+
def touchesEnded(touches, withEvent: event)
|
203
|
+
handle_touches(event.allTouches)
|
204
|
+
end
|
205
|
+
|
206
|
+
def touchesCancelled(touches, withEvent: event)
|
207
|
+
handle_touches(event.allTouches)
|
208
|
+
end
|
209
|
+
|
210
|
+
def handle_touches(touches)
|
211
|
+
@touch_center = CGPointZero
|
212
|
+
|
213
|
+
if touches.count >= 2
|
214
|
+
touches.each do |touch|
|
215
|
+
touch_location = touch.locationInView(image_view)
|
216
|
+
|
217
|
+
@touch_center = CGPointMake(touch_center.x + touch_location.x, touch_center.y + touch_location.y)
|
218
|
+
end
|
219
|
+
|
220
|
+
@touch_center = CGPointMake(touch_center.x / touches.count, touch_center.y / touches.count)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def handle_state?(state)
|
225
|
+
if state == UIGestureRecognizerStateEnded
|
226
|
+
new_scale = bounded_scale(scale)
|
227
|
+
|
228
|
+
delta_x = scale_center.x - image_view.bounds.size.width / 2.0
|
229
|
+
delta_y = scale_center.y - image_view.bounds.size.height / 2.0
|
230
|
+
|
231
|
+
transform = CGAffineTransformTranslate(image_view.transform, delta_x, delta_y)
|
232
|
+
|
233
|
+
transform = CGAffineTransformScale(transform, new_scale / scale , new_scale / scale)
|
234
|
+
|
235
|
+
transform = CGAffineTransformTranslate(transform, -delta_x, -delta_y)
|
236
|
+
|
237
|
+
view.userInteractionEnabled = false
|
238
|
+
|
239
|
+
UIView.animateWithDuration(
|
240
|
+
ANIMATION_DURATION,
|
241
|
+
delay: 0,
|
242
|
+
options: UIViewAnimationOptionCurveEaseOut,
|
243
|
+
animations: -> { image_view.transform = transform },
|
244
|
+
completion: -> (finished) {
|
245
|
+
view.userInteractionEnabled = true
|
246
|
+
@scale = new_scale
|
247
|
+
})
|
248
|
+
|
249
|
+
false
|
250
|
+
else
|
251
|
+
true
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def handle_pan(recognizer)
|
256
|
+
translation = recognizer.translationInView(image_view)
|
257
|
+
transform = CGAffineTransformTranslate(image_view.transform, translation.x, translation.y)
|
258
|
+
|
259
|
+
image_view.transform = transform
|
260
|
+
|
261
|
+
recognizer.setTranslation(CGPointZero, inView: crop_view)
|
262
|
+
end
|
263
|
+
|
264
|
+
def handle_pinch(recognizer)
|
265
|
+
if handle_state? recognizer.state
|
266
|
+
if recognizer.state == UIGestureRecognizerStateBegan
|
267
|
+
@scale_center = @touch_center
|
268
|
+
end
|
269
|
+
|
270
|
+
delta_x = scale_center.x - image_view.bounds.size.width / 2.0
|
271
|
+
delta_y = scale_center.y - image_view.bounds.size.height / 2.0
|
272
|
+
|
273
|
+
transform = CGAffineTransformTranslate(image_view.transform, delta_x, delta_y)
|
274
|
+
transform = CGAffineTransformScale(transform, recognizer.scale, recognizer.scale)
|
275
|
+
transform = CGAffineTransformTranslate(transform, -delta_x, -delta_y)
|
276
|
+
|
277
|
+
@scale *= recognizer.scale
|
278
|
+
|
279
|
+
image_view.transform = transform
|
280
|
+
|
281
|
+
recognizer.scale = 1
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
def handle_rotation(recognizer)
|
286
|
+
delta_x = touch_center.x - image_view.bounds.size.width / 2
|
287
|
+
delta_y = touch_center.y - image_view.bounds.size.height / 2
|
288
|
+
|
289
|
+
transform = CGAffineTransformTranslate(image_view.transform, delta_x, delta_y)
|
290
|
+
transform = CGAffineTransformRotate(transform, recognizer.rotation)
|
291
|
+
transform = CGAffineTransformTranslate(transform, -delta_x, -delta_y)
|
292
|
+
|
293
|
+
image_view.transform = transform
|
294
|
+
|
295
|
+
recognizer.rotation = 0
|
296
|
+
end
|
297
|
+
|
298
|
+
def handle_tap(recognizer)
|
299
|
+
reset(animated: true)
|
300
|
+
end
|
301
|
+
|
302
|
+
def pan_recognizer
|
303
|
+
@pan_recognizer ||= UIPanGestureRecognizer.alloc.initWithTarget(self, action: 'handle_pan:').tap do |recognizer|
|
304
|
+
recognizer.cancelsTouchesInView = false
|
305
|
+
recognizer.delegate = self
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def tap_recognizer
|
310
|
+
@tap_recognizer ||= UITapGestureRecognizer.alloc.initWithTarget(self, action: 'handle_tap:').tap do |recognizer|
|
311
|
+
recognizer.numberOfTapsRequired = 2
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
def rotation_recognizer
|
316
|
+
@rotation_recognizer ||= UIRotationGestureRecognizer.alloc.initWithTarget(self, action: 'handle_rotation:').tap do |recognizer|
|
317
|
+
recognizer.cancelsTouchesInView = false
|
318
|
+
recognizer.delegate = self
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
def pinch_recognizer
|
323
|
+
@pinch_recognizer ||= UIPinchGestureRecognizer.alloc.initWithTarget(self, action: 'handle_pinch:').tap do |recognizer|
|
324
|
+
recognizer.cancelsTouchesInView = false
|
325
|
+
recognizer.delegate = self
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
def source_image=(image)
|
330
|
+
if image != source_image
|
331
|
+
@source_image = image
|
332
|
+
@preview_image = nil
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
def bounded_scale(scale)
|
337
|
+
return scale if (MINIMUM_SCALE..MAXIMUM_SCALE).include? scale
|
338
|
+
|
339
|
+
scale < MINIMUM_SCALE ? MINIMUM_SCALE : MAXIMUM_SCALE
|
340
|
+
end
|
341
|
+
|
342
|
+
def preview_image
|
343
|
+
@preview_image ||= source_image.resizedImageToFitInSize(view.bounds.size, scaleIfSmaller: false)
|
344
|
+
end
|
345
|
+
|
346
|
+
def image_view
|
347
|
+
@image_view ||= UIImageView.alloc.init.tap do |view|
|
348
|
+
view.translatesAutoresizingMaskIntoConstraints = false
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
def crop_view
|
353
|
+
@crop_view ||= Motion::ImageEditorView.alloc.init.tap do |view|
|
354
|
+
view.translatesAutoresizingMaskIntoConstraints = false
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
def prefersStatusBarHidden
|
359
|
+
true
|
360
|
+
end
|
361
|
+
end; end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
class Motion; class ImageEditorView < UIView
|
2
|
+
def initWithFrame(frame)
|
3
|
+
super.tap do |view|
|
4
|
+
view.opaque = false
|
5
|
+
view.layer.opacity = 0.7
|
6
|
+
view.backgroundColor = UIColor.clearColor
|
7
|
+
|
8
|
+
view.addSubview(image_view)
|
9
|
+
|
10
|
+
view.setup_constraints
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def setup_constraints
|
15
|
+
addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(
|
16
|
+
'H:|[image]|',
|
17
|
+
options: 0,
|
18
|
+
metrics: nil,
|
19
|
+
views: { 'image' => image_view }))
|
20
|
+
|
21
|
+
addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(
|
22
|
+
'V:|[image]|',
|
23
|
+
options: 0,
|
24
|
+
metrics: nil,
|
25
|
+
views: { 'image' => image_view }))
|
26
|
+
end
|
27
|
+
|
28
|
+
def crop_rect
|
29
|
+
@crop_rect ||= CGRectZero
|
30
|
+
end
|
31
|
+
|
32
|
+
def crop_rect=(rect)
|
33
|
+
@crop_rect = CGRectOffset(rect, frame.origin.x, frame.origin.y)
|
34
|
+
|
35
|
+
UIGraphicsBeginImageContextWithOptions(bounds.size, false, 0.0)
|
36
|
+
|
37
|
+
context = UIGraphicsGetCurrentContext()
|
38
|
+
|
39
|
+
UIColor.blackColor.setFill
|
40
|
+
UIRectFill(bounds)
|
41
|
+
|
42
|
+
UIColor.clearColor.setFill
|
43
|
+
UIRectFill(CGRectInset(rect, 1, 1))
|
44
|
+
|
45
|
+
image_view.image = UIGraphicsGetImageFromCurrentImageContext()
|
46
|
+
|
47
|
+
UIGraphicsEndImageContext()
|
48
|
+
end
|
49
|
+
|
50
|
+
def image_view
|
51
|
+
@image_view ||= UIImageView.alloc.init.tap do |image_view|
|
52
|
+
image_view.translatesAutoresizingMaskIntoConstraints = false
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end; end
|
metadata
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: motion-image-editor
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Devon Blandin
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-10-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: motion-cocoapods
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: RubyMotion image editing controller
|
42
|
+
email:
|
43
|
+
- dblandin@gmail.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- README.md
|
49
|
+
- lib/motion-image-editor.rb
|
50
|
+
- lib/project/controllers/image_editor_controller.rb
|
51
|
+
- lib/project/views/image_editor_view.rb
|
52
|
+
homepage: http://github.com/dblandin/motion-image-editor
|
53
|
+
licenses:
|
54
|
+
- MIT
|
55
|
+
metadata: {}
|
56
|
+
post_install_message:
|
57
|
+
rdoc_options: []
|
58
|
+
require_paths:
|
59
|
+
- lib
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - '>='
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
requirements: []
|
71
|
+
rubyforge_project:
|
72
|
+
rubygems_version: 2.1.1
|
73
|
+
signing_key:
|
74
|
+
specification_version: 4
|
75
|
+
summary: Easily scale, rotate, and crop images
|
76
|
+
test_files: []
|