motion-image-editor 0.0.1

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,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: []