image_cropper 1.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 +228 -0
- data/app/assets/javascript/image_cropper.js +688 -0
- data/app/assets/stylesheets/image_cropper.css +199 -0
- data/lib/generators/image_cropper/install_generator.rb +14 -0
- data/lib/generators/image_cropper/migrate_generator.rb +27 -0
- data/lib/generators/image_cropper/templates/images/default.png +0 -0
- data/lib/generators/image_cropper/templates/migration/migrate.rb +11 -0
- data/lib/image_cropper.rb +13 -0
- data/lib/image_cropper/model.rb +28 -0
- data/lib/image_cropper/rails.rb +8 -0
- data/lib/image_cropper/rails/engine.rb +9 -0
- data/lib/image_cropper/rails/form_builder.rb +89 -0
- data/lib/image_cropper/rails/form_helper.rb +16 -0
- data/lib/image_cropper/uploader.rb +89 -0
- data/lib/image_cropper/version.rb +3 -0
- metadata +114 -0
@@ -0,0 +1,199 @@
|
|
1
|
+
|
2
|
+
.image-cropper-container{
|
3
|
+
display: block;
|
4
|
+
width: 100%;
|
5
|
+
}
|
6
|
+
|
7
|
+
|
8
|
+
.image-cropper-container .image-cropper-crop-container{
|
9
|
+
position: relative;
|
10
|
+
}
|
11
|
+
|
12
|
+
|
13
|
+
|
14
|
+
|
15
|
+
|
16
|
+
|
17
|
+
.image-cropper-container .image-cropper-crop-container .image-cropper-crop-controls{
|
18
|
+
position: absolute;
|
19
|
+
right: 2%;
|
20
|
+
top: 2%;
|
21
|
+
bottom: 2%;
|
22
|
+
background-color: #0006;
|
23
|
+
padding: 10px;
|
24
|
+
border-radius: 8px;
|
25
|
+
-webkit-transition: background-color 0.5s ease;
|
26
|
+
-moz-transition: background-color 0.5s ease;
|
27
|
+
transition: background-color 0.5s ease;
|
28
|
+
z-index: 1;
|
29
|
+
display: flex;
|
30
|
+
flex-direction: column;
|
31
|
+
justify-content: space-around;
|
32
|
+
}
|
33
|
+
|
34
|
+
|
35
|
+
|
36
|
+
.image-cropper-container .image-cropper-crop-container .image-cropper-crop-controls .image-cropper-crop-control,
|
37
|
+
.image-cropper-container .image-cropper-crop-container .image-cropper-crop-controls .image-cropper-crop-control:hover,
|
38
|
+
.image-cropper-container .image-cropper-crop-container .image-cropper-crop-controls .image-cropper-crop-control:active,
|
39
|
+
.image-cropper-container .image-cropper-crop-container .image-cropper-crop-controls .image-cropper-crop-control:focus{
|
40
|
+
color: #FFF;
|
41
|
+
font-size: 32px;
|
42
|
+
width: 100%;
|
43
|
+
text-align: center;
|
44
|
+
text-decoration: none;
|
45
|
+
display: flex;
|
46
|
+
align-items: center;
|
47
|
+
justify-content: center;
|
48
|
+
opacity: 1;
|
49
|
+
outline: 0;
|
50
|
+
background-color: transparent;
|
51
|
+
}
|
52
|
+
|
53
|
+
|
54
|
+
.image-cropper-container .image-cropper-crop-container .image-cropper-crop-controls .image-cropper-crop-control:first-child{
|
55
|
+
margin-top: 0;
|
56
|
+
}
|
57
|
+
|
58
|
+
.image-cropper-container .image-cropper-crop-container .image-cropper-crop-controls .image-cropper-crop-control:hover i.fa{
|
59
|
+
color: #9F9;
|
60
|
+
-webkit-animation-iteration-count: 1;
|
61
|
+
animation-iteration-count: 1;
|
62
|
+
-webkit-animation-duration: 1s;
|
63
|
+
animation-duration: 1s;
|
64
|
+
-webkit-animation-fill-mode: forwards;
|
65
|
+
animation-fill-mode: forwards;
|
66
|
+
-webkit-animation-name: pulse;
|
67
|
+
animation-name: pulse;
|
68
|
+
}
|
69
|
+
|
70
|
+
.image-cropper-container .image-cropper-crop-container img.image-cropper-crop{
|
71
|
+
max-width: 100%;
|
72
|
+
}
|
73
|
+
|
74
|
+
|
75
|
+
|
76
|
+
.image-cropper-container .image-cropper-file{
|
77
|
+
position: absolute;
|
78
|
+
width: 0;
|
79
|
+
height: 0;
|
80
|
+
opacity: 0;
|
81
|
+
margin-left: -500px;
|
82
|
+
}
|
83
|
+
|
84
|
+
|
85
|
+
.image-cropper-container.circle .image-cropper-img,
|
86
|
+
.image-cropper-container.circle .image-cropper-img-controls,
|
87
|
+
.image-cropper-container.circle .image-cropper-img-background,
|
88
|
+
.image-cropper-container.circle .guillotine-window{
|
89
|
+
border-radius: 50%;
|
90
|
+
}
|
91
|
+
|
92
|
+
|
93
|
+
|
94
|
+
.image-cropper-crop-container.disable .guillotine-window {
|
95
|
+
cursor: not-allowed;
|
96
|
+
cursor: -webkit-not-allowed;
|
97
|
+
cursor: -moz-not-allowed;
|
98
|
+
}
|
99
|
+
|
100
|
+
.image-cropper-container .image-cropper-crop-container.disable .image-cropper-crop-controls .image-cropper-crop-control.image-cropper-zoom-in,
|
101
|
+
.image-cropper-container .image-cropper-crop-container.disable .image-cropper-crop-controls .image-cropper-crop-control.image-cropper-zoom-out{
|
102
|
+
display: none;
|
103
|
+
}
|
104
|
+
|
105
|
+
|
106
|
+
|
107
|
+
|
108
|
+
|
109
|
+
|
110
|
+
@-webkit-keyframes pulse {
|
111
|
+
from {
|
112
|
+
-webkit-transform: scale3d(1, 1, 1);
|
113
|
+
transform: scale3d(1, 1, 1);
|
114
|
+
}
|
115
|
+
|
116
|
+
to {
|
117
|
+
-webkit-transform: scale3d(1.3, 1.3, 1.3);
|
118
|
+
transform: scale3d(1.3, 1.3, 1.3);
|
119
|
+
}
|
120
|
+
}
|
121
|
+
|
122
|
+
@keyframes pulse {
|
123
|
+
from {
|
124
|
+
-webkit-transform: scale3d(1, 1, 1);
|
125
|
+
transform: scale3d(1, 1, 1);
|
126
|
+
}
|
127
|
+
|
128
|
+
to {
|
129
|
+
-webkit-transform: scale3d(1.3, 1.3, 1.3);
|
130
|
+
transform: scale3d(1.3, 1.3, 1.3);
|
131
|
+
}
|
132
|
+
}
|
133
|
+
|
134
|
+
|
135
|
+
|
136
|
+
img.image-cropper{
|
137
|
+
max-width: 100%;
|
138
|
+
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC');
|
139
|
+
}
|
140
|
+
|
141
|
+
|
142
|
+
|
143
|
+
|
144
|
+
|
145
|
+
|
146
|
+
|
147
|
+
/* --------------- GUILLOTINE STYLE -----------------*/
|
148
|
+
|
149
|
+
|
150
|
+
body.guillotine-dragging, body.guillotine-dragging * {
|
151
|
+
cursor: move !important;
|
152
|
+
cursor: -webkit-grabbing !important;
|
153
|
+
cursor: -moz-grabbing !important;
|
154
|
+
cursor: grabbing !important;
|
155
|
+
cursor: grabbing, move; /* IE hack */
|
156
|
+
}
|
157
|
+
|
158
|
+
.guillotine-window {
|
159
|
+
display: block;
|
160
|
+
position: relative;
|
161
|
+
overflow: hidden;
|
162
|
+
cursor: move;
|
163
|
+
cursor: -webkit-grab;
|
164
|
+
cursor: -moz-grab;
|
165
|
+
cursor: grab;
|
166
|
+
cursor: grab, move; /* IE hack */
|
167
|
+
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC');
|
168
|
+
}
|
169
|
+
|
170
|
+
.guillotine-canvas {
|
171
|
+
position: absolute;
|
172
|
+
top: 0;
|
173
|
+
left: 0;
|
174
|
+
text-align: center;
|
175
|
+
margin: 0 !important;
|
176
|
+
padding: 0 !important;
|
177
|
+
border: none !important;
|
178
|
+
}
|
179
|
+
|
180
|
+
.guillotine-canvas > * {
|
181
|
+
position: absolute;
|
182
|
+
top: 0;
|
183
|
+
left: 0;
|
184
|
+
max-width: none;
|
185
|
+
max-height: none;
|
186
|
+
width: 100%;
|
187
|
+
height: 100%;
|
188
|
+
margin: 0 !important;
|
189
|
+
padding: 0 !important;
|
190
|
+
border: none !important;
|
191
|
+
}
|
192
|
+
|
193
|
+
.guillotine-sample {
|
194
|
+
position: absolute !important;
|
195
|
+
top: -100000px !important;
|
196
|
+
left: -100000px !important;
|
197
|
+
width: auto !important;
|
198
|
+
height: auto !important;
|
199
|
+
}
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
|
3
|
+
module ImageCropper
|
4
|
+
class InstallGenerator < ::Rails::Generators::Base
|
5
|
+
source_root File.expand_path("../templates", __FILE__)
|
6
|
+
|
7
|
+
|
8
|
+
def install
|
9
|
+
copy_file 'images/default.png', 'app/assets/images/image_cropper/default.png'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
require 'rails/generators/migration'
|
3
|
+
|
4
|
+
module ImageCropper
|
5
|
+
class MigrateGenerator < ::Rails::Generators::Base
|
6
|
+
include ::Rails::Generators::Migration
|
7
|
+
source_root File.expand_path('../templates', __FILE__)
|
8
|
+
|
9
|
+
desc "add coords to model"
|
10
|
+
|
11
|
+
def self.next_migration_number(path)
|
12
|
+
unless @prev_migration_nr
|
13
|
+
@prev_migration_nr = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i
|
14
|
+
else
|
15
|
+
@prev_migration_nr += 1
|
16
|
+
end
|
17
|
+
@prev_migration_nr.to_s
|
18
|
+
end
|
19
|
+
|
20
|
+
def copy_migrations()
|
21
|
+
if self.args.count == 1
|
22
|
+
@tableize_name = self.args.first.tableize
|
23
|
+
migration_template "migration/migrate.rb", "db/migrate/image_cropper_add_fields_to_#{@tableize_name}.rb"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
Binary file
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class ImageCropperAddFieldsTo<%= @tableize_name.camelize %> < ActiveRecord::Migration[5.1]
|
2
|
+
def change
|
3
|
+
#Used to crop file
|
4
|
+
add_column :<%= @tableize_name %>, :coord_x, :float, default: 0
|
5
|
+
add_column :<%= @tableize_name %>, :coord_y, :float, default: 0
|
6
|
+
add_column :<%= @tableize_name %>, :coord_w, :float, default: 0
|
7
|
+
add_column :<%= @tableize_name %>, :coord_h, :float, default: 0
|
8
|
+
add_column :<%= @tableize_name %>, :coord_z, :float, default: 1
|
9
|
+
add_column :<%= @tableize_name %>, :frame, :string, default: 'square'
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
|
2
|
+
# requires all dependencies
|
3
|
+
Gem.loaded_specs['image_cropper'].dependencies.each do |d|
|
4
|
+
require d.name
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'image_cropper/rails' if defined?(Rails)
|
8
|
+
require 'image_cropper/uploader.rb'
|
9
|
+
require 'image_cropper/model.rb'
|
10
|
+
|
11
|
+
module ImageCropper
|
12
|
+
end
|
13
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module ImageCropper::Model
|
2
|
+
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
|
7
|
+
|
8
|
+
after_update :crop_file
|
9
|
+
|
10
|
+
#If method not exist, default mount column is 'file'
|
11
|
+
unless self.methods(false).include?(:mount_uploader_columns)
|
12
|
+
def self.mount_uploader_columns
|
13
|
+
[:file]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def crop_file
|
18
|
+
self.class.mount_uploader_columns.each do |c|
|
19
|
+
#OLD VERSION of match:
|
20
|
+
#self.send("#{c}_was") == self.send(c)
|
21
|
+
self.send(c).recreate_versions! if self.send(c).present? && self.send("#{c}_before_last_save") == self.send(c).file.filename
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module ImageCropper
|
2
|
+
module Rails
|
3
|
+
module FormBuilder
|
4
|
+
include ActionView::Helpers::TagHelper
|
5
|
+
include ActionView::Helpers::FormTagHelper
|
6
|
+
include ActionView::Helpers::JavaScriptHelper
|
7
|
+
include ActionView::Context
|
8
|
+
|
9
|
+
def image_cropper_field(method, options = {})
|
10
|
+
aspect_ratio = options[:aspect_ratio] || '4:3'
|
11
|
+
frame = options[:frame] || 'square'
|
12
|
+
operation_object = options[:parent_object] || self
|
13
|
+
destroyable = options[:destroyable] || false
|
14
|
+
destroy_class = options[:destroy_class] || '.image-cropper-container'
|
15
|
+
destroy_confirm = options[:destroy_confirm] || 'Are you sure?'
|
16
|
+
image_cropper_id = operation_object.index || 'only'
|
17
|
+
|
18
|
+
|
19
|
+
content_tag(:div, id: "image-cropper-#{operation_object.object_id}-#{image_cropper_id}", class: "image-cropper-container #{frame}", data: { 'aspect-ratio' => aspect_ratio}) do
|
20
|
+
|
21
|
+
concat(
|
22
|
+
content_tag(:div, class: "image-cropper-crop-container") do
|
23
|
+
concat(
|
24
|
+
content_tag(:div, class: "image-cropper-crop-controls") do
|
25
|
+
concat(
|
26
|
+
link_to('javascript:;', class: "image-cropper-crop-control image-cropper-file-trigger") do
|
27
|
+
content_tag(:i, '', class: "fa fa-image")
|
28
|
+
end
|
29
|
+
)
|
30
|
+
concat(
|
31
|
+
link_to('javascript:;', class: "image-cropper-crop-control image-cropper-zoom-in") do
|
32
|
+
content_tag(:i, '', class: "fa fa-search-plus")
|
33
|
+
end
|
34
|
+
)
|
35
|
+
concat(
|
36
|
+
link_to('javascript:;', class: "image-cropper-crop-control image-cropper-zoom-out") do
|
37
|
+
content_tag(:i, '', class: "fa fa-search-minus")
|
38
|
+
end
|
39
|
+
)
|
40
|
+
if destroyable == true
|
41
|
+
concat(@template.hidden_field(operation_object.object_name, :_destroy, value: operation_object.object._destroy))
|
42
|
+
concat(
|
43
|
+
link_to('javascript:;', class: "image-cropper-crop-control image-cropper-remove", data: {destroy_confirm: destroy_confirm}) do
|
44
|
+
content_tag(:i, '', class: "fa fa-trash")
|
45
|
+
end
|
46
|
+
)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
)
|
50
|
+
concat(
|
51
|
+
@template.image_tag(@object.send(method.to_sym).url, class: 'image-cropper-crop')
|
52
|
+
)
|
53
|
+
end
|
54
|
+
)
|
55
|
+
concat(@template.hidden_field(@object_name, :coord_x, class: 'coord-x', value: @object.coord_x))
|
56
|
+
concat(@template.hidden_field(@object_name, :coord_y, class: 'coord-y', value: @object.coord_y))
|
57
|
+
concat(@template.hidden_field(@object_name, :coord_w, class: 'coord-w', value: @object.coord_w))
|
58
|
+
concat(@template.hidden_field(@object_name, :coord_h, class: 'coord-h', value: @object.coord_h))
|
59
|
+
concat(@template.hidden_field(@object_name, :coord_z, class: 'coord-z', value: @object.coord_z))
|
60
|
+
concat(@template.hidden_field(@object_name, :frame, value: frame))
|
61
|
+
|
62
|
+
concat(@template.file_field(@object_name, method.to_sym, class: 'image-cropper-file', autoComplete: 'off'))
|
63
|
+
if @object.send("#{method}_cache".to_sym).present?
|
64
|
+
concat(@template.hidden_field(@object_name, "#{method}_cache".to_sym, class: 'image-cropper-file-cache', value: @object.send("#{method}_cache".to_sym)))
|
65
|
+
end
|
66
|
+
concat(@template.hidden_field(@object_name, "remote_#{method}_url".to_sym, class: 'image-cropper-file-url', value: @object.send("remote_#{method}_url".to_sym)))
|
67
|
+
|
68
|
+
|
69
|
+
concat(content_tag(:div, '', style: "clear:both;"))
|
70
|
+
|
71
|
+
concat(
|
72
|
+
if @object.send("#{method}".to_sym).file.present?
|
73
|
+
javascript_tag("$('\#image-cropper-#{operation_object.object_id}-#{image_cropper_id}').image_cropper({destroy_class: '#{destroy_class}'});")
|
74
|
+
else
|
75
|
+
javascript_tag("$('\#image-cropper-#{operation_object.object_id}-#{image_cropper_id}').image_cropper({enabled: false, destroy_class: '#{destroy_class}'});")
|
76
|
+
end
|
77
|
+
)
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
ImageCropper::FormBuilder = ImageCropper::Rails::FormBuilder
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'action_view/helpers'
|
2
|
+
require 'image_cropper/rails/form_builder'
|
3
|
+
|
4
|
+
module ImageCropper
|
5
|
+
module Rails
|
6
|
+
module FormHelper
|
7
|
+
def self.included(_base)
|
8
|
+
ActionView::Helpers::FormBuilder.instance_eval do
|
9
|
+
include FormBuilder
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
ActionView::Base.send :include, ImageCropper::Rails::FormHelper
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module ImageCropper::Uploader
|
2
|
+
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
|
7
|
+
include CarrierWave::MiniMagick
|
8
|
+
|
9
|
+
def default_url(*args)
|
10
|
+
ActionController::Base.helpers.asset_path("image_cropper/default.png")
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
version :thumb do
|
15
|
+
process :crop
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
def crop
|
20
|
+
|
21
|
+
x = model.coord_x
|
22
|
+
y = model.coord_y
|
23
|
+
w = model.coord_w
|
24
|
+
h = model.coord_h
|
25
|
+
z = model.coord_z
|
26
|
+
w_z = self.width*z
|
27
|
+
h_z = self.height*z
|
28
|
+
|
29
|
+
manipulate! do |img|
|
30
|
+
|
31
|
+
#img.resize "#{w_z}x#{h_z}"
|
32
|
+
#img.coalesce
|
33
|
+
#img.repage "0x0"
|
34
|
+
#img.crop "#{w}x#{h}+#{x}+#{y}"
|
35
|
+
#img << "+repage"
|
36
|
+
|
37
|
+
img.combine_options do |c|
|
38
|
+
c.resize "#{w_z}x#{h_z}"
|
39
|
+
c.coalesce
|
40
|
+
c.repage("0x0")
|
41
|
+
c.crop "#{w}x#{h}+#{x}+#{y}"
|
42
|
+
c.args << '+repage'
|
43
|
+
end
|
44
|
+
|
45
|
+
if model.frame == 'circle'
|
46
|
+
|
47
|
+
mask = ::MiniMagick::Image.open img.path
|
48
|
+
|
49
|
+
mask.combine_options do |m|
|
50
|
+
m.alpha 'transparent'
|
51
|
+
m.background 'none'
|
52
|
+
m.fill 'white'
|
53
|
+
m.strokewidth 0
|
54
|
+
m.draw "ellipse #{w/2},#{h/2} #{w/2},#{h/2}, 0, 360"
|
55
|
+
end
|
56
|
+
|
57
|
+
overlay = ::MiniMagick::Image.open img.path
|
58
|
+
|
59
|
+
overlay.combine_options do |o|
|
60
|
+
o.alpha 'transparent'
|
61
|
+
o.background 'none'
|
62
|
+
o.fill 'none'
|
63
|
+
o.strokewidth 0
|
64
|
+
o.draw "ellipse #{w/2},#{h/2} #{w/2},#{h/2}, 0, 360"
|
65
|
+
end
|
66
|
+
|
67
|
+
masked = img.composite(mask) do |i|
|
68
|
+
i.alpha "set"
|
69
|
+
i.compose 'DstIn'
|
70
|
+
end
|
71
|
+
|
72
|
+
masked.composite(overlay) do |i|
|
73
|
+
i.compose 'Over'
|
74
|
+
end
|
75
|
+
|
76
|
+
else
|
77
|
+
|
78
|
+
img
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|