image_cropper 1.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.
- 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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2e153fbfc633756a6098169bdb4c5e8e7b7f94b8
|
4
|
+
data.tar.gz: c1ea8b304c8ed45b56c204ed4ae40005add49760
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a75b951de33ab1038173d78d0f6a258ffe0acb9442a00ab17787bc9f8153827b43ef46a3a593adfb9fac34c0737a5722b26e9055240a77c1289f71210118b06d
|
7
|
+
data.tar.gz: 7708f4b822e256822940d02495f2b57c41e4356ef4be3762c80b2a44c1bf872196f6b2336d38491bf60ae5cd9d8b0732f09137959fe82385c1a269319a00ea7b
|
data/README.md
ADDED
@@ -0,0 +1,228 @@
|
|
1
|
+
# image_cropper
|
2
|
+
|
3
|
+
Combine upload images with a cropper
|
4
|
+
|
5
|
+
|
6
|
+
## Features
|
7
|
+
|
8
|
+
* Previews image before upload
|
9
|
+
* Considers carrierwave cache (when form return with error)
|
10
|
+
* Responsive
|
11
|
+
* Real crop (generate 2 files, and in case of circle frame, effectively crop an circular image)
|
12
|
+
|
13
|
+
## Dependencies
|
14
|
+
|
15
|
+
* Gem [jquery-rails](https://github.com/rails/jquery-rails)
|
16
|
+
* Gem [CarrierWave](https://github.com/carrierwaveuploader/carrierwave) to upload files
|
17
|
+
* Gem [RMagick](https://github.com/rmagick/rmagick) to crop images
|
18
|
+
* Gem [font-awesome-rails](https://github.com/bokmann/font-awesome-rails) to show pretty icons
|
19
|
+
|
20
|
+
|
21
|
+
___
|
22
|
+
|
23
|
+
|
24
|
+
# Getting Started
|
25
|
+
|
26
|
+
## Installation
|
27
|
+
|
28
|
+
Add this line to your application's Gemfile:
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
gem 'image_cropper', git: 'git@gitlab.snappler.com:gems/image_cropper.git'
|
32
|
+
```
|
33
|
+
|
34
|
+
And then execute:
|
35
|
+
|
36
|
+
$ bundle install
|
37
|
+
|
38
|
+
|
39
|
+
Finally run:
|
40
|
+
|
41
|
+
rails g image_cropper:install
|
42
|
+
|
43
|
+
This one generate the default null image **'app/assets/images/image_cropper/default.png'** used to show when the image window don't have one (later you can modify if you need)
|
44
|
+
|
45
|
+
## Create/Update the Uploader
|
46
|
+
|
47
|
+
Start off by [generating an uploader](https://github.com/carrierwaveuploader/carrierwave#user-content-getting-started) or modify if you already get one:
|
48
|
+
|
49
|
+
Using "Image" as an example, you should have a file
|
50
|
+
|
51
|
+
app/uploaders/image_uploader.rb
|
52
|
+
|
53
|
+
Add these lines and leave it like this:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
class ImageUploader < CarrierWave::Uploader::Base
|
57
|
+
|
58
|
+
#If you need to define another/s columns, just define in class
|
59
|
+
#this methods and puts in th array, but it's no necesary and take :file as default
|
60
|
+
#def self.mount_uploader_columns
|
61
|
+
# [:file]
|
62
|
+
#end
|
63
|
+
|
64
|
+
#Include the functionality of the gem
|
65
|
+
include ImageCropper::Uploader
|
66
|
+
|
67
|
+
storage :file
|
68
|
+
|
69
|
+
def store_dir
|
70
|
+
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
```
|
74
|
+
|
75
|
+
|
76
|
+
|
77
|
+
|
78
|
+
|
79
|
+
## Update Model
|
80
|
+
|
81
|
+
As an example I use a dummy model **User**:
|
82
|
+
|
83
|
+
$ rails g scaffold User name
|
84
|
+
|
85
|
+
Then integrate with [Carrierwave generating the column that it need](https://github.com/carrierwaveuploader/carrierwave#user-content-activerecord)
|
86
|
+
_(In the example of carrierwave use **avatar** as column, in this one I choose **file** as column because is more pretty :P)_
|
87
|
+
|
88
|
+
|
89
|
+
|
90
|
+
Now, add fields to the model you want to mount the uploader by executing:
|
91
|
+
|
92
|
+
$ rails g image_cropper:migrate User
|
93
|
+
|
94
|
+
This generate a file **XXXXXXXXXXXXXX_image_cropper_add_fields_to_users.rb**
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
class ImageCropperAddFieldsToUsers < ActiveRecord::Migration
|
98
|
+
def change
|
99
|
+
#Used to crop file
|
100
|
+
add_column :users, :coord_x, :float, default: 0
|
101
|
+
add_column :users, :coord_y, :float, default: 0
|
102
|
+
add_column :users, :coord_w, :float, default: 0
|
103
|
+
add_column :users, :coord_h, :float, default: 0
|
104
|
+
add_column :users, :coord_z, :float, default: 1
|
105
|
+
add_column :users, :frame, :string, default: 'square'
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
```
|
110
|
+
|
111
|
+
Then execute:
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
rake db:migrate
|
115
|
+
```
|
116
|
+
|
117
|
+
|
118
|
+
Open your model file and add these line:
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
class User < ActiveRecord::Base
|
122
|
+
include ImageCropper::Model
|
123
|
+
mount_uploader :file, ImageUploader
|
124
|
+
end
|
125
|
+
```
|
126
|
+
|
127
|
+
|
128
|
+
## Require Assets (Css/Js)
|
129
|
+
|
130
|
+
Include the following in your javascript manifest:
|
131
|
+
|
132
|
+
```javascript
|
133
|
+
//= require jquery
|
134
|
+
//= require image_cropper
|
135
|
+
```
|
136
|
+
|
137
|
+
And the following in your stylesheet manifest:
|
138
|
+
|
139
|
+
```css
|
140
|
+
*= require font-awesome
|
141
|
+
*= require image_cropper
|
142
|
+
```
|
143
|
+
(for the moment font-awesome is dependency and you need to require)
|
144
|
+
|
145
|
+
|
146
|
+
# Using
|
147
|
+
|
148
|
+
Now you're able to use it in forms:
|
149
|
+
|
150
|
+
```erb
|
151
|
+
<div style='max-width: 400px;'>
|
152
|
+
<%= f.image_cropper_field :file %>
|
153
|
+
</div>
|
154
|
+
```
|
155
|
+
|
156
|
+
If you need to save remote image, you need to set value in the hidden field and trigger 'change'
|
157
|
+
```js
|
158
|
+
$('.image-cropper-file-url').val('https://www.gettyimages.es/gi-resources/images/Embed/new/embed2.jpg').trigger('change');
|
159
|
+
```
|
160
|
+
|
161
|
+
|
162
|
+
remember put the property 'multipart' to form when you use image_cropper:
|
163
|
+
|
164
|
+
```erb
|
165
|
+
<%= form_with(model: user, local: true, html: {multipart: true}) do |form| %>
|
166
|
+
```
|
167
|
+
|
168
|
+
And add to strong parameters the fields
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
params.require(:user).permit(:name, :coord_x, :coord_y, :coord_w, :coord_h, :coord_z, :frame, :file, :file_cache, :remote_file_url)
|
172
|
+
```
|
173
|
+
**(Is very important the orden of these params, always leave the :file_cache, and :file to the end)**
|
174
|
+
|
175
|
+
|
176
|
+
|
177
|
+
And in a show view:
|
178
|
+
|
179
|
+
```erb
|
180
|
+
<div style='max-width: 400px;'>
|
181
|
+
<%= image_tag @user.file.thumb.url, class: 'image-cropper' %>
|
182
|
+
</div>
|
183
|
+
```
|
184
|
+
|
185
|
+
You can send some params
|
186
|
+
|
187
|
+
* aspect_ratio
|
188
|
+
* frame
|
189
|
+
* operation_object
|
190
|
+
* destroyable
|
191
|
+
* destroy_class
|
192
|
+
* destroy_confirm
|
193
|
+
|
194
|
+
| Params | Type | Default | Options | Description |
|
195
|
+
| ---------------- |:-------------:|:-------------------------------:|:--------------------------------------:|-----------------------------------------------------------------------------:|
|
196
|
+
| aspect_ratio | _(string)_ | **'4:3'** | any combination of **'number:number'** | 4:4 are perfect square, 4:3 rectangle horizontal, 3:4 rectangle vertical |
|
197
|
+
| frame | _(string)_ | **'square'** | **'square'** or **'circle'** | frame of image, if you want a circle o square frame |
|
198
|
+
| operation_object | _(object)_ | **self** | any **object** | maybe the object where mount the uploader it's not the one you want to erase |
|
199
|
+
| destroyable | _(boolean)_ | **false** | **true** or **false** | If you want a button to remove object with nested_attributes |
|
200
|
+
| destroy_class | _(string)_ | **'.image-cropper-container'** | a class or id of containter | Is the container that disapear when you click remove |
|
201
|
+
| destroy_confirm | _(string)_ | **'Are you sure?'** | any message you want | message to display to confirm to remove the image |
|
202
|
+
|
203
|
+
|
204
|
+
___
|
205
|
+
|
206
|
+
|
207
|
+
## Features to the future
|
208
|
+
* Make test
|
209
|
+
* Problem with gif when crop area, not resize - **SOLVED**
|
210
|
+
* Problem with jpg, jpeg, circular crop - **NOT ALOW, JPG AND JPEG CAN'T CIRCULAR CROP**
|
211
|
+
|
212
|
+
## Thanks!
|
213
|
+
|
214
|
+
RubyGems
|
215
|
+
|
216
|
+
Esta gema está publicada en [RubyGems](https://rubygems.org/gems/image_cropper) bajo el user juan.labattaglia@snappler.com
|
217
|
+
|
218
|
+
|
219
|
+
## Actualización
|
220
|
+
|
221
|
+
Una vez que hay una nueva versión, modificar `lib/image_cropper/version.rb`
|
222
|
+
|
223
|
+
Y luego ejecutar:
|
224
|
+
|
225
|
+
```shell
|
226
|
+
gem build image_cropper.gemspec
|
227
|
+
gem push image_cropper-[VERSION].gem
|
228
|
+
```
|
@@ -0,0 +1,688 @@
|
|
1
|
+
|
2
|
+
(function ( $ ) {
|
3
|
+
|
4
|
+
$.fn.image_cropper = function( options ) {
|
5
|
+
|
6
|
+
var settings = $.extend({
|
7
|
+
}, options );
|
8
|
+
|
9
|
+
//-------------------------- Definitions
|
10
|
+
//Containers
|
11
|
+
var image_cropper_container = $(this);
|
12
|
+
var image_cropper_crop_container = image_cropper_container.find('.image-cropper-crop-container');
|
13
|
+
|
14
|
+
//Components
|
15
|
+
var image_cropper_crop = image_cropper_container.find('.image-cropper-crop');
|
16
|
+
var image_cropper_file = image_cropper_container.find('.image-cropper-file');
|
17
|
+
var image_cropper_file_cache = image_cropper_container.find('.image-cropper-file-cache');
|
18
|
+
var image_cropper_file_url = image_cropper_container.find('.image-cropper-file-url');
|
19
|
+
|
20
|
+
//Coords
|
21
|
+
var coord_x = image_cropper_container.find('.coord-x');
|
22
|
+
var coord_y = image_cropper_container.find('.coord-y');
|
23
|
+
var coord_z = image_cropper_container.find('.coord-z');
|
24
|
+
var coord_w = image_cropper_container.find('.coord-w');
|
25
|
+
var coord_h = image_cropper_container.find('.coord-h');
|
26
|
+
|
27
|
+
//Buttons
|
28
|
+
var image_cropper_file_trigger = image_cropper_container.find('.image-cropper-file-trigger');
|
29
|
+
var coord_zoom_in = image_cropper_container.find('.image-cropper-zoom-in');
|
30
|
+
var coord_zoom_out = image_cropper_container.find('.image-cropper-zoom-out');
|
31
|
+
var image_cropper_remove = image_cropper_container.find('.image-cropper-remove');
|
32
|
+
|
33
|
+
//Aux vars
|
34
|
+
var coord_x_val, coord_y_val, coord_z_val, aspect_ratio, aspect_ratio_arr, w_val, h_val, coord_w_val, coord_h_val;
|
35
|
+
|
36
|
+
|
37
|
+
|
38
|
+
var image_cropper_initialize_vars = function(){
|
39
|
+
|
40
|
+
|
41
|
+
//Set image_cropper active
|
42
|
+
image_cropper_container.addClass('active');
|
43
|
+
|
44
|
+
//Get X,Y,W,H,Z
|
45
|
+
coord_x_val = parseFloat(coord_x.val());
|
46
|
+
if (isNaN(coord_x_val)){
|
47
|
+
coord_x_val = 0;
|
48
|
+
}
|
49
|
+
|
50
|
+
coord_y_val = parseFloat(coord_y.val());
|
51
|
+
if (isNaN(coord_y_val)){
|
52
|
+
coord_y_val = 0;
|
53
|
+
}
|
54
|
+
coord_z_val = parseFloat(coord_z.val());
|
55
|
+
if (isNaN(coord_z_val)){
|
56
|
+
coord_z_val = 1;
|
57
|
+
}
|
58
|
+
|
59
|
+
aspect_ratio = image_cropper_container.data('aspect-ratio');
|
60
|
+
|
61
|
+
if (aspect_ratio === undefined){
|
62
|
+
aspect_ratio = '4:3';
|
63
|
+
}
|
64
|
+
|
65
|
+
aspect_ratio_arr = aspect_ratio.split(':');
|
66
|
+
w_val = parseFloat(aspect_ratio_arr[0]);
|
67
|
+
if (isNaN(w_val)){
|
68
|
+
w_val = 4;
|
69
|
+
}
|
70
|
+
h_val = parseFloat(aspect_ratio_arr[1]);
|
71
|
+
if (isNaN(h_val)){
|
72
|
+
h_val = 3;
|
73
|
+
}
|
74
|
+
coord_w_val = w_val * 160;
|
75
|
+
coord_h_val = h_val * 160;
|
76
|
+
|
77
|
+
|
78
|
+
//Define Crop
|
79
|
+
image_cropper_crop.guillotine({
|
80
|
+
width: coord_w_val,
|
81
|
+
height: coord_h_val,
|
82
|
+
init: { scale: coord_z_val, x: coord_x_val, y: coord_y_val },
|
83
|
+
onChange: function(data, action){
|
84
|
+
coord_x.val(data.x);
|
85
|
+
coord_y.val(data.y);
|
86
|
+
coord_w.val(data.w);
|
87
|
+
coord_h.val(data.h);
|
88
|
+
coord_z.val(data.scale);
|
89
|
+
}
|
90
|
+
});
|
91
|
+
|
92
|
+
|
93
|
+
if ((settings['enabled'] !== undefined) && (settings['enabled'] === false)){
|
94
|
+
image_cropper_crop.guillotine('disable');
|
95
|
+
image_cropper_crop_container.addClass('disable');
|
96
|
+
}
|
97
|
+
|
98
|
+
//Initialize
|
99
|
+
data = image_cropper_crop.guillotine('getData');
|
100
|
+
coord_x.val(data.x);
|
101
|
+
coord_y.val(data.y);
|
102
|
+
coord_w.val(data.w);
|
103
|
+
coord_h.val(data.h);
|
104
|
+
coord_z.val(data.scale);
|
105
|
+
}
|
106
|
+
|
107
|
+
var image_cropper_initialize_events = function(){
|
108
|
+
|
109
|
+
|
110
|
+
//remove image
|
111
|
+
image_cropper_remove.off('click');
|
112
|
+
image_cropper_remove.on('click', function() {
|
113
|
+
if (confirm($(this).data('destroy-confirm'))) {
|
114
|
+
$(this).prev('input[type=hidden]').val('1');
|
115
|
+
|
116
|
+
$(this).closest(settings['destroy_class']).fadeOut(function(){
|
117
|
+
$(this).hide();
|
118
|
+
});
|
119
|
+
}
|
120
|
+
});
|
121
|
+
|
122
|
+
|
123
|
+
//Upload image
|
124
|
+
image_cropper_file_trigger.off('click');
|
125
|
+
image_cropper_file_trigger.on('click', function() {
|
126
|
+
image_cropper_file.trigger('click');
|
127
|
+
});
|
128
|
+
|
129
|
+
//When change input file show img
|
130
|
+
image_cropper_file.off('change');
|
131
|
+
image_cropper_file.on('change', function() {
|
132
|
+
if (this.files && this.files[0]) {
|
133
|
+
//image_cropper_file_binary.val('');
|
134
|
+
var reader = new FileReader;
|
135
|
+
reader.onload = function(e) {
|
136
|
+
set_image(e.target.result);
|
137
|
+
image_cropper_file_url.val('');
|
138
|
+
image_cropper_file_cache.val('');
|
139
|
+
};
|
140
|
+
reader.readAsDataURL(this.files[0]);
|
141
|
+
}
|
142
|
+
});
|
143
|
+
|
144
|
+
|
145
|
+
//When change input url show img
|
146
|
+
image_cropper_file_url.off('change');
|
147
|
+
image_cropper_file_url.on('change', function() {
|
148
|
+
if ($(this).val() != '') {
|
149
|
+
set_image($(this).val());
|
150
|
+
image_cropper_file.val('');
|
151
|
+
image_cropper_file_cache.val('');
|
152
|
+
}
|
153
|
+
});
|
154
|
+
|
155
|
+
|
156
|
+
//Zoom in
|
157
|
+
coord_zoom_in.off('click');
|
158
|
+
coord_zoom_in.on('click', function() {
|
159
|
+
image_cropper_crop.guillotine('zoomIn');
|
160
|
+
});
|
161
|
+
|
162
|
+
//Zoom out
|
163
|
+
coord_zoom_out.off('click');
|
164
|
+
coord_zoom_out.on('click', function() {
|
165
|
+
image_cropper_crop.guillotine('zoomOut');
|
166
|
+
});
|
167
|
+
|
168
|
+
//Scrolling Zoom
|
169
|
+
image_cropper_crop.off('DOMMouseScroll mousewheel');
|
170
|
+
image_cropper_crop.on('DOMMouseScroll mousewheel', function (event) {
|
171
|
+
var guillotine = image_cropper_crop.guillotine('instance');
|
172
|
+
if(guillotine !== undefined){
|
173
|
+
if( event.originalEvent.detail > 0 || event.originalEvent.wheelDelta < 0 ) { //alternative options for wheelData: wheelDeltaX & wheelDeltaY
|
174
|
+
image_cropper_crop.guillotine('zoomOut');
|
175
|
+
} else {
|
176
|
+
image_cropper_crop.guillotine('zoomIn');
|
177
|
+
}
|
178
|
+
}
|
179
|
+
return false;
|
180
|
+
});
|
181
|
+
|
182
|
+
|
183
|
+
}
|
184
|
+
|
185
|
+
|
186
|
+
var set_image = function(data_uri){
|
187
|
+
var i;
|
188
|
+
image_cropper_crop.guillotine('remove');
|
189
|
+
image_cropper_crop.attr('src', data_uri);
|
190
|
+
coord_x.val('');
|
191
|
+
coord_y.val('');
|
192
|
+
coord_w.val('');
|
193
|
+
coord_h.val('');
|
194
|
+
coord_z.val('');
|
195
|
+
settings['enabled'] = true;
|
196
|
+
image_cropper_crop.guillotine('enable');
|
197
|
+
image_cropper_crop_container.removeClass('disable');
|
198
|
+
|
199
|
+
i = new Image();
|
200
|
+
i.src = data_uri;
|
201
|
+
i.onload = function() {
|
202
|
+
//alert(i.naturalWidth + ' x ' + i.naturalHeight);
|
203
|
+
image_cropper_initialize_vars();
|
204
|
+
};
|
205
|
+
}
|
206
|
+
|
207
|
+
|
208
|
+
|
209
|
+
|
210
|
+
//-------------------------- Initialize
|
211
|
+
//alert(image_cropper_container.hasClass('active'));
|
212
|
+
if(!image_cropper_container.hasClass('active')){
|
213
|
+
var i;
|
214
|
+
i = new Image();
|
215
|
+
i.src = image_cropper_crop.attr('src');
|
216
|
+
i.onload = function(e) {
|
217
|
+
image_cropper_initialize_events();
|
218
|
+
image_cropper_initialize_vars();
|
219
|
+
};
|
220
|
+
}
|
221
|
+
|
222
|
+
|
223
|
+
};
|
224
|
+
|
225
|
+
}( jQuery ));
|
226
|
+
|
227
|
+
|
228
|
+
|
229
|
+
|
230
|
+
|
231
|
+
|
232
|
+
|
233
|
+
|
234
|
+
|
235
|
+
|
236
|
+
|
237
|
+
|
238
|
+
|
239
|
+
|
240
|
+
|
241
|
+
|
242
|
+
|
243
|
+
|
244
|
+
|
245
|
+
|
246
|
+
|
247
|
+
|
248
|
+
|
249
|
+
|
250
|
+
|
251
|
+
// Generated by CoffeeScript 1.8.0
|
252
|
+
|
253
|
+
/*
|
254
|
+
* jQuery Guillotine Plugin v1.3.1
|
255
|
+
* http://matiasgagliano.github.com/guillotine/
|
256
|
+
*
|
257
|
+
* Copyright 2014, Matías Gagliano.
|
258
|
+
* Dual licensed under the MIT or GPLv3 licenses.
|
259
|
+
* http://opensource.org/licenses/MIT
|
260
|
+
* http://opensource.org/licenses/GPL-3.0
|
261
|
+
*
|
262
|
+
*/
|
263
|
+
|
264
|
+
(function() {
|
265
|
+
"use strict";
|
266
|
+
var $, Guillotine, canTransform, defaults, events, getPointerPosition, hardwareAccelerate, isTouch, pluginName, scope, touchRegExp, validEvent, whitelist,
|
267
|
+
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
|
268
|
+
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
|
269
|
+
|
270
|
+
$ = jQuery;
|
271
|
+
|
272
|
+
pluginName = 'guillotine';
|
273
|
+
|
274
|
+
scope = 'guillotine';
|
275
|
+
|
276
|
+
events = {
|
277
|
+
start: "touchstart." + scope + " mousedown." + scope,
|
278
|
+
move: "touchmove." + scope + " mousemove." + scope,
|
279
|
+
stop: "touchend." + scope + " mouseup." + scope
|
280
|
+
};
|
281
|
+
|
282
|
+
defaults = {
|
283
|
+
width: 400,
|
284
|
+
height: 300,
|
285
|
+
zoomStep: 0.1,
|
286
|
+
init: null,
|
287
|
+
eventOnChange: null,
|
288
|
+
onChange: null
|
289
|
+
};
|
290
|
+
|
291
|
+
touchRegExp = /touch/i;
|
292
|
+
|
293
|
+
isTouch = function(e) {
|
294
|
+
return touchRegExp.test(e.type);
|
295
|
+
};
|
296
|
+
|
297
|
+
validEvent = function(e) {
|
298
|
+
if (isTouch(e)) {
|
299
|
+
return e.originalEvent.changedTouches.length === 1;
|
300
|
+
} else {
|
301
|
+
return e.which === 1;
|
302
|
+
}
|
303
|
+
};
|
304
|
+
|
305
|
+
getPointerPosition = function(e) {
|
306
|
+
if (isTouch(e)) {
|
307
|
+
e = e.originalEvent.touches[0];
|
308
|
+
}
|
309
|
+
return {
|
310
|
+
x: e.pageX,
|
311
|
+
y: e.pageY
|
312
|
+
};
|
313
|
+
};
|
314
|
+
|
315
|
+
canTransform = function() {
|
316
|
+
var hasTransform, helper, prefix, prefixes, prop, test, tests, value, _i, _len;
|
317
|
+
hasTransform = false;
|
318
|
+
prefixes = ['webkit', 'Moz', 'O', 'ms', 'Khtml'];
|
319
|
+
tests = {
|
320
|
+
transform: 'transform'
|
321
|
+
};
|
322
|
+
for (_i = 0, _len = prefixes.length; _i < _len; _i++) {
|
323
|
+
prefix = prefixes[_i];
|
324
|
+
tests[prefix + 'Transform'] = '-' + prefix.toLowerCase() + '-transform';
|
325
|
+
}
|
326
|
+
helper = document.createElement('img');
|
327
|
+
document.body.insertBefore(helper, null);
|
328
|
+
for (test in tests) {
|
329
|
+
prop = tests[test];
|
330
|
+
if (helper.style[test] === void 0) {
|
331
|
+
continue;
|
332
|
+
}
|
333
|
+
helper.style[test] = 'rotate(90deg)';
|
334
|
+
value = window.getComputedStyle(helper).getPropertyValue(prop);
|
335
|
+
if ((value != null) && value.length && value !== 'none') {
|
336
|
+
hasTransform = true;
|
337
|
+
break;
|
338
|
+
}
|
339
|
+
}
|
340
|
+
document.body.removeChild(helper);
|
341
|
+
canTransform = hasTransform ? (function() {
|
342
|
+
return true;
|
343
|
+
}) : (function() {
|
344
|
+
return false;
|
345
|
+
});
|
346
|
+
return canTransform();
|
347
|
+
};
|
348
|
+
|
349
|
+
hardwareAccelerate = function(el) {
|
350
|
+
return $(el).css({
|
351
|
+
'-webkit-perspective': 1000,
|
352
|
+
'perspective': 1000,
|
353
|
+
'-webkit-backface-visibility': 'hidden',
|
354
|
+
'backface-visibility': 'hidden'
|
355
|
+
});
|
356
|
+
};
|
357
|
+
|
358
|
+
Guillotine = (function() {
|
359
|
+
function Guillotine(element, options) {
|
360
|
+
this._drag = __bind(this._drag, this);
|
361
|
+
this._unbind = __bind(this._unbind, this);
|
362
|
+
this._start = __bind(this._start, this);
|
363
|
+
this.op = $.extend(true, {}, defaults, options, $(element).data(pluginName));
|
364
|
+
this.enabled = true;
|
365
|
+
this.zoomInFactor = 1 + this.op.zoomStep;
|
366
|
+
this.zoomOutFactor = 1 / this.zoomInFactor;
|
367
|
+
this.glltRatio = this.op.height / this.op.width;
|
368
|
+
this.width = this.height = this.left = this.top = this.angle = 0;
|
369
|
+
this.data = {
|
370
|
+
scale: 1,
|
371
|
+
angle: 0,
|
372
|
+
x: 0,
|
373
|
+
y: 0,
|
374
|
+
w: this.op.width,
|
375
|
+
h: this.op.height
|
376
|
+
};
|
377
|
+
this._wrap(element);
|
378
|
+
if (this.op.init != null) {
|
379
|
+
this._init();
|
380
|
+
}
|
381
|
+
if (this.width < 1 || this.height < 1) {
|
382
|
+
this._fit() && this._center();
|
383
|
+
}
|
384
|
+
hardwareAccelerate(this.$el);
|
385
|
+
this.$el.on(events.start, this._start);
|
386
|
+
}
|
387
|
+
|
388
|
+
Guillotine.prototype._wrap = function(element) {
|
389
|
+
var canvas, el, guillotine, height, paddingTop, width;
|
390
|
+
el = $(element);
|
391
|
+
if (element.tagName === 'IMG') {
|
392
|
+
if (element.naturalWidth) {
|
393
|
+
width = element.naturalWidth;
|
394
|
+
height = element.naturalHeight;
|
395
|
+
} else {
|
396
|
+
el.addClass('guillotine-sample');
|
397
|
+
width = el.width();
|
398
|
+
height = el.height();
|
399
|
+
el.removeClass('guillotine-sample');
|
400
|
+
}
|
401
|
+
} else {
|
402
|
+
width = el.width();
|
403
|
+
height = el.height();
|
404
|
+
}
|
405
|
+
this.width = width / this.op.width;
|
406
|
+
this.height = height / this.op.height;
|
407
|
+
canvas = $('<div>').addClass('guillotine-canvas');
|
408
|
+
canvas.css({
|
409
|
+
width: this.width * 100 + '%',
|
410
|
+
height: this.height * 100 + '%',
|
411
|
+
top: 0,
|
412
|
+
left: 0
|
413
|
+
});
|
414
|
+
canvas = el.wrap(canvas).parent();
|
415
|
+
paddingTop = this.op.height / this.op.width * 100 + '%';
|
416
|
+
guillotine = $('<div>').addClass('guillotine-window');
|
417
|
+
guillotine.css({
|
418
|
+
width: '100%',
|
419
|
+
height: 'auto',
|
420
|
+
'padding-top': paddingTop
|
421
|
+
});
|
422
|
+
guillotine = canvas.wrap(guillotine).parent();
|
423
|
+
this.$el = el;
|
424
|
+
this.el = el[0];
|
425
|
+
this.$canvas = canvas;
|
426
|
+
this.canvas = canvas[0];
|
427
|
+
this.$gllt = guillotine;
|
428
|
+
this.gllt = guillotine[0];
|
429
|
+
this.$document = $(element.ownerDocument);
|
430
|
+
return this.$body = $('body', this.$document);
|
431
|
+
};
|
432
|
+
|
433
|
+
Guillotine.prototype._unwrap = function() {
|
434
|
+
this.$el.removeAttr('style');
|
435
|
+
this.$el.insertBefore(this.gllt);
|
436
|
+
return this.$gllt.remove();
|
437
|
+
};
|
438
|
+
|
439
|
+
Guillotine.prototype._init = function() {
|
440
|
+
var angle, o, scale;
|
441
|
+
o = this.op.init;
|
442
|
+
if ((scale = parseFloat(o.scale))) {
|
443
|
+
this._zoom(scale);
|
444
|
+
}
|
445
|
+
if ((angle = parseInt(o.angle))) {
|
446
|
+
this._rotate(angle);
|
447
|
+
}
|
448
|
+
return this._offset(parseInt(o.x) / this.op.width || 0, parseInt(o.y) / this.op.height || 0);
|
449
|
+
};
|
450
|
+
|
451
|
+
Guillotine.prototype._start = function(e) {
|
452
|
+
if (!(this.enabled && validEvent(e))) {
|
453
|
+
return;
|
454
|
+
}
|
455
|
+
e.preventDefault();
|
456
|
+
e.stopImmediatePropagation();
|
457
|
+
this.p = getPointerPosition(e);
|
458
|
+
return this._bind();
|
459
|
+
};
|
460
|
+
|
461
|
+
Guillotine.prototype._bind = function() {
|
462
|
+
this.$body.addClass('guillotine-dragging');
|
463
|
+
this.$document.on(events.move, this._drag);
|
464
|
+
return this.$document.on(events.stop, this._unbind);
|
465
|
+
};
|
466
|
+
|
467
|
+
Guillotine.prototype._unbind = function(e) {
|
468
|
+
this.$body.removeClass('guillotine-dragging');
|
469
|
+
this.$document.off(events.move, this._drag);
|
470
|
+
this.$document.off(events.stop, this._unbind);
|
471
|
+
if (e != null) {
|
472
|
+
return this._trigger('drag');
|
473
|
+
}
|
474
|
+
};
|
475
|
+
|
476
|
+
Guillotine.prototype._trigger = function(action) {
|
477
|
+
if (this.op.eventOnChange != null) {
|
478
|
+
this.$el.trigger(this.op.eventOnChange, [this.data, action]);
|
479
|
+
}
|
480
|
+
if (typeof this.op.onChange === 'function') {
|
481
|
+
return this.op.onChange.call(this.el, this.data, action);
|
482
|
+
}
|
483
|
+
};
|
484
|
+
|
485
|
+
Guillotine.prototype._drag = function(e) {
|
486
|
+
var dx, dy, left, p, top;
|
487
|
+
e.preventDefault();
|
488
|
+
e.stopImmediatePropagation();
|
489
|
+
p = getPointerPosition(e);
|
490
|
+
dx = p.x - this.p.x;
|
491
|
+
dy = p.y - this.p.y;
|
492
|
+
this.p = p;
|
493
|
+
left = dx === 0 ? null : this.left - dx / this.gllt.clientWidth;
|
494
|
+
top = dy === 0 ? null : this.top - dy / this.gllt.clientHeight;
|
495
|
+
return this._offset(left, top);
|
496
|
+
};
|
497
|
+
|
498
|
+
Guillotine.prototype._offset = function(left, top) {
|
499
|
+
if (left || left === 0) {
|
500
|
+
if (left < 0) {
|
501
|
+
left = 0;
|
502
|
+
}
|
503
|
+
if (left > this.width - 1) {
|
504
|
+
left = this.width - 1;
|
505
|
+
}
|
506
|
+
this.canvas.style.left = (-left * 100).toFixed(2) + '%';
|
507
|
+
this.left = left;
|
508
|
+
this.data.x = Math.round(left * this.op.width);
|
509
|
+
}
|
510
|
+
if (top || top === 0) {
|
511
|
+
if (top < 0) {
|
512
|
+
top = 0;
|
513
|
+
}
|
514
|
+
if (top > this.height - 1) {
|
515
|
+
top = this.height - 1;
|
516
|
+
}
|
517
|
+
this.canvas.style.top = (-top * 100).toFixed(2) + '%';
|
518
|
+
this.top = top;
|
519
|
+
return this.data.y = Math.round(top * this.op.height);
|
520
|
+
}
|
521
|
+
};
|
522
|
+
|
523
|
+
Guillotine.prototype._zoom = function(factor) {
|
524
|
+
var h, left, top, w;
|
525
|
+
if (factor <= 0 || factor === 1) {
|
526
|
+
return;
|
527
|
+
}
|
528
|
+
w = this.width;
|
529
|
+
h = this.height;
|
530
|
+
if (w * factor > 1 && h * factor > 1) {
|
531
|
+
this.width *= factor;
|
532
|
+
this.height *= factor;
|
533
|
+
this.canvas.style.width = (this.width * 100).toFixed(2) + '%';
|
534
|
+
this.canvas.style.height = (this.height * 100).toFixed(2) + '%';
|
535
|
+
this.data.scale *= factor;
|
536
|
+
} else {
|
537
|
+
this._fit();
|
538
|
+
factor = this.width / w;
|
539
|
+
}
|
540
|
+
left = (this.left + 0.5) * factor - 0.5;
|
541
|
+
top = (this.top + 0.5) * factor - 0.5;
|
542
|
+
return this._offset(left, top);
|
543
|
+
};
|
544
|
+
|
545
|
+
Guillotine.prototype._fit = function() {
|
546
|
+
var prevWidth, relativeRatio;
|
547
|
+
prevWidth = this.width;
|
548
|
+
relativeRatio = this.height / this.width;
|
549
|
+
if (relativeRatio > 1) {
|
550
|
+
this.width = 1;
|
551
|
+
this.height = relativeRatio;
|
552
|
+
} else {
|
553
|
+
this.width = 1 / relativeRatio;
|
554
|
+
this.height = 1;
|
555
|
+
}
|
556
|
+
this.canvas.style.width = (this.width * 100).toFixed(2) + '%';
|
557
|
+
this.canvas.style.height = (this.height * 100).toFixed(2) + '%';
|
558
|
+
return this.data.scale *= this.width / prevWidth;
|
559
|
+
};
|
560
|
+
|
561
|
+
Guillotine.prototype._center = function() {
|
562
|
+
return this._offset((this.width - 1) / 2, (this.height - 1) / 2);
|
563
|
+
};
|
564
|
+
|
565
|
+
Guillotine.prototype._rotate = function(angle) {
|
566
|
+
var canvasRatio, glltRatio, h, w, _ref, _ref1, _ref2;
|
567
|
+
if (!canTransform()) {
|
568
|
+
return;
|
569
|
+
}
|
570
|
+
if (!(angle !== 0 && angle % 90 === 0)) {
|
571
|
+
return;
|
572
|
+
}
|
573
|
+
this.angle = (this.angle + angle) % 360;
|
574
|
+
if (this.angle < 0) {
|
575
|
+
this.angle = 360 + this.angle;
|
576
|
+
}
|
577
|
+
if (angle % 180 !== 0) {
|
578
|
+
glltRatio = this.op.height / this.op.width;
|
579
|
+
_ref = [this.height * glltRatio, this.width / glltRatio], this.width = _ref[0], this.height = _ref[1];
|
580
|
+
if (this.width >= 1 && this.height >= 1) {
|
581
|
+
this.canvas.style.width = this.width * 100 + '%';
|
582
|
+
this.canvas.style.height = this.height * 100 + '%';
|
583
|
+
} else {
|
584
|
+
this._fit();
|
585
|
+
}
|
586
|
+
}
|
587
|
+
_ref1 = [1, 1], w = _ref1[0], h = _ref1[1];
|
588
|
+
if (this.angle % 180 !== 0) {
|
589
|
+
canvasRatio = this.height / this.width * glltRatio;
|
590
|
+
_ref2 = [canvasRatio, 1 / canvasRatio], w = _ref2[0], h = _ref2[1];
|
591
|
+
}
|
592
|
+
this.el.style.width = w * 100 + '%';
|
593
|
+
this.el.style.height = h * 100 + '%';
|
594
|
+
this.el.style.left = (1 - w) / 2 * 100 + '%';
|
595
|
+
this.el.style.top = (1 - h) / 2 * 100 + '%';
|
596
|
+
this.$el.css({
|
597
|
+
transform: "rotate(" + this.angle + "deg)"
|
598
|
+
});
|
599
|
+
this._center();
|
600
|
+
return this.data.angle = this.angle;
|
601
|
+
};
|
602
|
+
|
603
|
+
Guillotine.prototype.rotateLeft = function() {
|
604
|
+
return this.enabled && (this._rotate(-90), this._trigger('rotateLeft'));
|
605
|
+
};
|
606
|
+
|
607
|
+
Guillotine.prototype.rotateRight = function() {
|
608
|
+
return this.enabled && (this._rotate(90), this._trigger('rotateRight'));
|
609
|
+
};
|
610
|
+
|
611
|
+
Guillotine.prototype.center = function() {
|
612
|
+
return this.enabled && (this._center(), this._trigger('center'));
|
613
|
+
};
|
614
|
+
|
615
|
+
Guillotine.prototype.fit = function() {
|
616
|
+
return this.enabled && (this._fit(), this._center(), this._trigger('fit'));
|
617
|
+
};
|
618
|
+
|
619
|
+
Guillotine.prototype.zoomIn = function() {
|
620
|
+
return this.enabled && (this._zoom(this.zoomInFactor), this._trigger('zoomIn'));
|
621
|
+
};
|
622
|
+
|
623
|
+
Guillotine.prototype.zoomOut = function() {
|
624
|
+
return this.enabled && (this._zoom(this.zoomOutFactor), this._trigger('zoomOut'));
|
625
|
+
};
|
626
|
+
|
627
|
+
Guillotine.prototype.getData = function() {
|
628
|
+
return this.data;
|
629
|
+
};
|
630
|
+
|
631
|
+
Guillotine.prototype.enable = function() {
|
632
|
+
return this.enabled = true;
|
633
|
+
};
|
634
|
+
|
635
|
+
Guillotine.prototype.disable = function() {
|
636
|
+
return this.enabled = false;
|
637
|
+
};
|
638
|
+
|
639
|
+
Guillotine.prototype.remove = function() {
|
640
|
+
this._unbind();
|
641
|
+
this._unwrap();
|
642
|
+
this.disable();
|
643
|
+
this.$el.off(events.start, this._start);
|
644
|
+
return this.$el.removeData(pluginName + 'Instance');
|
645
|
+
};
|
646
|
+
|
647
|
+
return Guillotine;
|
648
|
+
|
649
|
+
})();
|
650
|
+
|
651
|
+
whitelist = ['rotateLeft', 'rotateRight', 'center', 'fit', 'zoomIn', 'zoomOut', 'instance', 'getData', 'enable', 'disable', 'remove'];
|
652
|
+
|
653
|
+
$.fn[pluginName] = function(options) {
|
654
|
+
if (typeof options !== 'string') {
|
655
|
+
return this.each(function() {
|
656
|
+
var guillotine;
|
657
|
+
if (!$.data(this, pluginName + 'Instance')) {
|
658
|
+
guillotine = new Guillotine(this, options);
|
659
|
+
return $.data(this, pluginName + 'Instance', guillotine);
|
660
|
+
}
|
661
|
+
});
|
662
|
+
} else if (__indexOf.call(whitelist, options) >= 0) {
|
663
|
+
if (options === 'instance') {
|
664
|
+
return $.data(this[0], pluginName + 'Instance');
|
665
|
+
}
|
666
|
+
if (options === 'getData') {
|
667
|
+
return $.data(this[0], pluginName + 'Instance')[options]();
|
668
|
+
}
|
669
|
+
return this.each(function() {
|
670
|
+
var guillotine;
|
671
|
+
guillotine = $.data(this, pluginName + 'Instance');
|
672
|
+
if (guillotine) {
|
673
|
+
return guillotine[options]();
|
674
|
+
}
|
675
|
+
});
|
676
|
+
}
|
677
|
+
};
|
678
|
+
|
679
|
+
}).call(this);
|
680
|
+
|
681
|
+
|
682
|
+
|
683
|
+
|
684
|
+
|
685
|
+
|
686
|
+
|
687
|
+
|
688
|
+
|