motion-image-editor 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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: []