carrierwave-crop 0.0.10.alpha

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 55ff6ba0e684d207e2b8cc074245062fbea34a12
4
+ data.tar.gz: 3f78cf5b312a14017b60cd348feb1eb68e3c1286
5
+ SHA512:
6
+ metadata.gz: 86275835d5512a8326a708ba9cc8327de4260b291572f22655dd4eebc2e02b6e5aa9d170f5ed8e99318b341b31ee03a3aa01408af809c58c4a7613243a99861d
7
+ data.tar.gz: 1a200f12c4be8c3da9c3c58d69274865f29f6457b21a44267bf71435dbb08a7ff22c998b285f4ea07becc64ba9ad9ac8019a1108d9ab1a8d8cc329d38b2da9a9
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .DS_Store
19
+ .keep
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ rvm:
2
+ - 1.9.3
3
+ - 2.0.0
4
+ - 2.1.1
5
+ - ruby-head
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in carrierwave-crop.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 kirtithorat
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # Carrierwave-Crop
2
+
3
+ [![Build Status](https://travis-ci.org/kirtithorat/carrierwave-crop.svg?branch=master)](https://travis-ci.org/kirtithorat/carrierwave-crop)
4
+
5
+ Carrierwave extension to crop uploaded images using Jcrop plugin with preview.
6
+
7
+ ## Installation
8
+
9
+ Install the latest stable release:
10
+
11
+ $[sudo] gem install carrierwave
12
+
13
+ In Rails, add it to your Gemfile:
14
+
15
+ gem 'carrierwave-crop'
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Finally, restart the server to apply the changes.
22
+
23
+ ## Getting Started
24
+
25
+ Add the required files in assets
26
+
27
+ In `application.js`
28
+
29
+ //= require jquery
30
+ //= require jquery.jcrop
31
+
32
+ In `application.css`
33
+
34
+ *= require jquery.jcrop
35
+
36
+ Generate a coffeescript for cropping:
37
+
38
+ rails generate cropper user avatar
39
+
40
+ this should give you a file in:
41
+
42
+ app/assets/javascripts/users.js.coffee
43
+
44
+ ## Usage
45
+
46
+ Open your model file and add the cropper:
47
+
48
+ class User < ActiveRecord::Base
49
+ mount_uploader :avatar, AvatarUploader
50
+ crop_uploaded :avatar ## Add this
51
+ end
52
+
53
+ Render a view after creating/updating a user, add a `crop` action in your `controller.` For example:
54
+
55
+ def create
56
+ @user = User.new(user_params)
57
+ respond_to do |format|
58
+ if @user.save
59
+ format.html {
60
+ if params[:user][:avatar].present?
61
+ render :crop ## Render the view for cropping
62
+ else
63
+ redirect_to @user, notice: 'User was successfully created.'
64
+ end
65
+ }
66
+ format.json { render action: 'show', status: :created, location: @user }
67
+ else
68
+ format.html { render action: 'new' }
69
+ format.json { render json: @user.errors, status: :unprocessable_entity }
70
+ end
71
+ end
72
+ end
73
+
74
+ For `Rails 4.x`, whitelist the cropping attributes - `fieldname_crop_x`, `fieldname_crop_y`, `fieldname_crop_w`, `fieldname_crop_h`.
75
+
76
+ For example:
77
+
78
+ def user_params
79
+ params.require(:user).permit(:avatar_crop_x, :avatar_crop_y, :avatar_crop_w, :avatar_crop_h, ....)
80
+ end
81
+
82
+
83
+ In the view, say `crop.html.erb`:
84
+
85
+ <%= form_for @user do |f| %>
86
+ <%= f.cropbox :avatar %>
87
+ <%= f.cropped_preview :avatar %>
88
+ <%= f.submit 'Crop' %>
89
+ <% end %>
90
+
91
+ In the carrierwave uploader, say `AvatarUploader`:
92
+
93
+ Call process on the version you would like to be cropped:
94
+
95
+ ## If ONLY "thumb" version is to be cropped
96
+ version :jumbo do
97
+ resize_to_limit(600,600)
98
+ end
99
+
100
+ version :thumb do
101
+ process crop: :avatar ## Add this
102
+ resize_to_limit(100,100)
103
+ end
104
+
105
+ ### Credits and resources
106
+ * [Carrierwave](https://github.com/carrierwaveuploader/carrierwave)
107
+ * [Deep Liquid's JCrop](http://deepliquid.com/content/Jcrop.html)
108
+ * And Ryan Bates [Railscast#182](http://railscasts.com/episodes/182-cropping-images/)
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'carrierwave/crop/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "carrierwave-crop"
8
+ spec.version = Carrierwave::Crop::VERSION
9
+ spec.authors = ["kirtithorat"]
10
+ spec.email = ["kirti.brenz@gmail.com"]
11
+ spec.summary = %q{Carrierwave extension for cropping images with preview.}
12
+ spec.description = %q{Carrierwave extension to crop uploaded images using Jcrop plugin.}
13
+ spec.homepage = "https://github.com/kirtithorat/carrierwave-crop"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split("\n")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "rails", ">= 3.1"
22
+ spec.add_dependency "jquery-rails"
23
+ spec.add_dependency "carrierwave", [">= 0.8.0", "< 0.11.0"]
24
+
25
+ spec.add_development_dependency "bundler", "~> 1.5"
26
+ spec.add_development_dependency "rake"
27
+ spec.add_development_dependency "rspec"
28
+ end
@@ -0,0 +1,8 @@
1
+ require "carrierwave"
2
+ require "carrierwave/crop/version"
3
+ require "carrierwave/crop/model_additions"
4
+ if defined? Rails
5
+ require 'carrierwave/crop/engine'
6
+ require "carrierwave/crop/railtie"
7
+ require 'carrierwave/crop/helpers'
8
+ end
@@ -0,0 +1,6 @@
1
+ module Carrierwave
2
+ module Crop
3
+ class Engine < ::Rails::Engine
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,46 @@
1
+ module Carrierwave
2
+ module Crop
3
+ module Helpers
4
+
5
+ def cropped_preview(attachment, opts = {})
6
+ attachment = attachment.to_sym
7
+ width = opts[:width] || 100
8
+ height = (width / 1).round
9
+
10
+ if(self.object.send(attachment).class.ancestors.include? CarrierWave::Uploader::Base )
11
+ wrapper_options = {
12
+ :id => "#{attachment}_crop_preview_wrapper",
13
+ :style => "width:#{width}px; height:#{height}px; overflow:hidden"
14
+ }
15
+
16
+ preview_image = @template.image_tag(self.object.send(attachment).url, :id => "#{attachment}_crop_preview")
17
+
18
+ @template.content_tag(:div, preview_image, wrapper_options)
19
+ end
20
+ end
21
+
22
+
23
+ def cropbox(attachment, opts={})
24
+ attachment = attachment.to_sym
25
+
26
+ if(self.object.send(attachment).class.ancestors.include? CarrierWave::Uploader::Base )
27
+ box = self.hidden_field(:"#{attachment}_crop_x", id: "#{self.object.class.name.downcase}_#{attachment}_crop_x")
28
+ [:crop_y, :crop_w, :crop_h].each do |attribute|
29
+ box << self.hidden_field(:"#{attachment}_#{attribute}", id: "#{self.object.class.name.downcase}_#{attachment}_#{attribute}")
30
+ end
31
+
32
+ crop_image = @template.image_tag(self.object.send(attachment).url, :id => "#{self.object.class.name.downcase}_#{attachment}_cropbox")
33
+
34
+ box << @template.content_tag(:div, crop_image)
35
+
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ if defined? ActionView::Helpers::FormBuilder
43
+ ActionView::Helpers::FormBuilder.class_eval do
44
+ include Carrierwave::Crop::Helpers
45
+ end
46
+ end
@@ -0,0 +1,67 @@
1
+ module Carrierwave
2
+ module Crop
3
+ module ModelAdditions
4
+
5
+ module ClassMethods
6
+
7
+ ## Adding dynamic attribute accessors
8
+ def crop_uploaded(attachment)
9
+
10
+ [:crop_x, :crop_y, :crop_w, :crop_h].each do |a|
11
+ attr_accessor :"#{attachment}_#{a}"
12
+ end
13
+ after_update :"recreate_#{attachment}_versions"
14
+ end
15
+ end
16
+
17
+ module InstanceMethods
18
+
19
+ def cropping?(attachment)
20
+ !self.send(:"#{attachment}_crop_x").blank? &&
21
+ !self.send(:"#{attachment}_crop_y").blank? &&
22
+ !self.send(:"#{attachment}_crop_w").blank? &&
23
+ !self.send(:"#{attachment}_crop_h").blank?
24
+ end
25
+
26
+ def method_missing(method, *args)
27
+ if method.to_s =~ /recreate_(\S{1,})_versions/
28
+ crop_image(method.to_s.scan(/recreate_(\S{1,})_versions/).flatten.first.to_sym)
29
+ else
30
+ super
31
+ end
32
+ end
33
+
34
+ def crop_image(attachment)
35
+ if cropping?(attachment)
36
+ attachment_instance = send(attachment)
37
+ attachment_instance.recreate_versions!
38
+ end
39
+ end
40
+
41
+ end
42
+ end
43
+
44
+ module Uploader
45
+
46
+ def crop(attachment)
47
+ if model.cropping?(attachment)
48
+ resize_to_limit(600, 600)
49
+ manipulate! do |img|
50
+ x = model.send("#{attachment}_crop_x").to_i
51
+ y = model.send("#{attachment}_crop_y").to_i
52
+ w = model.send("#{attachment}_crop_w").to_i
53
+ h = model.send("#{attachment}_crop_h").to_i
54
+ img.crop!(x, y, w, h)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+
63
+ if defined? CarrierWave::Uploader::Base
64
+ CarrierWave::Uploader::Base.class_eval do
65
+ include Carrierwave::Crop::Uploader
66
+ end
67
+ end
@@ -0,0 +1,12 @@
1
+ module Carrierwave
2
+ module Crop
3
+ class Railtie < Rails::Railtie
4
+ initializer 'carrierwave.crop' do
5
+ ActiveSupport.on_load :active_record do
6
+ extend ModelAdditions::ClassMethods
7
+ include ModelAdditions::InstanceMethods
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,5 @@
1
+ module Carrierwave
2
+ module Crop
3
+ VERSION = "0.0.10.alpha"
4
+ end
5
+ end
@@ -0,0 +1,12 @@
1
+ class CropperGenerator < Rails::Generators::NamedBase
2
+ source_root File.expand_path("../templates", __FILE__)
3
+
4
+ desc "This generator creates a cropper file at app/assets/javascripts"
5
+
6
+ argument :attachment_name, :required => true, :type => :string, :desc => "The names of the attachment to crop.",
7
+ :banner => "attachment_name"
8
+
9
+ def create_cropper_file
10
+ template "carrierwave_cropper.js.coffee", File.join('app/assets/javascripts', class_path, "#{file_name.pluralize}.js.coffee")
11
+ end
12
+ end
@@ -0,0 +1,24 @@
1
+ jQuery ->
2
+ new CarrierwaveCropper()
3
+
4
+ class CarrierwaveCropper
5
+ constructor: ->
6
+ $('#<%= file_name %>_<%= attachment_name %>_cropbox').Jcrop
7
+ aspectRatio: 1
8
+ setSelect: [0, 0, 200, 200]
9
+ onSelect: @update
10
+ onChange: @update
11
+
12
+ update: (coords) =>
13
+ $('#<%= file_name %>_<%= attachment_name %>_crop_x').val(coords.x)
14
+ $('#<%= file_name %>_<%= attachment_name %>_crop_y').val(coords.y)
15
+ $('#<%= file_name %>_<%= attachment_name %>_crop_w').val(coords.w)
16
+ $('#<%= file_name %>_<%= attachment_name %>_crop_h').val(coords.h)
17
+ @updatePreview(coords)
18
+
19
+ updatePreview: (coords) =>
20
+ $('#avatar_crop_preview').css
21
+ width: Math.round(100/coords.w * $('#<%= file_name %>_<%= attachment_name %>_cropbox').width()) + 'px'
22
+ height: Math.round(100/coords.h * $('#<%= file_name %>_<%= attachment_name %>_cropbox').height()) + 'px'
23
+ marginLeft: '-' + Math.round(100/coords.w * coords.x) + 'px'
24
+ marginTop: '-' + Math.round(100/coords.h * coords.y) + 'px'
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+
3
+ describe Carrierwave::Crop::ModelAdditions do
4
+
5
+
6
+ describe ".crop_uploaded" do
7
+ it "for column avatar, accessors avatar_crop_x, avatar_crop_y, avatar_crop_w and avatar_crop_h are added" do
8
+ end
9
+ end
10
+
11
+
12
+ end
data/spec/crop_spec.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'spec_helper'
2
+
@@ -0,0 +1,2 @@
1
+ require 'carrierwave/crop'
2
+
Binary file
@@ -0,0 +1,1694 @@
1
+ /**
2
+ * jquery.Jcrop.js v0.9.12
3
+ * jQuery Image Cropping Plugin - released under MIT License
4
+ * Author: Kelly Hallman <khallman@gmail.com>
5
+ * http://github.com/tapmodo/Jcrop
6
+ * Copyright (c) 2008-2013 Tapmodo Interactive LLC {{{
7
+ *
8
+ * Permission is hereby granted, free of charge, to any person
9
+ * obtaining a copy of this software and associated documentation
10
+ * files (the "Software"), to deal in the Software without
11
+ * restriction, including without limitation the rights to use,
12
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ * copies of the Software, and to permit persons to whom the
14
+ * Software is furnished to do so, subject to the following
15
+ * conditions:
16
+ *
17
+ * The above copyright notice and this permission notice shall be
18
+ * included in all copies or substantial portions of the Software.
19
+ *
20
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
22
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
24
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
25
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27
+ * OTHER DEALINGS IN THE SOFTWARE.
28
+ *
29
+ * }}}
30
+ */
31
+
32
+ (function ($) {
33
+
34
+ $.Jcrop = function (obj, opt) {
35
+ var options = $.extend({}, $.Jcrop.defaults),
36
+ docOffset,
37
+ _ua = navigator.userAgent.toLowerCase(),
38
+ is_msie = /msie/.test(_ua),
39
+ ie6mode = /msie [1-6]\./.test(_ua);
40
+
41
+ // Internal Methods {{{
42
+ function px(n) {
43
+ return Math.round(n) + 'px';
44
+ }
45
+ function cssClass(cl) {
46
+ return options.baseClass + '-' + cl;
47
+ }
48
+ function supportsColorFade() {
49
+ return $.fx.step.hasOwnProperty('backgroundColor');
50
+ }
51
+ function getPos(obj) //{{{
52
+ {
53
+ var pos = $(obj).offset();
54
+ return [pos.left, pos.top];
55
+ }
56
+ //}}}
57
+ function mouseAbs(e) //{{{
58
+ {
59
+ return [(e.pageX - docOffset[0]), (e.pageY - docOffset[1])];
60
+ }
61
+ //}}}
62
+ function setOptions(opt) //{{{
63
+ {
64
+ if (typeof(opt) !== 'object') opt = {};
65
+ options = $.extend(options, opt);
66
+
67
+ $.each(['onChange','onSelect','onRelease','onDblClick'],function(i,e) {
68
+ if (typeof(options[e]) !== 'function') options[e] = function () {};
69
+ });
70
+ }
71
+ //}}}
72
+ function startDragMode(mode, pos, touch) //{{{
73
+ {
74
+ docOffset = getPos($img);
75
+ Tracker.setCursor(mode === 'move' ? mode : mode + '-resize');
76
+
77
+ if (mode === 'move') {
78
+ return Tracker.activateHandlers(createMover(pos), doneSelect, touch);
79
+ }
80
+
81
+ var fc = Coords.getFixed();
82
+ var opp = oppLockCorner(mode);
83
+ var opc = Coords.getCorner(oppLockCorner(opp));
84
+
85
+ Coords.setPressed(Coords.getCorner(opp));
86
+ Coords.setCurrent(opc);
87
+
88
+ Tracker.activateHandlers(dragmodeHandler(mode, fc), doneSelect, touch);
89
+ }
90
+ //}}}
91
+ function dragmodeHandler(mode, f) //{{{
92
+ {
93
+ return function (pos) {
94
+ if (!options.aspectRatio) {
95
+ switch (mode) {
96
+ case 'e':
97
+ pos[1] = f.y2;
98
+ break;
99
+ case 'w':
100
+ pos[1] = f.y2;
101
+ break;
102
+ case 'n':
103
+ pos[0] = f.x2;
104
+ break;
105
+ case 's':
106
+ pos[0] = f.x2;
107
+ break;
108
+ }
109
+ } else {
110
+ switch (mode) {
111
+ case 'e':
112
+ pos[1] = f.y + 1;
113
+ break;
114
+ case 'w':
115
+ pos[1] = f.y + 1;
116
+ break;
117
+ case 'n':
118
+ pos[0] = f.x + 1;
119
+ break;
120
+ case 's':
121
+ pos[0] = f.x + 1;
122
+ break;
123
+ }
124
+ }
125
+ Coords.setCurrent(pos);
126
+ Selection.update();
127
+ };
128
+ }
129
+ //}}}
130
+ function createMover(pos) //{{{
131
+ {
132
+ var lloc = pos;
133
+ KeyManager.watchKeys();
134
+
135
+ return function (pos) {
136
+ Coords.moveOffset([pos[0] - lloc[0], pos[1] - lloc[1]]);
137
+ lloc = pos;
138
+
139
+ Selection.update();
140
+ };
141
+ }
142
+ //}}}
143
+ function oppLockCorner(ord) //{{{
144
+ {
145
+ switch (ord) {
146
+ case 'n':
147
+ return 'sw';
148
+ case 's':
149
+ return 'nw';
150
+ case 'e':
151
+ return 'nw';
152
+ case 'w':
153
+ return 'ne';
154
+ case 'ne':
155
+ return 'sw';
156
+ case 'nw':
157
+ return 'se';
158
+ case 'se':
159
+ return 'nw';
160
+ case 'sw':
161
+ return 'ne';
162
+ }
163
+ }
164
+ //}}}
165
+ function createDragger(ord) //{{{
166
+ {
167
+ return function (e) {
168
+ if (options.disabled) {
169
+ return false;
170
+ }
171
+ if ((ord === 'move') && !options.allowMove) {
172
+ return false;
173
+ }
174
+
175
+ // Fix position of crop area when dragged the very first time.
176
+ // Necessary when crop image is in a hidden element when page is loaded.
177
+ docOffset = getPos($img);
178
+
179
+ btndown = true;
180
+ startDragMode(ord, mouseAbs(e));
181
+ e.stopPropagation();
182
+ e.preventDefault();
183
+ return false;
184
+ };
185
+ }
186
+ //}}}
187
+ function presize($obj, w, h) //{{{
188
+ {
189
+ var nw = $obj.width(),
190
+ nh = $obj.height();
191
+ if ((nw > w) && w > 0) {
192
+ nw = w;
193
+ nh = (w / $obj.width()) * $obj.height();
194
+ }
195
+ if ((nh > h) && h > 0) {
196
+ nh = h;
197
+ nw = (h / $obj.height()) * $obj.width();
198
+ }
199
+ xscale = $obj.width() / nw;
200
+ yscale = $obj.height() / nh;
201
+ $obj.width(nw).height(nh);
202
+ }
203
+ //}}}
204
+ function unscale(c) //{{{
205
+ {
206
+ return {
207
+ x: c.x * xscale,
208
+ y: c.y * yscale,
209
+ x2: c.x2 * xscale,
210
+ y2: c.y2 * yscale,
211
+ w: c.w * xscale,
212
+ h: c.h * yscale
213
+ };
214
+ }
215
+ //}}}
216
+ function doneSelect(pos) //{{{
217
+ {
218
+ var c = Coords.getFixed();
219
+ if ((c.w > options.minSelect[0]) && (c.h > options.minSelect[1])) {
220
+ Selection.enableHandles();
221
+ Selection.done();
222
+ } else {
223
+ Selection.release();
224
+ }
225
+ Tracker.setCursor(options.allowSelect ? 'crosshair' : 'default');
226
+ }
227
+ //}}}
228
+ function newSelection(e) //{{{
229
+ {
230
+ if (options.disabled) {
231
+ return false;
232
+ }
233
+ if (!options.allowSelect) {
234
+ return false;
235
+ }
236
+ btndown = true;
237
+ docOffset = getPos($img);
238
+ Selection.disableHandles();
239
+ Tracker.setCursor('crosshair');
240
+ var pos = mouseAbs(e);
241
+ Coords.setPressed(pos);
242
+ Selection.update();
243
+ Tracker.activateHandlers(selectDrag, doneSelect, e.type.substring(0,5)==='touch');
244
+ KeyManager.watchKeys();
245
+
246
+ e.stopPropagation();
247
+ e.preventDefault();
248
+ return false;
249
+ }
250
+ //}}}
251
+ function selectDrag(pos) //{{{
252
+ {
253
+ Coords.setCurrent(pos);
254
+ Selection.update();
255
+ }
256
+ //}}}
257
+ function newTracker() //{{{
258
+ {
259
+ var trk = $('<div></div>').addClass(cssClass('tracker'));
260
+ if (is_msie) {
261
+ trk.css({
262
+ opacity: 0,
263
+ backgroundColor: 'white'
264
+ });
265
+ }
266
+ return trk;
267
+ }
268
+ //}}}
269
+
270
+ // }}}
271
+ // Initialization {{{
272
+ // Sanitize some options {{{
273
+ if (typeof(obj) !== 'object') {
274
+ obj = $(obj)[0];
275
+ }
276
+ if (typeof(opt) !== 'object') {
277
+ opt = {};
278
+ }
279
+ // }}}
280
+ setOptions(opt);
281
+ // Initialize some jQuery objects {{{
282
+ // The values are SET on the image(s) for the interface
283
+ // If the original image has any of these set, they will be reset
284
+ // However, if you destroy() the Jcrop instance the original image's
285
+ // character in the DOM will be as you left it.
286
+ var img_css = {
287
+ border: 'none',
288
+ visibility: 'visible',
289
+ margin: 0,
290
+ padding: 0,
291
+ position: 'absolute',
292
+ top: 0,
293
+ left: 0
294
+ };
295
+
296
+ var $origimg = $(obj),
297
+ img_mode = true;
298
+
299
+ if (obj.tagName == 'IMG') {
300
+ // Fix size of crop image.
301
+ // Necessary when crop image is within a hidden element when page is loaded.
302
+ if ($origimg[0].width != 0 && $origimg[0].height != 0) {
303
+ // Obtain dimensions from contained img element.
304
+ $origimg.width($origimg[0].width);
305
+ $origimg.height($origimg[0].height);
306
+ } else {
307
+ // Obtain dimensions from temporary image in case the original is not loaded yet (e.g. IE 7.0).
308
+ var tempImage = new Image();
309
+ tempImage.src = $origimg[0].src;
310
+ $origimg.width(tempImage.width);
311
+ $origimg.height(tempImage.height);
312
+ }
313
+
314
+ var $img = $origimg.clone().removeAttr('id').css(img_css).show();
315
+
316
+ $img.width($origimg.width());
317
+ $img.height($origimg.height());
318
+ $origimg.after($img).hide();
319
+
320
+ } else {
321
+ $img = $origimg.css(img_css).show();
322
+ img_mode = false;
323
+ if (options.shade === null) { options.shade = true; }
324
+ }
325
+
326
+ presize($img, options.boxWidth, options.boxHeight);
327
+
328
+ var boundx = $img.width(),
329
+ boundy = $img.height(),
330
+
331
+
332
+ $div = $('<div />').width(boundx).height(boundy).addClass(cssClass('holder')).css({
333
+ position: 'relative',
334
+ backgroundColor: options.bgColor
335
+ }).insertAfter($origimg).append($img);
336
+
337
+ if (options.addClass) {
338
+ $div.addClass(options.addClass);
339
+ }
340
+
341
+ var $img2 = $('<div />'),
342
+
343
+ $img_holder = $('<div />')
344
+ .width('100%').height('100%').css({
345
+ zIndex: 310,
346
+ position: 'absolute',
347
+ overflow: 'hidden'
348
+ }),
349
+
350
+ $hdl_holder = $('<div />')
351
+ .width('100%').height('100%').css('zIndex', 320),
352
+
353
+ $sel = $('<div />')
354
+ .css({
355
+ position: 'absolute',
356
+ zIndex: 600
357
+ }).dblclick(function(){
358
+ var c = Coords.getFixed();
359
+ options.onDblClick.call(api,c);
360
+ }).insertBefore($img).append($img_holder, $hdl_holder);
361
+
362
+ if (img_mode) {
363
+
364
+ $img2 = $('<img />')
365
+ .attr('src', $img.attr('src')).css(img_css).width(boundx).height(boundy),
366
+
367
+ $img_holder.append($img2);
368
+
369
+ }
370
+
371
+ if (ie6mode) {
372
+ $sel.css({
373
+ overflowY: 'hidden'
374
+ });
375
+ }
376
+
377
+ var bound = options.boundary;
378
+ var $trk = newTracker().width(boundx + (bound * 2)).height(boundy + (bound * 2)).css({
379
+ position: 'absolute',
380
+ top: px(-bound),
381
+ left: px(-bound),
382
+ zIndex: 290
383
+ }).mousedown(newSelection);
384
+
385
+ /* }}} */
386
+ // Set more variables {{{
387
+ var bgcolor = options.bgColor,
388
+ bgopacity = options.bgOpacity,
389
+ xlimit, ylimit, xmin, ymin, xscale, yscale, enabled = true,
390
+ btndown, animating, shift_down;
391
+
392
+ docOffset = getPos($img);
393
+ // }}}
394
+ // }}}
395
+ // Internal Modules {{{
396
+ // Touch Module {{{
397
+ var Touch = (function () {
398
+ // Touch support detection function adapted (under MIT License)
399
+ // from code by Jeffrey Sambells - http://github.com/iamamused/
400
+ function hasTouchSupport() {
401
+ var support = {}, events = ['touchstart', 'touchmove', 'touchend'],
402
+ el = document.createElement('div'), i;
403
+
404
+ try {
405
+ for(i=0; i<events.length; i++) {
406
+ var eventName = events[i];
407
+ eventName = 'on' + eventName;
408
+ var isSupported = (eventName in el);
409
+ if (!isSupported) {
410
+ el.setAttribute(eventName, 'return;');
411
+ isSupported = typeof el[eventName] == 'function';
412
+ }
413
+ support[events[i]] = isSupported;
414
+ }
415
+ return support.touchstart && support.touchend && support.touchmove;
416
+ }
417
+ catch(err) {
418
+ return false;
419
+ }
420
+ }
421
+
422
+ function detectSupport() {
423
+ if ((options.touchSupport === true) || (options.touchSupport === false)) return options.touchSupport;
424
+ else return hasTouchSupport();
425
+ }
426
+ return {
427
+ createDragger: function (ord) {
428
+ return function (e) {
429
+ if (options.disabled) {
430
+ return false;
431
+ }
432
+ if ((ord === 'move') && !options.allowMove) {
433
+ return false;
434
+ }
435
+ docOffset = getPos($img);
436
+ btndown = true;
437
+ startDragMode(ord, mouseAbs(Touch.cfilter(e)), true);
438
+ e.stopPropagation();
439
+ e.preventDefault();
440
+ return false;
441
+ };
442
+ },
443
+ newSelection: function (e) {
444
+ return newSelection(Touch.cfilter(e));
445
+ },
446
+ cfilter: function (e){
447
+ e.pageX = e.originalEvent.changedTouches[0].pageX;
448
+ e.pageY = e.originalEvent.changedTouches[0].pageY;
449
+ return e;
450
+ },
451
+ isSupported: hasTouchSupport,
452
+ support: detectSupport()
453
+ };
454
+ }());
455
+ // }}}
456
+ // Coords Module {{{
457
+ var Coords = (function () {
458
+ var x1 = 0,
459
+ y1 = 0,
460
+ x2 = 0,
461
+ y2 = 0,
462
+ ox, oy;
463
+
464
+ function setPressed(pos) //{{{
465
+ {
466
+ pos = rebound(pos);
467
+ x2 = x1 = pos[0];
468
+ y2 = y1 = pos[1];
469
+ }
470
+ //}}}
471
+ function setCurrent(pos) //{{{
472
+ {
473
+ pos = rebound(pos);
474
+ ox = pos[0] - x2;
475
+ oy = pos[1] - y2;
476
+ x2 = pos[0];
477
+ y2 = pos[1];
478
+ }
479
+ //}}}
480
+ function getOffset() //{{{
481
+ {
482
+ return [ox, oy];
483
+ }
484
+ //}}}
485
+ function moveOffset(offset) //{{{
486
+ {
487
+ var ox = offset[0],
488
+ oy = offset[1];
489
+
490
+ if (0 > x1 + ox) {
491
+ ox -= ox + x1;
492
+ }
493
+ if (0 > y1 + oy) {
494
+ oy -= oy + y1;
495
+ }
496
+
497
+ if (boundy < y2 + oy) {
498
+ oy += boundy - (y2 + oy);
499
+ }
500
+ if (boundx < x2 + ox) {
501
+ ox += boundx - (x2 + ox);
502
+ }
503
+
504
+ x1 += ox;
505
+ x2 += ox;
506
+ y1 += oy;
507
+ y2 += oy;
508
+ }
509
+ //}}}
510
+ function getCorner(ord) //{{{
511
+ {
512
+ var c = getFixed();
513
+ switch (ord) {
514
+ case 'ne':
515
+ return [c.x2, c.y];
516
+ case 'nw':
517
+ return [c.x, c.y];
518
+ case 'se':
519
+ return [c.x2, c.y2];
520
+ case 'sw':
521
+ return [c.x, c.y2];
522
+ }
523
+ }
524
+ //}}}
525
+ function getFixed() //{{{
526
+ {
527
+ if (!options.aspectRatio) {
528
+ return getRect();
529
+ }
530
+ // This function could use some optimization I think...
531
+ var aspect = options.aspectRatio,
532
+ min_x = options.minSize[0] / xscale,
533
+
534
+
535
+ //min_y = options.minSize[1]/yscale,
536
+ max_x = options.maxSize[0] / xscale,
537
+ max_y = options.maxSize[1] / yscale,
538
+ rw = x2 - x1,
539
+ rh = y2 - y1,
540
+ rwa = Math.abs(rw),
541
+ rha = Math.abs(rh),
542
+ real_ratio = rwa / rha,
543
+ xx, yy, w, h;
544
+
545
+ if (max_x === 0) {
546
+ max_x = boundx * 10;
547
+ }
548
+ if (max_y === 0) {
549
+ max_y = boundy * 10;
550
+ }
551
+ if (real_ratio < aspect) {
552
+ yy = y2;
553
+ w = rha * aspect;
554
+ xx = rw < 0 ? x1 - w : w + x1;
555
+
556
+ if (xx < 0) {
557
+ xx = 0;
558
+ h = Math.abs((xx - x1) / aspect);
559
+ yy = rh < 0 ? y1 - h : h + y1;
560
+ } else if (xx > boundx) {
561
+ xx = boundx;
562
+ h = Math.abs((xx - x1) / aspect);
563
+ yy = rh < 0 ? y1 - h : h + y1;
564
+ }
565
+ } else {
566
+ xx = x2;
567
+ h = rwa / aspect;
568
+ yy = rh < 0 ? y1 - h : y1 + h;
569
+ if (yy < 0) {
570
+ yy = 0;
571
+ w = Math.abs((yy - y1) * aspect);
572
+ xx = rw < 0 ? x1 - w : w + x1;
573
+ } else if (yy > boundy) {
574
+ yy = boundy;
575
+ w = Math.abs(yy - y1) * aspect;
576
+ xx = rw < 0 ? x1 - w : w + x1;
577
+ }
578
+ }
579
+
580
+ // Magic %-)
581
+ if (xx > x1) { // right side
582
+ if (xx - x1 < min_x) {
583
+ xx = x1 + min_x;
584
+ } else if (xx - x1 > max_x) {
585
+ xx = x1 + max_x;
586
+ }
587
+ if (yy > y1) {
588
+ yy = y1 + (xx - x1) / aspect;
589
+ } else {
590
+ yy = y1 - (xx - x1) / aspect;
591
+ }
592
+ } else if (xx < x1) { // left side
593
+ if (x1 - xx < min_x) {
594
+ xx = x1 - min_x;
595
+ } else if (x1 - xx > max_x) {
596
+ xx = x1 - max_x;
597
+ }
598
+ if (yy > y1) {
599
+ yy = y1 + (x1 - xx) / aspect;
600
+ } else {
601
+ yy = y1 - (x1 - xx) / aspect;
602
+ }
603
+ }
604
+
605
+ if (xx < 0) {
606
+ x1 -= xx;
607
+ xx = 0;
608
+ } else if (xx > boundx) {
609
+ x1 -= xx - boundx;
610
+ xx = boundx;
611
+ }
612
+
613
+ if (yy < 0) {
614
+ y1 -= yy;
615
+ yy = 0;
616
+ } else if (yy > boundy) {
617
+ y1 -= yy - boundy;
618
+ yy = boundy;
619
+ }
620
+
621
+ return makeObj(flipCoords(x1, y1, xx, yy));
622
+ }
623
+ //}}}
624
+ function rebound(p) //{{{
625
+ {
626
+ if (p[0] < 0) p[0] = 0;
627
+ if (p[1] < 0) p[1] = 0;
628
+
629
+ if (p[0] > boundx) p[0] = boundx;
630
+ if (p[1] > boundy) p[1] = boundy;
631
+
632
+ return [Math.round(p[0]), Math.round(p[1])];
633
+ }
634
+ //}}}
635
+ function flipCoords(x1, y1, x2, y2) //{{{
636
+ {
637
+ var xa = x1,
638
+ xb = x2,
639
+ ya = y1,
640
+ yb = y2;
641
+ if (x2 < x1) {
642
+ xa = x2;
643
+ xb = x1;
644
+ }
645
+ if (y2 < y1) {
646
+ ya = y2;
647
+ yb = y1;
648
+ }
649
+ return [xa, ya, xb, yb];
650
+ }
651
+ //}}}
652
+ function getRect() //{{{
653
+ {
654
+ var xsize = x2 - x1,
655
+ ysize = y2 - y1,
656
+ delta;
657
+
658
+ if (xlimit && (Math.abs(xsize) > xlimit)) {
659
+ x2 = (xsize > 0) ? (x1 + xlimit) : (x1 - xlimit);
660
+ }
661
+ if (ylimit && (Math.abs(ysize) > ylimit)) {
662
+ y2 = (ysize > 0) ? (y1 + ylimit) : (y1 - ylimit);
663
+ }
664
+
665
+ if (ymin / yscale && (Math.abs(ysize) < ymin / yscale)) {
666
+ y2 = (ysize > 0) ? (y1 + ymin / yscale) : (y1 - ymin / yscale);
667
+ }
668
+ if (xmin / xscale && (Math.abs(xsize) < xmin / xscale)) {
669
+ x2 = (xsize > 0) ? (x1 + xmin / xscale) : (x1 - xmin / xscale);
670
+ }
671
+
672
+ if (x1 < 0) {
673
+ x2 -= x1;
674
+ x1 -= x1;
675
+ }
676
+ if (y1 < 0) {
677
+ y2 -= y1;
678
+ y1 -= y1;
679
+ }
680
+ if (x2 < 0) {
681
+ x1 -= x2;
682
+ x2 -= x2;
683
+ }
684
+ if (y2 < 0) {
685
+ y1 -= y2;
686
+ y2 -= y2;
687
+ }
688
+ if (x2 > boundx) {
689
+ delta = x2 - boundx;
690
+ x1 -= delta;
691
+ x2 -= delta;
692
+ }
693
+ if (y2 > boundy) {
694
+ delta = y2 - boundy;
695
+ y1 -= delta;
696
+ y2 -= delta;
697
+ }
698
+ if (x1 > boundx) {
699
+ delta = x1 - boundy;
700
+ y2 -= delta;
701
+ y1 -= delta;
702
+ }
703
+ if (y1 > boundy) {
704
+ delta = y1 - boundy;
705
+ y2 -= delta;
706
+ y1 -= delta;
707
+ }
708
+
709
+ return makeObj(flipCoords(x1, y1, x2, y2));
710
+ }
711
+ //}}}
712
+ function makeObj(a) //{{{
713
+ {
714
+ return {
715
+ x: a[0],
716
+ y: a[1],
717
+ x2: a[2],
718
+ y2: a[3],
719
+ w: a[2] - a[0],
720
+ h: a[3] - a[1]
721
+ };
722
+ }
723
+ //}}}
724
+
725
+ return {
726
+ flipCoords: flipCoords,
727
+ setPressed: setPressed,
728
+ setCurrent: setCurrent,
729
+ getOffset: getOffset,
730
+ moveOffset: moveOffset,
731
+ getCorner: getCorner,
732
+ getFixed: getFixed
733
+ };
734
+ }());
735
+
736
+ //}}}
737
+ // Shade Module {{{
738
+ var Shade = (function() {
739
+ var enabled = false,
740
+ holder = $('<div />').css({
741
+ position: 'absolute',
742
+ zIndex: 240,
743
+ opacity: 0
744
+ }),
745
+ shades = {
746
+ top: createShade(),
747
+ left: createShade().height(boundy),
748
+ right: createShade().height(boundy),
749
+ bottom: createShade()
750
+ };
751
+
752
+ function resizeShades(w,h) {
753
+ shades.left.css({ height: px(h) });
754
+ shades.right.css({ height: px(h) });
755
+ }
756
+ function updateAuto()
757
+ {
758
+ return updateShade(Coords.getFixed());
759
+ }
760
+ function updateShade(c)
761
+ {
762
+ shades.top.css({
763
+ left: px(c.x),
764
+ width: px(c.w),
765
+ height: px(c.y)
766
+ });
767
+ shades.bottom.css({
768
+ top: px(c.y2),
769
+ left: px(c.x),
770
+ width: px(c.w),
771
+ height: px(boundy-c.y2)
772
+ });
773
+ shades.right.css({
774
+ left: px(c.x2),
775
+ width: px(boundx-c.x2)
776
+ });
777
+ shades.left.css({
778
+ width: px(c.x)
779
+ });
780
+ }
781
+ function createShade() {
782
+ return $('<div />').css({
783
+ position: 'absolute',
784
+ backgroundColor: options.shadeColor||options.bgColor
785
+ }).appendTo(holder);
786
+ }
787
+ function enableShade() {
788
+ if (!enabled) {
789
+ enabled = true;
790
+ holder.insertBefore($img);
791
+ updateAuto();
792
+ Selection.setBgOpacity(1,0,1);
793
+ $img2.hide();
794
+
795
+ setBgColor(options.shadeColor||options.bgColor,1);
796
+ if (Selection.isAwake())
797
+ {
798
+ setOpacity(options.bgOpacity,1);
799
+ }
800
+ else setOpacity(1,1);
801
+ }
802
+ }
803
+ function setBgColor(color,now) {
804
+ colorChangeMacro(getShades(),color,now);
805
+ }
806
+ function disableShade() {
807
+ if (enabled) {
808
+ holder.remove();
809
+ $img2.show();
810
+ enabled = false;
811
+ if (Selection.isAwake()) {
812
+ Selection.setBgOpacity(options.bgOpacity,1,1);
813
+ } else {
814
+ Selection.setBgOpacity(1,1,1);
815
+ Selection.disableHandles();
816
+ }
817
+ colorChangeMacro($div,0,1);
818
+ }
819
+ }
820
+ function setOpacity(opacity,now) {
821
+ if (enabled) {
822
+ if (options.bgFade && !now) {
823
+ holder.animate({
824
+ opacity: 1-opacity
825
+ },{
826
+ queue: false,
827
+ duration: options.fadeTime
828
+ });
829
+ }
830
+ else holder.css({opacity:1-opacity});
831
+ }
832
+ }
833
+ function refreshAll() {
834
+ options.shade ? enableShade() : disableShade();
835
+ if (Selection.isAwake()) setOpacity(options.bgOpacity);
836
+ }
837
+ function getShades() {
838
+ return holder.children();
839
+ }
840
+
841
+ return {
842
+ update: updateAuto,
843
+ updateRaw: updateShade,
844
+ getShades: getShades,
845
+ setBgColor: setBgColor,
846
+ enable: enableShade,
847
+ disable: disableShade,
848
+ resize: resizeShades,
849
+ refresh: refreshAll,
850
+ opacity: setOpacity
851
+ };
852
+ }());
853
+ // }}}
854
+ // Selection Module {{{
855
+ var Selection = (function () {
856
+ var awake,
857
+ hdep = 370,
858
+ borders = {},
859
+ handle = {},
860
+ dragbar = {},
861
+ seehandles = false;
862
+
863
+ // Private Methods
864
+ function insertBorder(type) //{{{
865
+ {
866
+ var jq = $('<div />').css({
867
+ position: 'absolute',
868
+ opacity: options.borderOpacity
869
+ }).addClass(cssClass(type));
870
+ $img_holder.append(jq);
871
+ return jq;
872
+ }
873
+ //}}}
874
+ function dragDiv(ord, zi) //{{{
875
+ {
876
+ var jq = $('<div />').mousedown(createDragger(ord)).css({
877
+ cursor: ord + '-resize',
878
+ position: 'absolute',
879
+ zIndex: zi
880
+ }).addClass('ord-'+ord);
881
+
882
+ if (Touch.support) {
883
+ jq.bind('touchstart.jcrop', Touch.createDragger(ord));
884
+ }
885
+
886
+ $hdl_holder.append(jq);
887
+ return jq;
888
+ }
889
+ //}}}
890
+ function insertHandle(ord) //{{{
891
+ {
892
+ var hs = options.handleSize,
893
+
894
+ div = dragDiv(ord, hdep++).css({
895
+ opacity: options.handleOpacity
896
+ }).addClass(cssClass('handle'));
897
+
898
+ if (hs) { div.width(hs).height(hs); }
899
+
900
+ return div;
901
+ }
902
+ //}}}
903
+ function insertDragbar(ord) //{{{
904
+ {
905
+ return dragDiv(ord, hdep++).addClass('jcrop-dragbar');
906
+ }
907
+ //}}}
908
+ function createDragbars(li) //{{{
909
+ {
910
+ var i;
911
+ for (i = 0; i < li.length; i++) {
912
+ dragbar[li[i]] = insertDragbar(li[i]);
913
+ }
914
+ }
915
+ //}}}
916
+ function createBorders(li) //{{{
917
+ {
918
+ var cl,i;
919
+ for (i = 0; i < li.length; i++) {
920
+ switch(li[i]){
921
+ case'n': cl='hline'; break;
922
+ case's': cl='hline bottom'; break;
923
+ case'e': cl='vline right'; break;
924
+ case'w': cl='vline'; break;
925
+ }
926
+ borders[li[i]] = insertBorder(cl);
927
+ }
928
+ }
929
+ //}}}
930
+ function createHandles(li) //{{{
931
+ {
932
+ var i;
933
+ for (i = 0; i < li.length; i++) {
934
+ handle[li[i]] = insertHandle(li[i]);
935
+ }
936
+ }
937
+ //}}}
938
+ function moveto(x, y) //{{{
939
+ {
940
+ if (!options.shade) {
941
+ $img2.css({
942
+ top: px(-y),
943
+ left: px(-x)
944
+ });
945
+ }
946
+ $sel.css({
947
+ top: px(y),
948
+ left: px(x)
949
+ });
950
+ }
951
+ //}}}
952
+ function resize(w, h) //{{{
953
+ {
954
+ $sel.width(Math.round(w)).height(Math.round(h));
955
+ }
956
+ //}}}
957
+ function refresh() //{{{
958
+ {
959
+ var c = Coords.getFixed();
960
+
961
+ Coords.setPressed([c.x, c.y]);
962
+ Coords.setCurrent([c.x2, c.y2]);
963
+
964
+ updateVisible();
965
+ }
966
+ //}}}
967
+
968
+ // Internal Methods
969
+ function updateVisible(select) //{{{
970
+ {
971
+ if (awake) {
972
+ return update(select);
973
+ }
974
+ }
975
+ //}}}
976
+ function update(select) //{{{
977
+ {
978
+ var c = Coords.getFixed();
979
+
980
+ resize(c.w, c.h);
981
+ moveto(c.x, c.y);
982
+ if (options.shade) Shade.updateRaw(c);
983
+
984
+ awake || show();
985
+
986
+ if (select) {
987
+ options.onSelect.call(api, unscale(c));
988
+ } else {
989
+ options.onChange.call(api, unscale(c));
990
+ }
991
+ }
992
+ //}}}
993
+ function setBgOpacity(opacity,force,now) //{{{
994
+ {
995
+ if (!awake && !force) return;
996
+ if (options.bgFade && !now) {
997
+ $img.animate({
998
+ opacity: opacity
999
+ },{
1000
+ queue: false,
1001
+ duration: options.fadeTime
1002
+ });
1003
+ } else {
1004
+ $img.css('opacity', opacity);
1005
+ }
1006
+ }
1007
+ //}}}
1008
+ function show() //{{{
1009
+ {
1010
+ $sel.show();
1011
+
1012
+ if (options.shade) Shade.opacity(bgopacity);
1013
+ else setBgOpacity(bgopacity,true);
1014
+
1015
+ awake = true;
1016
+ }
1017
+ //}}}
1018
+ function release() //{{{
1019
+ {
1020
+ disableHandles();
1021
+ $sel.hide();
1022
+
1023
+ if (options.shade) Shade.opacity(1);
1024
+ else setBgOpacity(1);
1025
+
1026
+ awake = false;
1027
+ options.onRelease.call(api);
1028
+ }
1029
+ //}}}
1030
+ function showHandles() //{{{
1031
+ {
1032
+ if (seehandles) {
1033
+ $hdl_holder.show();
1034
+ }
1035
+ }
1036
+ //}}}
1037
+ function enableHandles() //{{{
1038
+ {
1039
+ seehandles = true;
1040
+ if (options.allowResize) {
1041
+ $hdl_holder.show();
1042
+ return true;
1043
+ }
1044
+ }
1045
+ //}}}
1046
+ function disableHandles() //{{{
1047
+ {
1048
+ seehandles = false;
1049
+ $hdl_holder.hide();
1050
+ }
1051
+ //}}}
1052
+ function animMode(v) //{{{
1053
+ {
1054
+ if (v) {
1055
+ animating = true;
1056
+ disableHandles();
1057
+ } else {
1058
+ animating = false;
1059
+ enableHandles();
1060
+ }
1061
+ }
1062
+ //}}}
1063
+ function done() //{{{
1064
+ {
1065
+ animMode(false);
1066
+ refresh();
1067
+ }
1068
+ //}}}
1069
+ // Insert draggable elements {{{
1070
+ // Insert border divs for outline
1071
+
1072
+ if (options.dragEdges && $.isArray(options.createDragbars))
1073
+ createDragbars(options.createDragbars);
1074
+
1075
+ if ($.isArray(options.createHandles))
1076
+ createHandles(options.createHandles);
1077
+
1078
+ if (options.drawBorders && $.isArray(options.createBorders))
1079
+ createBorders(options.createBorders);
1080
+
1081
+ //}}}
1082
+
1083
+ // This is a hack for iOS5 to support drag/move touch functionality
1084
+ $(document).bind('touchstart.jcrop-ios',function(e) {
1085
+ if ($(e.currentTarget).hasClass('jcrop-tracker')) e.stopPropagation();
1086
+ });
1087
+
1088
+ var $track = newTracker().mousedown(createDragger('move')).css({
1089
+ cursor: 'move',
1090
+ position: 'absolute',
1091
+ zIndex: 360
1092
+ });
1093
+
1094
+ if (Touch.support) {
1095
+ $track.bind('touchstart.jcrop', Touch.createDragger('move'));
1096
+ }
1097
+
1098
+ $img_holder.append($track);
1099
+ disableHandles();
1100
+
1101
+ return {
1102
+ updateVisible: updateVisible,
1103
+ update: update,
1104
+ release: release,
1105
+ refresh: refresh,
1106
+ isAwake: function () {
1107
+ return awake;
1108
+ },
1109
+ setCursor: function (cursor) {
1110
+ $track.css('cursor', cursor);
1111
+ },
1112
+ enableHandles: enableHandles,
1113
+ enableOnly: function () {
1114
+ seehandles = true;
1115
+ },
1116
+ showHandles: showHandles,
1117
+ disableHandles: disableHandles,
1118
+ animMode: animMode,
1119
+ setBgOpacity: setBgOpacity,
1120
+ done: done
1121
+ };
1122
+ }());
1123
+
1124
+ //}}}
1125
+ // Tracker Module {{{
1126
+ var Tracker = (function () {
1127
+ var onMove = function () {},
1128
+ onDone = function () {},
1129
+ trackDoc = options.trackDocument;
1130
+
1131
+ function toFront(touch) //{{{
1132
+ {
1133
+ $trk.css({
1134
+ zIndex: 450
1135
+ });
1136
+
1137
+ if (touch)
1138
+ $(document)
1139
+ .bind('touchmove.jcrop', trackTouchMove)
1140
+ .bind('touchend.jcrop', trackTouchEnd);
1141
+
1142
+ else if (trackDoc)
1143
+ $(document)
1144
+ .bind('mousemove.jcrop',trackMove)
1145
+ .bind('mouseup.jcrop',trackUp);
1146
+ }
1147
+ //}}}
1148
+ function toBack() //{{{
1149
+ {
1150
+ $trk.css({
1151
+ zIndex: 290
1152
+ });
1153
+ $(document).unbind('.jcrop');
1154
+ }
1155
+ //}}}
1156
+ function trackMove(e) //{{{
1157
+ {
1158
+ onMove(mouseAbs(e));
1159
+ return false;
1160
+ }
1161
+ //}}}
1162
+ function trackUp(e) //{{{
1163
+ {
1164
+ e.preventDefault();
1165
+ e.stopPropagation();
1166
+
1167
+ if (btndown) {
1168
+ btndown = false;
1169
+
1170
+ onDone(mouseAbs(e));
1171
+
1172
+ if (Selection.isAwake()) {
1173
+ options.onSelect.call(api, unscale(Coords.getFixed()));
1174
+ }
1175
+
1176
+ toBack();
1177
+ onMove = function () {};
1178
+ onDone = function () {};
1179
+ }
1180
+
1181
+ return false;
1182
+ }
1183
+ //}}}
1184
+ function activateHandlers(move, done, touch) //{{{
1185
+ {
1186
+ btndown = true;
1187
+ onMove = move;
1188
+ onDone = done;
1189
+ toFront(touch);
1190
+ return false;
1191
+ }
1192
+ //}}}
1193
+ function trackTouchMove(e) //{{{
1194
+ {
1195
+ onMove(mouseAbs(Touch.cfilter(e)));
1196
+ return false;
1197
+ }
1198
+ //}}}
1199
+ function trackTouchEnd(e) //{{{
1200
+ {
1201
+ return trackUp(Touch.cfilter(e));
1202
+ }
1203
+ //}}}
1204
+ function setCursor(t) //{{{
1205
+ {
1206
+ $trk.css('cursor', t);
1207
+ }
1208
+ //}}}
1209
+
1210
+ if (!trackDoc) {
1211
+ $trk.mousemove(trackMove).mouseup(trackUp).mouseout(trackUp);
1212
+ }
1213
+
1214
+ $img.before($trk);
1215
+ return {
1216
+ activateHandlers: activateHandlers,
1217
+ setCursor: setCursor
1218
+ };
1219
+ }());
1220
+ //}}}
1221
+ // KeyManager Module {{{
1222
+ var KeyManager = (function () {
1223
+ var $keymgr = $('<input type="radio" />').css({
1224
+ position: 'fixed',
1225
+ left: '-120px',
1226
+ width: '12px'
1227
+ }).addClass('jcrop-keymgr'),
1228
+
1229
+ $keywrap = $('<div />').css({
1230
+ position: 'absolute',
1231
+ overflow: 'hidden'
1232
+ }).append($keymgr);
1233
+
1234
+ function watchKeys() //{{{
1235
+ {
1236
+ if (options.keySupport) {
1237
+ $keymgr.show();
1238
+ $keymgr.focus();
1239
+ }
1240
+ }
1241
+ //}}}
1242
+ function onBlur(e) //{{{
1243
+ {
1244
+ $keymgr.hide();
1245
+ }
1246
+ //}}}
1247
+ function doNudge(e, x, y) //{{{
1248
+ {
1249
+ if (options.allowMove) {
1250
+ Coords.moveOffset([x, y]);
1251
+ Selection.updateVisible(true);
1252
+ }
1253
+ e.preventDefault();
1254
+ e.stopPropagation();
1255
+ }
1256
+ //}}}
1257
+ function parseKey(e) //{{{
1258
+ {
1259
+ if (e.ctrlKey || e.metaKey) {
1260
+ return true;
1261
+ }
1262
+ shift_down = e.shiftKey ? true : false;
1263
+ var nudge = shift_down ? 10 : 1;
1264
+
1265
+ switch (e.keyCode) {
1266
+ case 37:
1267
+ doNudge(e, -nudge, 0);
1268
+ break;
1269
+ case 39:
1270
+ doNudge(e, nudge, 0);
1271
+ break;
1272
+ case 38:
1273
+ doNudge(e, 0, -nudge);
1274
+ break;
1275
+ case 40:
1276
+ doNudge(e, 0, nudge);
1277
+ break;
1278
+ case 27:
1279
+ if (options.allowSelect) Selection.release();
1280
+ break;
1281
+ case 9:
1282
+ return true;
1283
+ }
1284
+
1285
+ return false;
1286
+ }
1287
+ //}}}
1288
+
1289
+ if (options.keySupport) {
1290
+ $keymgr.keydown(parseKey).blur(onBlur);
1291
+ if (ie6mode || !options.fixedSupport) {
1292
+ $keymgr.css({
1293
+ position: 'absolute',
1294
+ left: '-20px'
1295
+ });
1296
+ $keywrap.append($keymgr).insertBefore($img);
1297
+ } else {
1298
+ $keymgr.insertBefore($img);
1299
+ }
1300
+ }
1301
+
1302
+
1303
+ return {
1304
+ watchKeys: watchKeys
1305
+ };
1306
+ }());
1307
+ //}}}
1308
+ // }}}
1309
+ // API methods {{{
1310
+ function setClass(cname) //{{{
1311
+ {
1312
+ $div.removeClass().addClass(cssClass('holder')).addClass(cname);
1313
+ }
1314
+ //}}}
1315
+ function animateTo(a, callback) //{{{
1316
+ {
1317
+ var x1 = a[0] / xscale,
1318
+ y1 = a[1] / yscale,
1319
+ x2 = a[2] / xscale,
1320
+ y2 = a[3] / yscale;
1321
+
1322
+ if (animating) {
1323
+ return;
1324
+ }
1325
+
1326
+ var animto = Coords.flipCoords(x1, y1, x2, y2),
1327
+ c = Coords.getFixed(),
1328
+ initcr = [c.x, c.y, c.x2, c.y2],
1329
+ animat = initcr,
1330
+ interv = options.animationDelay,
1331
+ ix1 = animto[0] - initcr[0],
1332
+ iy1 = animto[1] - initcr[1],
1333
+ ix2 = animto[2] - initcr[2],
1334
+ iy2 = animto[3] - initcr[3],
1335
+ pcent = 0,
1336
+ velocity = options.swingSpeed;
1337
+
1338
+ x1 = animat[0];
1339
+ y1 = animat[1];
1340
+ x2 = animat[2];
1341
+ y2 = animat[3];
1342
+
1343
+ Selection.animMode(true);
1344
+ var anim_timer;
1345
+
1346
+ function queueAnimator() {
1347
+ window.setTimeout(animator, interv);
1348
+ }
1349
+ var animator = (function () {
1350
+ return function () {
1351
+ pcent += (100 - pcent) / velocity;
1352
+
1353
+ animat[0] = Math.round(x1 + ((pcent / 100) * ix1));
1354
+ animat[1] = Math.round(y1 + ((pcent / 100) * iy1));
1355
+ animat[2] = Math.round(x2 + ((pcent / 100) * ix2));
1356
+ animat[3] = Math.round(y2 + ((pcent / 100) * iy2));
1357
+
1358
+ if (pcent >= 99.8) {
1359
+ pcent = 100;
1360
+ }
1361
+ if (pcent < 100) {
1362
+ setSelectRaw(animat);
1363
+ queueAnimator();
1364
+ } else {
1365
+ Selection.done();
1366
+ Selection.animMode(false);
1367
+ if (typeof(callback) === 'function') {
1368
+ callback.call(api);
1369
+ }
1370
+ }
1371
+ };
1372
+ }());
1373
+ queueAnimator();
1374
+ }
1375
+ //}}}
1376
+ function setSelect(rect) //{{{
1377
+ {
1378
+ setSelectRaw([rect[0] / xscale, rect[1] / yscale, rect[2] / xscale, rect[3] / yscale]);
1379
+ options.onSelect.call(api, unscale(Coords.getFixed()));
1380
+ Selection.enableHandles();
1381
+ }
1382
+ //}}}
1383
+ function setSelectRaw(l) //{{{
1384
+ {
1385
+ Coords.setPressed([l[0], l[1]]);
1386
+ Coords.setCurrent([l[2], l[3]]);
1387
+ Selection.update();
1388
+ }
1389
+ //}}}
1390
+ function tellSelect() //{{{
1391
+ {
1392
+ return unscale(Coords.getFixed());
1393
+ }
1394
+ //}}}
1395
+ function tellScaled() //{{{
1396
+ {
1397
+ return Coords.getFixed();
1398
+ }
1399
+ //}}}
1400
+ function setOptionsNew(opt) //{{{
1401
+ {
1402
+ setOptions(opt);
1403
+ interfaceUpdate();
1404
+ }
1405
+ //}}}
1406
+ function disableCrop() //{{{
1407
+ {
1408
+ options.disabled = true;
1409
+ Selection.disableHandles();
1410
+ Selection.setCursor('default');
1411
+ Tracker.setCursor('default');
1412
+ }
1413
+ //}}}
1414
+ function enableCrop() //{{{
1415
+ {
1416
+ options.disabled = false;
1417
+ interfaceUpdate();
1418
+ }
1419
+ //}}}
1420
+ function cancelCrop() //{{{
1421
+ {
1422
+ Selection.done();
1423
+ Tracker.activateHandlers(null, null);
1424
+ }
1425
+ //}}}
1426
+ function destroy() //{{{
1427
+ {
1428
+ $div.remove();
1429
+ $origimg.show();
1430
+ $origimg.css('visibility','visible');
1431
+ $(obj).removeData('Jcrop');
1432
+ }
1433
+ //}}}
1434
+ function setImage(src, callback) //{{{
1435
+ {
1436
+ Selection.release();
1437
+ disableCrop();
1438
+ var img = new Image();
1439
+ img.onload = function () {
1440
+ var iw = img.width;
1441
+ var ih = img.height;
1442
+ var bw = options.boxWidth;
1443
+ var bh = options.boxHeight;
1444
+ $img.width(iw).height(ih);
1445
+ $img.attr('src', src);
1446
+ $img2.attr('src', src);
1447
+ presize($img, bw, bh);
1448
+ boundx = $img.width();
1449
+ boundy = $img.height();
1450
+ $img2.width(boundx).height(boundy);
1451
+ $trk.width(boundx + (bound * 2)).height(boundy + (bound * 2));
1452
+ $div.width(boundx).height(boundy);
1453
+ Shade.resize(boundx,boundy);
1454
+ enableCrop();
1455
+
1456
+ if (typeof(callback) === 'function') {
1457
+ callback.call(api);
1458
+ }
1459
+ };
1460
+ img.src = src;
1461
+ }
1462
+ //}}}
1463
+ function colorChangeMacro($obj,color,now) {
1464
+ var mycolor = color || options.bgColor;
1465
+ if (options.bgFade && supportsColorFade() && options.fadeTime && !now) {
1466
+ $obj.animate({
1467
+ backgroundColor: mycolor
1468
+ }, {
1469
+ queue: false,
1470
+ duration: options.fadeTime
1471
+ });
1472
+ } else {
1473
+ $obj.css('backgroundColor', mycolor);
1474
+ }
1475
+ }
1476
+ function interfaceUpdate(alt) //{{{
1477
+ // This method tweaks the interface based on options object.
1478
+ // Called when options are changed and at end of initialization.
1479
+ {
1480
+ if (options.allowResize) {
1481
+ if (alt) {
1482
+ Selection.enableOnly();
1483
+ } else {
1484
+ Selection.enableHandles();
1485
+ }
1486
+ } else {
1487
+ Selection.disableHandles();
1488
+ }
1489
+
1490
+ Tracker.setCursor(options.allowSelect ? 'crosshair' : 'default');
1491
+ Selection.setCursor(options.allowMove ? 'move' : 'default');
1492
+
1493
+ if (options.hasOwnProperty('trueSize')) {
1494
+ xscale = options.trueSize[0] / boundx;
1495
+ yscale = options.trueSize[1] / boundy;
1496
+ }
1497
+
1498
+ if (options.hasOwnProperty('setSelect')) {
1499
+ setSelect(options.setSelect);
1500
+ Selection.done();
1501
+ delete(options.setSelect);
1502
+ }
1503
+
1504
+ Shade.refresh();
1505
+
1506
+ if (options.bgColor != bgcolor) {
1507
+ colorChangeMacro(
1508
+ options.shade? Shade.getShades(): $div,
1509
+ options.shade?
1510
+ (options.shadeColor || options.bgColor):
1511
+ options.bgColor
1512
+ );
1513
+ bgcolor = options.bgColor;
1514
+ }
1515
+
1516
+ if (bgopacity != options.bgOpacity) {
1517
+ bgopacity = options.bgOpacity;
1518
+ if (options.shade) Shade.refresh();
1519
+ else Selection.setBgOpacity(bgopacity);
1520
+ }
1521
+
1522
+ xlimit = options.maxSize[0] || 0;
1523
+ ylimit = options.maxSize[1] || 0;
1524
+ xmin = options.minSize[0] || 0;
1525
+ ymin = options.minSize[1] || 0;
1526
+
1527
+ if (options.hasOwnProperty('outerImage')) {
1528
+ $img.attr('src', options.outerImage);
1529
+ delete(options.outerImage);
1530
+ }
1531
+
1532
+ Selection.refresh();
1533
+ }
1534
+ //}}}
1535
+ //}}}
1536
+
1537
+ if (Touch.support) $trk.bind('touchstart.jcrop', Touch.newSelection);
1538
+
1539
+ $hdl_holder.hide();
1540
+ interfaceUpdate(true);
1541
+
1542
+ var api = {
1543
+ setImage: setImage,
1544
+ animateTo: animateTo,
1545
+ setSelect: setSelect,
1546
+ setOptions: setOptionsNew,
1547
+ tellSelect: tellSelect,
1548
+ tellScaled: tellScaled,
1549
+ setClass: setClass,
1550
+
1551
+ disable: disableCrop,
1552
+ enable: enableCrop,
1553
+ cancel: cancelCrop,
1554
+ release: Selection.release,
1555
+ destroy: destroy,
1556
+
1557
+ focus: KeyManager.watchKeys,
1558
+
1559
+ getBounds: function () {
1560
+ return [boundx * xscale, boundy * yscale];
1561
+ },
1562
+ getWidgetSize: function () {
1563
+ return [boundx, boundy];
1564
+ },
1565
+ getScaleFactor: function () {
1566
+ return [xscale, yscale];
1567
+ },
1568
+ getOptions: function() {
1569
+ // careful: internal values are returned
1570
+ return options;
1571
+ },
1572
+
1573
+ ui: {
1574
+ holder: $div,
1575
+ selection: $sel
1576
+ }
1577
+ };
1578
+
1579
+ if (is_msie) $div.bind('selectstart', function () { return false; });
1580
+
1581
+ $origimg.data('Jcrop', api);
1582
+ return api;
1583
+ };
1584
+ $.fn.Jcrop = function (options, callback) //{{{
1585
+ {
1586
+ var api;
1587
+ // Iterate over each object, attach Jcrop
1588
+ this.each(function () {
1589
+ // If we've already attached to this object
1590
+ if ($(this).data('Jcrop')) {
1591
+ // The API can be requested this way (undocumented)
1592
+ if (options === 'api') return $(this).data('Jcrop');
1593
+ // Otherwise, we just reset the options...
1594
+ else $(this).data('Jcrop').setOptions(options);
1595
+ }
1596
+ // If we haven't been attached, preload and attach
1597
+ else {
1598
+ if (this.tagName == 'IMG')
1599
+ $.Jcrop.Loader(this,function(){
1600
+ $(this).css({display:'block',visibility:'hidden'});
1601
+ api = $.Jcrop(this, options);
1602
+ if ($.isFunction(callback)) callback.call(api);
1603
+ });
1604
+ else {
1605
+ $(this).css({display:'block',visibility:'hidden'});
1606
+ api = $.Jcrop(this, options);
1607
+ if ($.isFunction(callback)) callback.call(api);
1608
+ }
1609
+ }
1610
+ });
1611
+
1612
+ // Return "this" so the object is chainable (jQuery-style)
1613
+ return this;
1614
+ };
1615
+ //}}}
1616
+ // $.Jcrop.Loader - basic image loader {{{
1617
+
1618
+ $.Jcrop.Loader = function(imgobj,success,error){
1619
+ var $img = $(imgobj), img = $img[0];
1620
+
1621
+ function completeCheck(){
1622
+ if (img.complete) {
1623
+ $img.unbind('.jcloader');
1624
+ if ($.isFunction(success)) success.call(img);
1625
+ }
1626
+ else window.setTimeout(completeCheck,50);
1627
+ }
1628
+
1629
+ $img
1630
+ .bind('load.jcloader',completeCheck)
1631
+ .bind('error.jcloader',function(e){
1632
+ $img.unbind('.jcloader');
1633
+ if ($.isFunction(error)) error.call(img);
1634
+ });
1635
+
1636
+ if (img.complete && $.isFunction(success)){
1637
+ $img.unbind('.jcloader');
1638
+ success.call(img);
1639
+ }
1640
+ };
1641
+
1642
+ //}}}
1643
+ // Global Defaults {{{
1644
+ $.Jcrop.defaults = {
1645
+
1646
+ // Basic Settings
1647
+ allowSelect: true,
1648
+ allowMove: true,
1649
+ allowResize: true,
1650
+
1651
+ trackDocument: true,
1652
+
1653
+ // Styling Options
1654
+ baseClass: 'jcrop',
1655
+ addClass: null,
1656
+ bgColor: 'black',
1657
+ bgOpacity: 0.6,
1658
+ bgFade: false,
1659
+ borderOpacity: 0.4,
1660
+ handleOpacity: 0.5,
1661
+ handleSize: null,
1662
+
1663
+ aspectRatio: 0,
1664
+ keySupport: true,
1665
+ createHandles: ['n','s','e','w','nw','ne','se','sw'],
1666
+ createDragbars: ['n','s','e','w'],
1667
+ createBorders: ['n','s','e','w'],
1668
+ drawBorders: true,
1669
+ dragEdges: true,
1670
+ fixedSupport: true,
1671
+ touchSupport: null,
1672
+
1673
+ shade: null,
1674
+
1675
+ boxWidth: 0,
1676
+ boxHeight: 0,
1677
+ boundary: 2,
1678
+ fadeTime: 400,
1679
+ animationDelay: 20,
1680
+ swingSpeed: 3,
1681
+
1682
+ minSelect: [0, 0],
1683
+ maxSize: [0, 0],
1684
+ minSize: [0, 0],
1685
+
1686
+ // Callbacks / Event Handlers
1687
+ onChange: function () {},
1688
+ onSelect: function () {},
1689
+ onDblClick: function () {},
1690
+ onRelease: function () {}
1691
+ };
1692
+
1693
+ // }}}
1694
+ }(jQuery));