under-os-crop 1.0.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.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/Gemfile +3 -0
- data/README.md +75 -0
- data/lib/assets/uos-crop.css +30 -0
- data/lib/under-os-crop.rb +1 -0
- data/lib/under_os/crop.rb +96 -0
- data/lib/under_os/crop/processor.rb +94 -0
- data/lib/under_os/crop/scroll.rb +37 -0
- data/lib/under_os/crop/window.rb +77 -0
- data/lib/under_os/styles.rb +2 -0
- data/resources/Default-568h@2x.png +0 -0
- data/under-os-crop.gemspec +19 -0
- metadata +70 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 959af2e74aa93432122b4fd46402818a414f53bf
|
4
|
+
data.tar.gz: fa0ebb332fb43498c11de8c2f3c4ba67ac795158
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d3f40c0cd23bd720bbf6769b8311cb2c5c4e216189cbb50e77b86c0e26a9c35167a76ba5597e6edad800b496b94324cb16a4331a06a30b1c0e966f5c259ebc40
|
7
|
+
data.tar.gz: 55c6742e4e0fa43ea4e384ce00a3a167fae0b1dd1aae0e554c8e1943e64dd71dc317527f2d4b7513b84d5650bed5a275065cdd5905ed6d70516e5a08602b880a
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# UnderOS Crop
|
2
|
+
|
3
|
+
This is a nice image crop plugin for the [under-os](http://under-os.com) project.
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
|
7
|
+
Add it to your `Gemfile`
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem `under-os`
|
11
|
+
gem `under-os-crop`
|
12
|
+
```
|
13
|
+
|
14
|
+
Then you can either use it as a `CROP` element in your HTML markup
|
15
|
+
|
16
|
+
```html
|
17
|
+
<page title="Image crop">
|
18
|
+
<crop id="the-crop"></crop>
|
19
|
+
</page>
|
20
|
+
```
|
21
|
+
|
22
|
+
Or spawn it programmatically in your `UOS::Page` controller
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
class CropPage < UOS::Page
|
26
|
+
def initialize
|
27
|
+
@crop = UOS::Crop.new
|
28
|
+
end
|
29
|
+
end
|
30
|
+
```
|
31
|
+
|
32
|
+
Either way the `UOS::Crop` class is a subclass of the `UOS::UI::View` so
|
33
|
+
all the normal under-os rules apply.
|
34
|
+
|
35
|
+
## Cropping images
|
36
|
+
|
37
|
+
Once you've got an instance of the `UOS::Crop` class, you can give it an
|
38
|
+
instance of a `UIImage` class as the image that needs to be cropped and
|
39
|
+
then read it the same way from the same `#src` property
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
# set a new image to crop
|
43
|
+
@crop.src = UIImage.alloc.initWithImage("test.png")
|
44
|
+
|
45
|
+
# whenever the user is done, you can read it back
|
46
|
+
@crop.src # -> the cropped image
|
47
|
+
```
|
48
|
+
|
49
|
+
## Setting Aspect Ratio
|
50
|
+
|
51
|
+
You can set an aspect ratio for the crop widget as well through the `#ratio` property
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
@crop.ratio = "3:4"
|
55
|
+
@crop.ratio = "1:1"
|
56
|
+
@crop.ratio = nil # no ratio
|
57
|
+
```
|
58
|
+
|
59
|
+
## Tilting & Turning
|
60
|
+
|
61
|
+
You also can set the image tilt programmatically (say from a slider in UI) or turn
|
62
|
+
the image 90 degrees with the `#turn` and `#tilt` methods
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
@crop.turn Math::PI / 4 # turn 90 degrees
|
66
|
+
@crop.tilt Math::PI / 12 # tilt image by 30 degrees
|
67
|
+
```
|
68
|
+
|
69
|
+
## Copyright & License
|
70
|
+
|
71
|
+
All the code in this repository is released under the terms of the MIT license
|
72
|
+
|
73
|
+
Copyright (C) 2014 Nikolay Nemshilov
|
74
|
+
|
75
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
.crop {
|
2
|
+
border-radius: 0;
|
3
|
+
}
|
4
|
+
.crop scroll {
|
5
|
+
width: 100%;
|
6
|
+
height: 100%;
|
7
|
+
overflow: hidden;
|
8
|
+
}
|
9
|
+
|
10
|
+
.crop .overlay {
|
11
|
+
background: #000;
|
12
|
+
opacity: 0.7;
|
13
|
+
z-index: 1;
|
14
|
+
}
|
15
|
+
|
16
|
+
.crop .window {
|
17
|
+
z-index: 2;
|
18
|
+
border-width: 1px;
|
19
|
+
border-color: rgba(200,200,200,0.5);
|
20
|
+
}
|
21
|
+
|
22
|
+
.crop .window .bar {
|
23
|
+
background: rgba(255,255,255,0.3);
|
24
|
+
}
|
25
|
+
.crop .window .bar.horizontal {
|
26
|
+
height: 1px;
|
27
|
+
}
|
28
|
+
.crop .window .bar.vertical {
|
29
|
+
width: 1px;
|
30
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
UnderOs.extend __FILE__
|
@@ -0,0 +1,96 @@
|
|
1
|
+
#
|
2
|
+
# An image crop UI
|
3
|
+
#
|
4
|
+
module UnderOs
|
5
|
+
class Crop < UnderOs::UI::View
|
6
|
+
tag :crop
|
7
|
+
|
8
|
+
attr_reader :ratio, :image
|
9
|
+
|
10
|
+
def initialize(options={})
|
11
|
+
super options.merge(class: 'crop')
|
12
|
+
|
13
|
+
append @scroll = Scroll.new
|
14
|
+
append @window = Window.new
|
15
|
+
|
16
|
+
@processor = Processor.new
|
17
|
+
self.ratio = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def ratio=(ratio)
|
21
|
+
ratio = ratio.to_s
|
22
|
+
ratio = "#{@original.size.width}:#{@original.size.height}" if ratio == 'true'
|
23
|
+
|
24
|
+
if m = ratio.match(/^([\d\.+]+):([\d\.]+)$/)
|
25
|
+
@ratio = m[1].to_f / m[2].to_f
|
26
|
+
else
|
27
|
+
@ratio = nil
|
28
|
+
end
|
29
|
+
|
30
|
+
@window.expand @ratio if self.size.x > 0
|
31
|
+
end
|
32
|
+
|
33
|
+
def repaint(*args)
|
34
|
+
super(*args).tap{ @window.expand @ratio if @window }
|
35
|
+
end
|
36
|
+
|
37
|
+
def src
|
38
|
+
@processor.render *original_crop_rectangle
|
39
|
+
end
|
40
|
+
|
41
|
+
def src=(image)
|
42
|
+
@processor.image = image
|
43
|
+
@original = @processor.image # fixed original
|
44
|
+
reset
|
45
|
+
end
|
46
|
+
|
47
|
+
def reset
|
48
|
+
@scroll.resetting!
|
49
|
+
render { @processor.reset }
|
50
|
+
end
|
51
|
+
|
52
|
+
def turn(angle)
|
53
|
+
render { @processor.turn(angle) }
|
54
|
+
end
|
55
|
+
|
56
|
+
def tilt(angle)
|
57
|
+
render { @processor.tilt(angle) }
|
58
|
+
end
|
59
|
+
|
60
|
+
protected
|
61
|
+
|
62
|
+
def render(&block)
|
63
|
+
return if @_working; @_working = true
|
64
|
+
|
65
|
+
Dispatch::Queue.new("uos.gems.crop.tilt").async do
|
66
|
+
new_image = block.call
|
67
|
+
|
68
|
+
Dispatch::Queue.main.async do
|
69
|
+
@scroll.image = new_image
|
70
|
+
@_working = false
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def original_crop_rectangle
|
76
|
+
content_size = @scroll.contentSize / @scroll.scale
|
77
|
+
crop_offset = @scroll.contentOffset / @scroll.scale + @window.position / 2 + 1
|
78
|
+
crop_frame = @window.size / @scroll.scale
|
79
|
+
|
80
|
+
crop_offset.x = 0 if crop_offset.x < 0 # tall image
|
81
|
+
crop_offset.y = 0 if crop_offset.y < 0 # wide image
|
82
|
+
|
83
|
+
crop_frame.x = content_size.x if crop_frame.x > content_size.x
|
84
|
+
crop_frame.y = content_size.y if crop_frame.y > content_size.y
|
85
|
+
|
86
|
+
original_is_h = @original.size.width / @original.size.height > 1
|
87
|
+
content_is_h = content_size.x / content_size.y > 1
|
88
|
+
original_scale = @original.size.width / content_size.__send__(original_is_h == content_is_h ? :x : :y)
|
89
|
+
|
90
|
+
original_offset = crop_offset * original_scale
|
91
|
+
original_frame = crop_frame * original_scale
|
92
|
+
|
93
|
+
[original_offset.x, original_offset.y, original_frame.x, original_frame.y]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
class UnderOs::Crop::Processor
|
2
|
+
|
3
|
+
def initialize
|
4
|
+
@tilt_filter = CIFilter.filterWithName("CIStraightenFilter")
|
5
|
+
@turn_filter = CIFilter.filterWithName("CIAffineTransform")
|
6
|
+
@crop_filter = CIFilter.filterWithName("CICrop")
|
7
|
+
@context = begin
|
8
|
+
gl_context = EAGLContext.alloc.initWithAPI(KEAGLRenderingAPIOpenGLES2)
|
9
|
+
options = {KCIContextWorkingColorSpace => nil}
|
10
|
+
CIContext.contextWithEAGLContext(gl_context, options:options)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def image
|
15
|
+
@original
|
16
|
+
end
|
17
|
+
|
18
|
+
def image=(image)
|
19
|
+
@original = fix_orientation(image)
|
20
|
+
|
21
|
+
resize(@original).tap do |ui_image|
|
22
|
+
@resized_original = ui_image
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def reset
|
27
|
+
@resized_original.tap do |ui_image|
|
28
|
+
@working = CIImage.alloc.initWithImage(ui_image)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def turn(angle)
|
33
|
+
transform = NSValue.valueWithCGAffineTransform(CGAffineTransformMakeRotation(angle))
|
34
|
+
@turn_filter.setValue(transform, forKey:"inputTransform")
|
35
|
+
reset
|
36
|
+
apply(@turn_filter).tap{ |image| @working = CIImage.alloc.initWithImage(image) }
|
37
|
+
apply(@tilt_filter)
|
38
|
+
end
|
39
|
+
|
40
|
+
def tilt(angle)
|
41
|
+
@tilt_filter.setValue(angle, forKey:"inputAngle")
|
42
|
+
apply @tilt_filter
|
43
|
+
end
|
44
|
+
|
45
|
+
def crop(x, y, width, height)
|
46
|
+
img_size = UIImage.alloc.initWithCIImage(@working).size
|
47
|
+
y = img_size.height - y - height # this filter counts from the bottom
|
48
|
+
rectangle = CIVector.vectorWithX(x, Y:y, Z:width, W:height)
|
49
|
+
@crop_filter.setValue(rectangle, forKey:'inputRectangle')
|
50
|
+
apply @crop_filter
|
51
|
+
end
|
52
|
+
|
53
|
+
def render(*crop_rect)
|
54
|
+
@_working_image = @working
|
55
|
+
|
56
|
+
@working = CIImage.alloc.initWithImage(@original)
|
57
|
+
@working = CIImage.alloc.initWithImage(apply(@turn_filter))
|
58
|
+
@working = CIImage.alloc.initWithImage(apply(@tilt_filter))
|
59
|
+
|
60
|
+
crop(*crop_rect).tap{ |i| @working = @_working_image }
|
61
|
+
end
|
62
|
+
|
63
|
+
protected
|
64
|
+
|
65
|
+
def apply(filter)
|
66
|
+
filter.setValue(@working, forKey: 'inputImage')
|
67
|
+
|
68
|
+
image = filter.outputImage
|
69
|
+
cg_image = @context.createCGImage(image, fromRect:image.extent)
|
70
|
+
image = UIImage.imageWithCGImage(cg_image)
|
71
|
+
|
72
|
+
CGImageRelease(cg_image)
|
73
|
+
filter.setValue(nil, forKey: 'inputImage')
|
74
|
+
|
75
|
+
image
|
76
|
+
end
|
77
|
+
|
78
|
+
def fix_orientation(image)
|
79
|
+
resize(image, image.size.width)
|
80
|
+
end
|
81
|
+
|
82
|
+
def resize(image, max_width=nil)
|
83
|
+
max_width ||= UOS::Screen.size.x * 4 # double the retina for zoom
|
84
|
+
ratio = max_width / image.size.width
|
85
|
+
new_size = CGSizeMake(max_width, image.size.height * ratio)
|
86
|
+
|
87
|
+
UIGraphicsBeginImageContext(new_size)
|
88
|
+
image.drawInRect(CGRectMake(0,0,new_size.width,new_size.height))
|
89
|
+
new_image = UIGraphicsGetImageFromCurrentImageContext()
|
90
|
+
UIGraphicsEndImageContext()
|
91
|
+
|
92
|
+
new_image
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class UnderOs::Crop < UnderOs::UI::View
|
2
|
+
class Scroll < UOS::UI::Scroll
|
3
|
+
attr_reader :image
|
4
|
+
|
5
|
+
def initialize(options={})
|
6
|
+
super options
|
7
|
+
|
8
|
+
UnderOs::App.history.current_page._.automaticallyAdjustsScrollViewInsets = false
|
9
|
+
|
10
|
+
self.minScale = 1.0
|
11
|
+
self.maxScale = 3.0
|
12
|
+
@_.decelerationRate = 0.2
|
13
|
+
|
14
|
+
append @image = UOS::UI::Image.new
|
15
|
+
|
16
|
+
self.zoomItem = @image
|
17
|
+
self.on(:zoom) { centerContent }
|
18
|
+
end
|
19
|
+
|
20
|
+
def image=(src)
|
21
|
+
@image.src = src
|
22
|
+
self.scale = 1.0 and @resetting = nil if @resetting
|
23
|
+
|
24
|
+
scale = [self.size.x / src.size.width, self.size.y / src.size.height].min
|
25
|
+
@new_size = {x: src.size.width * scale * self.scale, y: src.size.height * scale * self.scale}
|
26
|
+
|
27
|
+
@image.size = @new_size
|
28
|
+
self.contentSize = @new_size
|
29
|
+
|
30
|
+
centerContent
|
31
|
+
end
|
32
|
+
|
33
|
+
def resetting!
|
34
|
+
@resetting = true
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
class UnderOs::Crop::Window < UnderOs::UI::View
|
2
|
+
def initialize(options={})
|
3
|
+
super class: 'window'
|
4
|
+
|
5
|
+
@_.userInteractionEnabled = false
|
6
|
+
|
7
|
+
append @over_1 = Overlay.new
|
8
|
+
append @over_2 = Overlay.new
|
9
|
+
|
10
|
+
append @bar_h1 = Bar.new('horizontal')
|
11
|
+
append @bar_h2 = Bar.new('horizontal')
|
12
|
+
|
13
|
+
append @bar_v1 = Bar.new('vertical')
|
14
|
+
append @bar_v2 = Bar.new('vertical')
|
15
|
+
end
|
16
|
+
|
17
|
+
def expand(ratio)
|
18
|
+
parent_size = parent.size
|
19
|
+
parent_ratio = parent_size.x / parent_size.y
|
20
|
+
ratio ||= parent_ratio
|
21
|
+
new_size = {x: parent_size.x, y: parent_size.y}
|
22
|
+
|
23
|
+
if ratio > parent_ratio
|
24
|
+
new_size[:y] = parent_size.x / ratio
|
25
|
+
else
|
26
|
+
new_size[:x] = parent_size.y * ratio
|
27
|
+
end
|
28
|
+
|
29
|
+
self.size = {x: new_size[:x] + 2, y: new_size[:y] + 2}
|
30
|
+
end
|
31
|
+
|
32
|
+
def size=(new_size)
|
33
|
+
super new_size
|
34
|
+
|
35
|
+
self.position = {
|
36
|
+
x: parent.size.x / 2 - size.x / 2,
|
37
|
+
y: parent.size.y / 2 - size.y / 2
|
38
|
+
}
|
39
|
+
|
40
|
+
move_bars
|
41
|
+
move_overlays
|
42
|
+
end
|
43
|
+
|
44
|
+
def move_bars
|
45
|
+
@bar_h1.position.y = size.y / 3
|
46
|
+
@bar_h2.position.y = size.y / 3 * 2
|
47
|
+
@bar_v1.position.x = size.x / 3
|
48
|
+
@bar_v2.position.x = size.x / 3 * 2
|
49
|
+
|
50
|
+
@bar_h1.size.x = size.x
|
51
|
+
@bar_h2.size.x = size.x
|
52
|
+
@bar_v1.size.y = size.y
|
53
|
+
@bar_v2.size.y = size.y
|
54
|
+
end
|
55
|
+
|
56
|
+
def move_overlays
|
57
|
+
if position.x > position.y # vertical
|
58
|
+
@over_1.style = {width: position.x, height: size.y, top: 0, left: -position.x}
|
59
|
+
@over_2.style = {width: position.x, height: size.y, top: 0, right: -position.x}
|
60
|
+
else # horizontal
|
61
|
+
@over_1.style = {width: size.x, height: position.y, left: 0, top: -position.y}
|
62
|
+
@over_2.style = {width: size.x, height: position.y, left: 0, bottom: -position.y}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class Bar < UnderOs::UI::View
|
67
|
+
def initialize(orientation)
|
68
|
+
super class: "bar #{orientation}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class Overlay < UnderOs::UI::View
|
73
|
+
def initialize(options={})
|
74
|
+
super class: 'overlay'
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
Binary file
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |gem|
|
4
|
+
gem.name = "under-os-crop"
|
5
|
+
gem.version = '1.0.0'
|
6
|
+
|
7
|
+
gem.authors = ["Nikolay Nemshilov"]
|
8
|
+
gem.email = ['nemshilov@gmail.com']
|
9
|
+
gem.description = "The images crop UI for UnderOS"
|
10
|
+
gem.summary = "The images crop UI for UnderOS. Like totes"
|
11
|
+
gem.license = 'MIT'
|
12
|
+
|
13
|
+
gem.files = `git ls-files`.split($/).reject{|f| f.slice(0,5) == 'gems/'}
|
14
|
+
gem.executables = gem.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
15
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
16
|
+
|
17
|
+
gem.add_runtime_dependency 'under-os-ui'
|
18
|
+
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: under-os-crop
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nikolay Nemshilov
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-05-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: under-os-ui
|
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
|
+
description: The images crop UI for UnderOS
|
28
|
+
email:
|
29
|
+
- nemshilov@gmail.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- ".gitignore"
|
35
|
+
- Gemfile
|
36
|
+
- README.md
|
37
|
+
- lib/assets/uos-crop.css
|
38
|
+
- lib/under-os-crop.rb
|
39
|
+
- lib/under_os/crop.rb
|
40
|
+
- lib/under_os/crop/processor.rb
|
41
|
+
- lib/under_os/crop/scroll.rb
|
42
|
+
- lib/under_os/crop/window.rb
|
43
|
+
- lib/under_os/styles.rb
|
44
|
+
- resources/Default-568h@2x.png
|
45
|
+
- under-os-crop.gemspec
|
46
|
+
homepage:
|
47
|
+
licenses:
|
48
|
+
- MIT
|
49
|
+
metadata: {}
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options: []
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
requirements: []
|
65
|
+
rubyforge_project:
|
66
|
+
rubygems_version: 2.2.2
|
67
|
+
signing_key:
|
68
|
+
specification_version: 4
|
69
|
+
summary: The images crop UI for UnderOS. Like totes
|
70
|
+
test_files: []
|