├── .gitignore ├── .rspec ├── .travis.yml ├── CHANGELOG.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── carrierwave-crop.gemspec ├── lib ├── carrierwave │ ├── crop.rb │ └── crop │ │ ├── all.rb │ │ ├── engine.rb │ │ ├── error.rb │ │ ├── helpers.rb │ │ ├── model_additions.rb │ │ ├── railtie.rb │ │ └── version.rb └── generators │ ├── cropper_generator.rb │ └── templates │ └── carrierwave_cropper.js.coffee ├── spec ├── crop │ └── model_additions_spec.rb ├── crop_spec.rb └── spec_helper.rb └── vendor └── assets ├── images └── Jcrop.gif ├── javascripts ├── .keep └── jquery.jcrop.js └── stylesheets ├── .keep └── jquery.jcrop.css /.gitignore: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format documentation -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | rvm: 2 | - 1.9.3 3 | - 2.0.0 4 | - 2.1.1 5 | - ruby-head 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## CarrierWave-Crop v0.1.0 2 | * Initial commit 3 | * Supports processors `rmagick`. 4 | * `process crop: :avatar` resizes the original image to `600x600` and performs cropping on it. 5 | 6 | ## CarrierWave-Crop v0.1.1 7 | * Supports processors `rmagick` and `mini-magick`. 8 | * Changed module name `Carrierwave` to `CarrierWave` in sync with `CarrierWave` gem. 9 | * Deprecated `cropped_preview` form helper. Replaced it with `previewbox`. 10 | * Supports cropping of ONE attachment per model. Can be directly applied on original attachment if no versions exists. 11 | 12 | process crop: :avatar 13 | 14 | * Supports cropping of MULTIPLE versions of one attachment per model. 15 | * Updated the cropping coffeescript (created by rails generator) to incorporate the changes in form helpers. 16 | * Added `version`, `width` and `height` options to form helpers `cropbox` and `previewbox`. 17 | 18 | ##To render a specific version for cropping pass `version` option. 19 | <%= f.cropbox :avatar , version: :medium %> 20 | <%= f.peviewbox :avatar , version: :medium %> ## Pass the same version as specified in cropbox 21 | 22 | ## If you override ONE of width/height you MUST override both, otherwise passed option would be ignored. 23 | <%= f.cropbox :avatar, width: 550, height: 600 %> 24 | <%= f.previewbox :avatar, width: 200, height: 200 %> 25 | 26 | * Added `width` and `height` arguments to proccessor method `crop`. 27 | 28 | ## If ONLY "thumb" version is to be cropped 29 | version :jumbo do 30 | resize_to_limit(600,600) 31 | end 32 | version :thumb do 33 | process crop: :avatar ## Crops this version based on original image 34 | resize_to_limit(100,100) 35 | end 36 | 37 | ## With width and height 38 | ## If ONLY "thumb" version is to be cropped 39 | version :jumbo do 40 | resize_to_limit(600,600) 41 | end 42 | version :thumb do 43 | ## To crop this version based on `jumbo` version, pass width = 600 and height = 600 44 | ## Specify both width and height values otherwise they would be ignored 45 | process crop: [:avatar, 600, 600] 46 | resize_to_limit(100,100) 47 | end 48 | 49 | ## CarrierWave-Crop v0.1.1 50 | * Fixes Issue #1: Colons in html `id` attribute for Namespaced Models -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in carrierwave-crop.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 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. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 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-crop 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.previewbox :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 ## Crops this version based on original image 102 | resize_to_limit(100,100) 103 | end 104 | 105 | If there are no versions, and original file is to be cropped directly then call the process directly within `AvatarUploader`, 106 | 107 | process crop: :avatar 108 | 109 | ##NOTES 110 | 111 | 1. Current Documentation is for **CarrierWave-Crop v0.1.2** 112 | 2. Supports processors `rmagick` and `mini-magick`. 113 | 114 | **To use `rmagick`, add it in your `Gemfile` as:** 115 | 116 | gem 'rmagick', :require => 'RMagick' ## Specify appropriate version, if needed 117 | 118 | Run `bundle` 119 | 120 | Include it in your CarrierWave Uploader. For example: 121 | 122 | class AvatarUploader < CarrierWave::Uploader::Base 123 | include CarrierWave::RMagick 124 | ## ... 125 | end 126 | 127 | **To use `mini_magick`, add it in your `Gemfile` as:** 128 | 129 | gem 'mini_magick' ## Specify appropriate version, if needed 130 | 131 | Run `bundle` 132 | 133 | Include it in your CarrierWave Uploader. For example: 134 | 135 | class AvatarUploader < CarrierWave::Uploader::Base 136 | include CarrierWave::MiniMagick 137 | ## ... 138 | end 139 | 140 | 3. Supports cropping of ONE attachment per model. Can be directly applied on original attachment if no versions exists. 141 | 142 | process crop: :avatar 143 | 144 | 4. Supports cropping of MULTIPLE versions of one attachment per model. 145 | 5. In form helpers, by default *original image* is rendered. To render a specific version for cropping pass `version` option. For example: 146 | 147 | <%= f.cropbox :avatar , version: :medium %> 148 | <%= f.previewbox :avatar , version: :medium %> ## Pass the same version as specified in cropbox 149 | 150 | 6. By default `previewbox` has `100x100` dimensions and `cropbox` defaults to the target geometry for the version. 151 | `width` and `height` can be specified for these form helpers. BUT If you override ONE of width/height you MUST override both, otherwise passed option would be ignored. 152 | 153 | <%= f.cropbox :avatar, width: 550, height: 600 %> 154 | <%= f.previewbox :avatar, width: 200, height: 200 %> 155 | 156 | 7. By default, `process crop: :avatar` will create the cropped version based on original image. 157 | To resize the image to a particular dimension before cropping, pass two more arguments `width` and `height`. 158 | 159 | ## If ONLY "thumb" version is to be cropped 160 | version :jumbo do 161 | resize_to_limit(600,600) 162 | end 163 | 164 | version :thumb do 165 | ## To crop this version based on `jumbo` version, pass width = 600 and height = 600 166 | ## Specify both width and height values otherwise they would be ignored 167 | process crop: [:avatar, 600, 600] 168 | resize_to_limit(100,100) 169 | end 170 | 171 | ### Credits and resources 172 | * [CarrierWave](https://github.com/carrierwaveuploader/carrierwave) 173 | * [Deep Liquid's JCrop](http://deepliquid.com/content/Jcrop.html) 174 | * And Ryan Bates [Railscast#182](http://railscasts.com/episodes/182-cropping-images/) 175 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rspec/core/rake_task" 3 | 4 | RSpec::Core::RakeTask.new(:spec) 5 | 6 | task default: :spec 7 | -------------------------------------------------------------------------------- /carrierwave-crop.gemspec: -------------------------------------------------------------------------------- 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 = ["Kirti Thorat"] 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.2" 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 29 | -------------------------------------------------------------------------------- /lib/carrierwave/crop.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'carrierwave/crop/all' 4 | -------------------------------------------------------------------------------- /lib/carrierwave/crop/all.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require "carrierwave/crop/version" 4 | require "carrierwave/crop/error" 5 | 6 | if defined? Rails 7 | require "carrierwave/crop/engine" 8 | require 'carrierwave/crop/helpers' 9 | require "carrierwave/crop/model_additions" 10 | require "carrierwave/crop/railtie" 11 | end 12 | -------------------------------------------------------------------------------- /lib/carrierwave/crop/engine.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module CarrierWave 4 | module Crop 5 | class Engine < ::Rails::Engine 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/carrierwave/crop/error.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module CarrierWave 4 | module Crop 5 | class MissingProcessorError < StandardError; end 6 | class ProcessingError < StandardError; end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/carrierwave/crop/helpers.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module CarrierWave 4 | module Crop 5 | module Helpers 6 | 7 | # Form helper to render the preview of a cropped attachment. 8 | # Loads the actual image. Previewbox has no constraints on dimensions, image is renedred at full size. 9 | # By default box size is 100x100. Size can be customized by setting the :width and :height option. 10 | # If you override one of width/height you must override both. 11 | # By default original image is rendered. Specific version can be specified by passing version option 12 | # 13 | # previewbox :avatar 14 | # previewbox :avatar, width: 200, height: 200 15 | # previewbox :avatar, version: :medium 16 | # 17 | # @param attachment [Symbol] attachment name 18 | # @param opts [Hash] specify version or width and height options 19 | def previewbox(attachment, opts = {}) 20 | attachment = attachment.to_sym 21 | 22 | if(self.object.send(attachment).class.ancestors.include? CarrierWave::Uploader::Base ) 23 | ## Fixes Issue #1 : Colons in html id attributes with Namespaced Models 24 | model_name = self.object.class.name.downcase.split("::").last 25 | width, height = 100, 100 26 | if(opts[:width] && opts[:height]) 27 | width, height = opts[:width].round, opts[:height].round 28 | end 29 | wrapper_attributes = {id: "#{model_name}_#{attachment}_previewbox_wrapper", style: "width:#{width}px; height:#{height}px; overflow:hidden"} 30 | if opts[:version] 31 | img = self.object.send(attachment).url(opts[:version]) 32 | else 33 | img = self.object.send(attachment).url 34 | end 35 | preview_image = @template.image_tag(img, id: "#{model_name}_#{attachment}_previewbox") 36 | @template.content_tag(:div, preview_image, wrapper_attributes) 37 | end 38 | end 39 | 40 | # Form helper to render the actual cropping box of an attachment. 41 | # Loads the actual image. Cropbox has no constraints on dimensions, image is renedred at full size. 42 | # Box size can be restricted by setting the :width and :height option. If you override one of width/height you must override both. 43 | # By default original image is rendered. Specific version can be specified by passing version option 44 | # 45 | # cropbox :avatar 46 | # cropbox :avatar, width: 550, height: 600 47 | # cropbox :avatar, version: :medium 48 | # 49 | # @param attachment [Symbol] attachment name 50 | # @param opts [Hash] specify version or width and height options 51 | def cropbox(attachment, opts={}) 52 | attachment = attachment.to_sym 53 | attachment_instance = self.object.send(attachment) 54 | 55 | if(attachment_instance.class.ancestors.include? CarrierWave::Uploader::Base ) 56 | ## Fixes Issue #1 : Colons in html id attributes with Namespaced Models 57 | model_name = self.object.class.name.downcase.split("::").last 58 | hidden_elements = self.hidden_field(:"#{attachment}_crop_x", id: "#{model_name}_#{attachment}_crop_x") 59 | [:crop_y, :crop_w, :crop_h].each do |attribute| 60 | hidden_elements << self.hidden_field(:"#{attachment}_#{attribute}", id: "#{model_name}_#{attachment}_#{attribute}") 61 | end 62 | 63 | box = @template.content_tag(:div, hidden_elements, style: "display:none") 64 | 65 | wrapper_attributes = {id: "#{model_name}_#{attachment}_cropbox_wrapper"} 66 | if(opts[:width] && opts[:height]) 67 | wrapper_attributes.merge!(style: "width:#{opts[:width].round}px; height:#{opts[:height].round}px; overflow:hidden") 68 | end 69 | 70 | if opts[:version] 71 | img = self.object.send(attachment).url(opts[:version]) 72 | else 73 | img = self.object.send(attachment).url 74 | end 75 | crop_image = @template.image_tag(img, :id => "#{model_name}_#{attachment}_cropbox") 76 | box << @template.content_tag(:div, crop_image, wrapper_attributes) 77 | end 78 | end 79 | end 80 | end 81 | end 82 | 83 | if defined? ActionView::Helpers::FormBuilder 84 | ActionView::Helpers::FormBuilder.class_eval do 85 | include CarrierWave::Crop::Helpers 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /lib/carrierwave/crop/model_additions.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module CarrierWave 4 | module Crop 5 | module ModelAdditions 6 | module ClassMethods 7 | 8 | # Adds cropping feature on a specific attribute of your model 9 | # 10 | # crop_uploaded :avatar 11 | # 12 | # @param attachment [Symbol] Name of the attachment attribute to be cropped 13 | def crop_uploaded(attachment) 14 | 15 | [:crop_x, :crop_y, :crop_w, :crop_h].each do |a| 16 | attr_accessor :"#{attachment}_#{a}" 17 | end 18 | after_update :"recreate_#{attachment}_versions" 19 | 20 | end 21 | 22 | end ## End of ClassMethods 23 | 24 | module InstanceMethods 25 | 26 | # Checks if the attachment received cropping attributes 27 | # @param attachment [Symbol] Name of the attribute to be croppedv 28 | # 29 | # @return [Boolean] 30 | def cropping?(attachment) 31 | !self.send(:"#{attachment}_crop_x").blank? && 32 | !self.send(:"#{attachment}_crop_y").blank? && 33 | !self.send(:"#{attachment}_crop_w").blank? && 34 | !self.send(:"#{attachment}_crop_h").blank? 35 | end 36 | 37 | # method_missing is used to respond to the model callback 38 | def method_missing(method, *args) 39 | if method.to_s =~ /recreate_(\S{1,})_versions/ 40 | crop_image(method.to_s.scan(/recreate_(\S{1,})_versions/).flatten.first.to_sym) 41 | else 42 | super 43 | end 44 | end 45 | 46 | # Saves the attachment if the crop attributes are present 47 | # @param attachment [Symbol] Name of the attribute to be cropped 48 | def crop_image(attachment) 49 | if cropping?(attachment) 50 | attachment_instance = send(attachment) 51 | attachment_instance.recreate_versions! 52 | end 53 | end 54 | 55 | end ## End of InstanceMethods 56 | 57 | end ## End of ModelAdditions 58 | 59 | module Uploader 60 | 61 | # Performs cropping. 62 | # 63 | # On original version of attachment 64 | # process crop: :avatar 65 | # 66 | # Resizes the original image to 600x600 and then performs cropping 67 | # process crop: [:avatar, 600, 600] 68 | # 69 | # @param attachment [Symbol] Name of the attachment attribute to be cropped 70 | def crop(attachment, width = nil, height = nil) 71 | if model.cropping?(attachment) 72 | x = model.send("#{attachment}_crop_x").to_i 73 | y = model.send("#{attachment}_crop_y").to_i 74 | w = model.send("#{attachment}_crop_w").to_i 75 | h = model.send("#{attachment}_crop_h").to_i 76 | attachment_instance = model.send(attachment) 77 | 78 | if self.respond_to? "resize_to_limit" 79 | 80 | begin 81 | if width && height 82 | resize_to_limit(width, height) 83 | end 84 | manipulate! do |img| 85 | if attachment_instance.kind_of? CarrierWave::RMagick 86 | img.crop!(x, y, w, h) 87 | elsif attachment_instance.kind_of? CarrierWave::MiniMagick 88 | img.crop("#{w}x#{h}+#{x}+#{y}") 89 | img 90 | end 91 | end 92 | 93 | rescue Exception => e 94 | raise CarrierWave::Crop::ProcessingError, "Failed to crop - #{e.message}" 95 | end 96 | 97 | else 98 | raise CarrierWave::Crop::MissingProcessorError, "Failed to crop #{attachment}. Add rmagick or mini_magick." 99 | end 100 | end 101 | end 102 | 103 | end ## End of Uploader 104 | end ## End of Crop 105 | end ## End of CarrierWave 106 | 107 | 108 | if defined? CarrierWave::Uploader::Base 109 | CarrierWave::Uploader::Base.class_eval do 110 | include CarrierWave::Crop::Uploader 111 | end 112 | end 113 | -------------------------------------------------------------------------------- /lib/carrierwave/crop/railtie.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module CarrierWave 4 | module Crop 5 | class Railtie < Rails::Railtie 6 | initializer 'carrierwave.crop' do 7 | ActiveSupport.on_load :active_record do 8 | extend ModelAdditions::ClassMethods 9 | include ModelAdditions::InstanceMethods 10 | end 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/carrierwave/crop/version.rb: -------------------------------------------------------------------------------- 1 | module CarrierWave 2 | module Crop 3 | VERSION = "0.1.2" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/generators/cropper_generator.rb: -------------------------------------------------------------------------------- 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 -------------------------------------------------------------------------------- /lib/generators/templates/carrierwave_cropper.js.coffee: -------------------------------------------------------------------------------- 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 | $('#<%= file_name %>_<%= attachment_name %>_previewbox').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' 25 | -------------------------------------------------------------------------------- /spec/crop/model_additions_spec.rb: -------------------------------------------------------------------------------- 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 13 | -------------------------------------------------------------------------------- /spec/crop_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'carrierwave/crop' 2 | require 'carrierwave/crop/model_additions' 3 | -------------------------------------------------------------------------------- /vendor/assets/images/Jcrop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kirtithorat/carrierwave-crop/22e183b21125ec2ceefb7ee4d551f04fd934a8fa/vendor/assets/images/Jcrop.gif -------------------------------------------------------------------------------- /vendor/assets/javascripts/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kirtithorat/carrierwave-crop/22e183b21125ec2ceefb7ee4d551f04fd934a8fa/vendor/assets/javascripts/.keep -------------------------------------------------------------------------------- /vendor/assets/javascripts/jquery.jcrop.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jquery.Jcrop.js v0.9.12 3 | * jQuery Image Cropping Plugin - released under MIT License 4 | * Author: Kelly Hallman 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 = $('
').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 = $('
').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 = $('
'), 342 | 343 | $img_holder = $('
') 344 | .width('100%').height('100%').css({ 345 | zIndex: 310, 346 | position: 'absolute', 347 | overflow: 'hidden' 348 | }), 349 | 350 | $hdl_holder = $('
') 351 | .width('100%').height('100%').css('zIndex', 320), 352 | 353 | $sel = $('
') 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 = $('') 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 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 = $('
').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 $('
').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 = $('
').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 = $('
').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 = $('').css({ 1224 | position: 'fixed', 1225 | left: '-120px', 1226 | width: '12px' 1227 | }).addClass('jcrop-keymgr'), 1228 | 1229 | $keywrap = $('
').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)); 1695 | -------------------------------------------------------------------------------- /vendor/assets/stylesheets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kirtithorat/carrierwave-crop/22e183b21125ec2ceefb7ee4d551f04fd934a8fa/vendor/assets/stylesheets/.keep -------------------------------------------------------------------------------- /vendor/assets/stylesheets/jquery.jcrop.css: -------------------------------------------------------------------------------- 1 | /* jquery.Jcrop.css v0.9.12 - MIT License */ 2 | /* 3 | The outer-most container in a typical Jcrop instance 4 | If you are having difficulty with formatting related to styles 5 | on a parent element, place any fixes here or in a like selector 6 | 7 | You can also style this element if you want to add a border, etc 8 | A better method for styling can be seen below with .jcrop-light 9 | (Add a class to the holder and style elements for that extended class) 10 | */ 11 | .jcrop-holder { 12 | direction: ltr; 13 | text-align: left; 14 | } 15 | /* Selection Border */ 16 | .jcrop-vline, 17 | .jcrop-hline { 18 | background: #ffffff url("Jcrop.gif"); 19 | font-size: 0; 20 | position: absolute; 21 | } 22 | .jcrop-vline { 23 | height: 100%; 24 | width: 1px !important; 25 | } 26 | .jcrop-vline.right { 27 | right: 0; 28 | } 29 | .jcrop-hline { 30 | height: 1px !important; 31 | width: 100%; 32 | } 33 | .jcrop-hline.bottom { 34 | bottom: 0; 35 | } 36 | /* Invisible click targets */ 37 | .jcrop-tracker { 38 | height: 100%; 39 | width: 100%; 40 | /* "turn off" link highlight */ 41 | -webkit-tap-highlight-color: transparent; 42 | /* disable callout, image save panel */ 43 | -webkit-touch-callout: none; 44 | /* disable cut copy paste */ 45 | -webkit-user-select: none; 46 | } 47 | /* Selection Handles */ 48 | .jcrop-handle { 49 | background-color: #333333; 50 | border: 1px #eeeeee solid; 51 | width: 7px; 52 | height: 7px; 53 | font-size: 1px; 54 | } 55 | .jcrop-handle.ord-n { 56 | left: 50%; 57 | margin-left: -4px; 58 | margin-top: -4px; 59 | top: 0; 60 | } 61 | .jcrop-handle.ord-s { 62 | bottom: 0; 63 | left: 50%; 64 | margin-bottom: -4px; 65 | margin-left: -4px; 66 | } 67 | .jcrop-handle.ord-e { 68 | margin-right: -4px; 69 | margin-top: -4px; 70 | right: 0; 71 | top: 50%; 72 | } 73 | .jcrop-handle.ord-w { 74 | left: 0; 75 | margin-left: -4px; 76 | margin-top: -4px; 77 | top: 50%; 78 | } 79 | .jcrop-handle.ord-nw { 80 | left: 0; 81 | margin-left: -4px; 82 | margin-top: -4px; 83 | top: 0; 84 | } 85 | .jcrop-handle.ord-ne { 86 | margin-right: -4px; 87 | margin-top: -4px; 88 | right: 0; 89 | top: 0; 90 | } 91 | .jcrop-handle.ord-se { 92 | bottom: 0; 93 | margin-bottom: -4px; 94 | margin-right: -4px; 95 | right: 0; 96 | } 97 | .jcrop-handle.ord-sw { 98 | bottom: 0; 99 | left: 0; 100 | margin-bottom: -4px; 101 | margin-left: -4px; 102 | } 103 | /* Dragbars */ 104 | .jcrop-dragbar.ord-n, 105 | .jcrop-dragbar.ord-s { 106 | height: 7px; 107 | width: 100%; 108 | } 109 | .jcrop-dragbar.ord-e, 110 | .jcrop-dragbar.ord-w { 111 | height: 100%; 112 | width: 7px; 113 | } 114 | .jcrop-dragbar.ord-n { 115 | margin-top: -4px; 116 | } 117 | .jcrop-dragbar.ord-s { 118 | bottom: 0; 119 | margin-bottom: -4px; 120 | } 121 | .jcrop-dragbar.ord-e { 122 | margin-right: -4px; 123 | right: 0; 124 | } 125 | .jcrop-dragbar.ord-w { 126 | margin-left: -4px; 127 | } 128 | /* The "jcrop-light" class/extension */ 129 | .jcrop-light .jcrop-vline, 130 | .jcrop-light .jcrop-hline { 131 | background: #ffffff; 132 | filter: alpha(opacity=70) !important; 133 | opacity: .70!important; 134 | } 135 | .jcrop-light .jcrop-handle { 136 | -moz-border-radius: 3px; 137 | -webkit-border-radius: 3px; 138 | background-color: #000000; 139 | border-color: #ffffff; 140 | border-radius: 3px; 141 | } 142 | /* The "jcrop-dark" class/extension */ 143 | .jcrop-dark .jcrop-vline, 144 | .jcrop-dark .jcrop-hline { 145 | background: #000000; 146 | filter: alpha(opacity=70) !important; 147 | opacity: 0.7 !important; 148 | } 149 | .jcrop-dark .jcrop-handle { 150 | -moz-border-radius: 3px; 151 | -webkit-border-radius: 3px; 152 | background-color: #ffffff; 153 | border-color: #000000; 154 | border-radius: 3px; 155 | } 156 | /* Simple macro to turn off the antlines */ 157 | .solid-line .jcrop-vline, 158 | .solid-line .jcrop-hline { 159 | background: #ffffff; 160 | } 161 | /* Fix for twitter bootstrap et al. */ 162 | .jcrop-holder img, 163 | img.jcrop-preview { 164 | max-width: none; 165 | } 166 | --------------------------------------------------------------------------------