Crop, zoom and rotate image using carrierwave
We already have a good reading about cropping in rails using jcrop from ryanbates railscast (http://railscasts.com/episodes/182-cropping-images?view=asciicast). But in my recent rails project I need functionality to rotate and crop zoomed image. So I made following changes that might help others.
## My original image :-
## My original image :-
First of all we need carrierwave to upload images and mini_magick to manipulate images. So add following gems in your Gemfile.
##/Gemfile
gem 'carrierwave'
gem 'mini_magick'
To crop, zoom and rotate image we need to add 'cropzoom' file and 'custom.min' file in assets. Find here https://github.com/cropzoom/cropzoom
Following assetes changes required :
##/app/assets/javascripts/application.js
//= require jquery.cropzoom
//= require jquery-ui-1.10.3
##/app/assets/stylesheets/application.css
*= require jquery-ui-1.10.3.custom.min
*= require jquery.cropzoom
Now, to get coordinates of crop, zoom or rotated image we need to set attr_accessor for :
Cropped image coordinates : crop_x, crop_y, crop_w, crop_h
Zoomed image coordinates : zoom_x, zoom_y, zoom_w, zoom_h
Dragged image coordinates : drag_x, drag_y
Rotation angle : rotation_angle
So, my model looks like this :-
/app/models/user.rb
class User < ActiveRecord::Base
mount_uploader :image, ImageUploader
attr_accessor :crop_x, :crop_y, :crop_w, :crop_h, :rotation_angle, :zoom_w, :zoom_h, :zoom_x, :zoom_y, :drag_x, :drag_y
Changes required in image crop view.
##/app/views/users/crop.html.erb
# Hide the original image.
<%= image_tag @user.image.url, class: 'img-responsive hide_img' %>
# Container where we will crop, zoom and rotate image
<div id='crop_container'></div>
<% form_for @user do |f| %>
<% for attribute in [:zoom_w, :zoom_h, :zoom_x, :zoom_y, :drag_x, :drag_y, :rotation_angle, :crop_x, :crop_y, :crop_w, :crop_h] %>
<%= f.hidden_field attribute, :id => attribute %>
<% end %>
<p><%= f.submit "Crop" %></p>
<% end %>
# Initialize cropzoom container
<script>
$(document).ready(function() {
var cropzoom = $('#crop_container').cropzoom({
width:300,
height:300,
bgColor: '#CCC',
enableRotation:true,
enableZoom:true,
zoomSteps:10,
rotationSteps:10,
selector:{
centered:false,
startWithOverlay: true,
borderColor:'blue',
borderColorHover:'yellow'
},
image:{
source:$('.img-responsive').attr('src'),
width:768,
height:768,
minZoom: 20,
onRotate: function(imageObject, rotate_angle){
// Get rotatation angle
$("#rotation_angle").val(rotate_angle);
},
onZoom: function(imageObject, dimensions){
// Get zoom coordinates
$('#zoom_w').val(dimensions.w);
$('#zoom_h').val(dimensions.h);
$('#zoom_x').val(dimensions.posX);
$('#zoom_y').val(dimensions.posY);
// Set drag coordinates to zero when image is zoomed
$('#drag_x').val(0);
$('#drag_y').val(0);
},
onImageDrag: function(imageObject, position){
// Get dragged image coordinates
$('#drag_x').val(position.posX);
$('#drag_y').val(position.posY);
// Set zoom x-y coordinates to zero when image is dragged
$('#zoom_x').val(0);
$('#zoom_y').val(0);
}
}
});
});
// Get cropped image coordinates
$(document).on('click', 'input[type="submit"]', function() {
var get_html = $('#infoSelector').html()
var get_array = get_html.split('|')
var get_x_y_coords_array = get_array[0].split('-')
var get_w_h_coords_array = get_array[1].split('-')
var get_x_coord = get_x_y_coords_array[0].split(':')[1]
var get_y_coord = get_x_y_coords_array[1].split(':')[1]
var get_w_coord = get_w_h_coords_array[0].split(':')[1]
var get_h_coord = get_w_h_coords_array[1].split(':')[1]
$('#crop_x').val(get_x_coord);
$('#crop_y').val(get_y_coord);
$('#crop_w').val(get_w_coord);
$('#crop_h').val(get_h_coord);
});
</script>
Crop view looks like this :-
Note :- In documentation of cropzoom plugin 'onImageDrag()' shuold have two values, but it have only one. Line no. 182 in my case
So, We need to change in jquery.cropzoom.js.
##/app/assets/javascripts/jquery.cropzoom.js
Change in $($image).draggable() function.
Change :-
if ($options.image.onImageDrag != null)
$options.image.onImageDrag($image);
To :-
if ($options.image.onImageDrag != null)
$options.image.onImageDrag($image, getData('image'));
Required controller changes.
##/app/controllers/users_controller.rb
@user = User.find(params[:id])
#If we get image from remotely
@user.remote_image_url = User.first.image
if @user.update_attributes(params[:user])
if params[:user][:crop_x].present?
@user.image = @user.image.resize_and_crop
@user.save!
@user.image.recreate_versions!
end
end
##/app/assets/stylesheets/custom.css
// To hide image
.hide_img {
display: none;
}
Now, finally changes are required in carrierwave uploader.
##/app/uploaders/image_uploader.rb
include CarrierWave::MiniMagick
version :custom_crop do
process :resize_and_crop
end
def resize_and_crop
if model.class.to_s == "User"
if model.crop_x.present?
manipulate! do |img|
w = model.crop_w.to_i
h = model.crop_h.to_i
// Set x-y coordinates of cropped image.
x = model.zoom_x.to_i >= 0 ? (model.crop_x.to_i - model.zoom_x.to_i) : (model.zoom_x.to_i.abs + model.crop_x.to_i)
y = model.zoom_y.to_i >= 0 ? (model.crop_y.to_i - model.zoom_y.to_i) : (model.zoom_y.to_i.abs + model.crop_y.to_i)
x = model.drag_x.to_i >= 0 ? (x - model.drag_x.to_i) : (model.drag_x.to_i.to_i.abs + x)
y = model.drag_y.to_i >= 0 ? (y - model.drag_y.to_i) : (model.drag_y.to_i.to_i.abs + y)
img.combine_options do |i|
// First we need to resize image with zoomed image. For more details you can find here "https://github.com/minimagick/minimagick"
i.resize "#{model.zoom_w.to_i}x#{model.zoom_h.to_i}+#{model.zoom_x.to_i}+#{model.zoom_y.to_i}^\!"
// Rotate zoomed image
i.rotate(model.rotation_angle.to_i)
// Crop zoomed and rotated image
i.crop "#{w}x#{h}+#{x}+#{y}"
end
img
end
end
end
end
## Final output is :-