Skip to content

Instantly share code, notes, and snippets.

@gilcierweb
Last active March 20, 2021 12:49
Show Gist options
  • Save gilcierweb/9905e150919aea6f9f142c90fc1ced5e to your computer and use it in GitHub Desktop.
Save gilcierweb/9905e150919aea6f9f142c90fc1ced5e to your computer and use it in GitHub Desktop.
Upload image crop with Ruby and Rails, CarrierWave and Cropper.js
#app/views/businesses/_form.html.erb
<%= form_with(model: business, scope: :business, local: true, :html => {multipart: true}) do |form| %>
<div class="row">
<div class="col-md-12">
<%= form.file_field :logo_image, id: :business_logo_image %>
<%= form.label :logo_image, class: 'upload' do %>
<i class="material-icons">file_upload</i>
<span>Choose image</span>
<% end %>
</div>
<% %w[x y w h].each do |attribute| %>
<%= form.hidden_field "logo_crop_#{attribute}", id:"logo_crop_#{attribute}" %>
<% end %>
</div>
<style>
img {
max-width: 100%;
}
</style>
<!-- Modal -->
<div class="modal fade" id="upload-modal" aria-labelledby="modalLabel" role="dialog" tabindex="-1">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modalLabel">Cut logo</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div>
<img id="image" width="100%" src="" alt="Logo">
</div>
<p class="text-center">
<button type="button" class="btn btn-primary rotate" data-method="rotate" data-option="-30">
<i class="fa fa-undo"></i></button>
<button type="button" class="btn btn-primary rotate" data-method="rotate" data-option="30">
<i class="fa fa-repeat"></i></button>
</p>
<div id="result"></div>
<div id="preview"></div>
<div>
<canvas id="canvas">
Your browser does not support the HTML5 canvas element.
</canvas>
</div>
</div>
<div class="modal-footer">
<input type="button" id="btnCrop" value="Crop" />
<input type="button" id="btnRestore" value="Restore" />
<input type="button" class="btn btn-primary" id="button" value="Cut" data-dismiss="modal"></button>
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<%= form.submit t('form.submit'), class: 'btn btn-primary' %>
<% end %>
#app/models/business.rb
class Business < ApplicationRecord
mount_uploader :logo_image, LogoImageUploader
end
#app/controllers/businesses_controller.rb
class BusinessesController < ApplicationController
before_action :set_business, only: [:show, :edit, :update, :destroy]
before_action :sanitize_fields_params, only: [:create, :update]
...
private
def sanitize_fields_params
$logo_crop_x = 0
$logo_crop_y = 0
$logo_crop_w = 0
$logo_crop_h = 0
$logo_crop_x = params[:business][:logo_crop_x]
$logo_crop_y = params[:business][:logo_crop_y]
$logo_crop_w = params[:business][:logo_crop_w]
$logo_crop_h = params[:business][:logo_crop_h]
end
def business_params
params.require(:business).permit( :name, :logo_image, :logo_crop_x, :logo_crop_y, :logo_crop_w, :logo_crop_h)
end
end
// #app/assets/javascripts/cropper_custom.js
// https://fengyuanchen.github.io/cropperjs/
$(function () {
function crop_image_load(data) {
var $crop_x = $("input#logo_crop_x"),
$crop_y = $("input#logo_crop_y"),
$crop_w = $("input#logo_crop_w"),
$crop_h = $("input#logo_crop_h");
$crop_x.val('');
$crop_y.val('');
$crop_h.val('');
$crop_w.val('');
$crop_x.val(accounting.toFixed(data.x, 6));
$crop_y.val(accounting.toFixed(data.y, 6));
$crop_h.val(accounting.toFixed(data.height, 6));
$crop_w.val(accounting.toFixed(data.width, 6));
// $crop_x.val(Math.round(data.x));
// $crop_y.val(Math.round(data.y));
// $crop_h.val(Math.round(data.height));
// $crop_w.val(Math.round(data.width));
}
var $crop_x = $("input#logo_crop_x"),
$crop_y = $("input#logo_crop_y"),
$crop_w = $("input#logo_crop_w"),
$crop_h = $("input#logo_crop_h");
$crop_x.val('');
$crop_y.val('');
$crop_h.val('');
$crop_w.val('');
var $image = $('#image');
var $button = $('#button');
var $result = $('#result');
var croppable = false;
var cropBoxData;
var canvasData;
var img = new Image();
var img_tag = $('#business_logo_image').parent().find("#preview_avatar").children("img");
$('#upload-modal').on('shown.bs.modal', function () {
$image.cropper({
preview: '#preview',
viewMode: 1,
dragMode: 'move',
aspectRatio: 16 / 9,
autoCrop: true,
autoCropArea: 0.65,
restore: false,
guides: false,
highlight: false,
cropBoxMovable: false,
cropBoxResizable: false,
scalable: false,
zoomable: false,
rotatable: false,
getData: true,
checkCrossOrigin: true,
modal: true,
center: true,
// allowMove : true,
// allowResize : true,
// allowSelect : true,
// minSelect : [0, 0],
// outlineOpacity : 0.5,
// overlayOpacity : 0.5,
// selectionPosition : [0, 0],
// selectionWidth : 0,
// selectionHeight : 0,
ready: function () {
$image.cropper('setCanvasData', canvasData);
$image.cropper('setCropBoxData', cropBoxData);
},
crop: function (event) {
crop_image_load(event)
console.log(event.x);
console.log(event.y);
console.log(event.width);
console.log(event.height);
console.log(event.rotate);
console.log(event.scaleX);
console.log(event.scaleY);
}
});
}).on('hidden.bs.modal', function () {
cropBoxData = $image.cropper('getCropBoxData');
canvasData = $image.cropper('getCanvasData');
img.src = img_tag;
// cropImage();
$image.cropper('destroy');
});
$button.on('click', function () {
var croppedCanvas;
var roundedCanvas;
if (!croppable) {
return;
}
// Crop
croppedCanvas = $image.cropper('getCroppedCanvas');
// Round
roundedCanvas = getRoundedCanvas(croppedCanvas);
//console.log('<img src="' + roundedCanvas.toDataURL() + '">')
// Show
$result.html('<img src="' + roundedCanvas.toDataURL() + '">');
});
function readURL(input) {
if (input.files && input.files[0]) {
var reader = new FileReader();
reader.onload = function (e) {
$('#image').attr('src', e.target.result);
}
reader.readAsDataURL(input.files[0]);
}
}
$("#business_logo_image").change(function () {
// console.log(this)
$('#upload-modal').modal('show');
readURL(this);
var canvas = $("#canvas"),
context = canvas.get(0).getContext("2d"),
$result = $('#result');
if (this.files && this.files[0]) {
if ( this.files[0].type.match(/^image\//) ) {
var reader = new FileReader();
reader.onload = function(evt) {
var img = new Image();
img.onload = function() {
context.canvas.height = img.height;
context.canvas.width = img.width;
context.drawImage(img, 0, 0);
var cropper = canvas.cropper({
aspectRatio: 16 / 9
});
$('#btnCrop').click(function() {
console.log(evt)
crop_image_load(evt)
// Get a string base 64 data url
var croppedImageDataURL = canvas.cropper('getCroppedCanvas').toDataURL("image/png");
$result.append( $('<img>').attr('src', croppedImageDataURL) );
});
$('#btnRestore').click(function() {
canvas.cropper('reset');
$result.empty();
});
};
img.src = evt.target.result;
};
reader.readAsDataURL(this.files[0]);
}
else {
alert("Invalid file type! Please select an image file.");
}
}
else {
alert('No file(s) selected.');
}
});
var canvas = $("#canvas"),
context = canvas.get(0).getContext("2d"),
$result = $('#result');
$('#fileInput').on( 'change', function(){
if (this.files && this.files[0]) {
if ( this.files[0].type.match(/^image\//) ) {
var reader = new FileReader();
reader.onload = function(evt) {
var img = new Image();
img.onload = function() {
context.canvas.height = img.height;
context.canvas.width = img.width;
context.drawImage(img, 0, 0);
var cropper = canvas.cropper({
aspectRatio: 16 / 9
});
$('#btnCrop').click(function() {
// Get a string base 64 data url
var croppedImageDataURL = canvas.cropper('getCroppedCanvas').toDataURL("image/png");
$result.append( $('<img>').attr('src', croppedImageDataURL) );
});
$('#btnRestore').click(function() {
canvas.cropper('reset');
$result.empty();
});
};
img.src = evt.target.result;
};
reader.readAsDataURL(this.files[0]);
}
else {
alert("Invalid file type! Please select an image file.");
}
}
else {
alert('No file(s) selected.');
}
});
});
#app/uploaders/logo_image_uploader.rb
class LogoImageUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
storage :file
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
# process resize_to_fit: [800, 800]
# process :crop_image
version :large do
process :crop_image
process resize_to_fit: [800, 800]
# resize_to_limit(600, 600)
end
version :medium do
process :crop_image
# process resize_to_fit: [600, 600]
resize_to_limit(600, 600)
end
version :thumb do
process :crop_image
resize_to_fill(100, 100)
end
version :tiny, from_version: :thumb do
process resize_to_fill: [20, 20]
end
def crop_image
resize_to_limit(600, 600)
unless $logo_crop_x.blank?
manipulate! do |image|
x = $logo_crop_x.to_f
y = $logo_crop_y.to_f
w = $logo_crop_w.to_f
h = $logo_crop_h.to_f
#img.crop "#{w}x#{h}+#{x}+#{y}"
image.crop([[w, h].join('x'), [x, y].join('+')].join('+'))
end
end
end
def extension_white_list
%w(jpg jpeg gif png)
end
def filename
"#{secure_token(10)}.#{file.extension}" if original_filename.present?
end
protected
def secure_token(length = 16)
return SecureRandom.hex(length / 2)
end
end
#Steps
rails new cropper-image-rails
##edit file Gemfile
#cropper-image-rails/Gemfile
gem 'carrierwave'
gem 'mini_magick'
bundle install
rails generate scaffold business name:string description:text logo_image:string
rails generate uploader LogoImage
rails db:create
rails db:migrate
@azizmashkour
Copy link

azizmashkour commented Dec 19, 2017

Hello guys, I really appreciate you share this, I have been working on it since 5 days without success. Is it possible to have a demo link please?

@gilcierweb
Copy link
Author

Hello, unfortunately I can not release a demo because this is a private project, but it's very simple to make it work and only generate a scaffold whatsoever and apply the above files.
Can I do a consultation if I can not
[email protected]

@Rubioli
Copy link

Rubioli commented Feb 21, 2021

I can say this approach works fine, BUT you would need to do some adjustments in the JS and the uploader file in order to work properly. If anyone needs it let me know and I'll create one :)

@insomenia-heejae
Copy link

Thanks for the good reference - I 'm trying to mimic this, but modal window does not show up. Is this modal window hidden? or maybe I mistyped wrong word...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment