Last active
March 20, 2021 12:49
-
-
Save gilcierweb/9905e150919aea6f9f142c90fc1ced5e to your computer and use it in GitHub Desktop.
Upload image crop with Ruby and Rails, CarrierWave and Cropper.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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">×</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 %> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#app/models/business.rb | |
class Business < ApplicationRecord | |
mount_uploader :logo_image, LogoImageUploader | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// #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.'); | |
} | |
}); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
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]
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 :)
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
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?