Skip to content

Instantly share code, notes, and snippets.

@janfabry
Created December 2, 2010 15:19
Show Gist options
  • Save janfabry/725508 to your computer and use it in GitHub Desktop.
Save janfabry/725508 to your computer and use it in GitHub Desktop.
WordPress Fixed Image Cropper
// pxi: pixels in image scale
// pxc: pixels in crop scale
// Helper function to create an element with optional attributes
// Doesn't jQuery already have this?
Monkeyman_FixedImageCroppper_createElement = function(tagName, attr)
{
if (!attr) {
attr = {};
}
if (tagName[0] == '#') {
attr['id'] = tagName.substr(1);
tagName = 'div';
}
return jQuery(document.createElement(tagName)).attr(attr);
}
Monkeyman_FixedImageCropper_ImageInfo = function(pxiWidth, pxiHeight, urlImage, cropInfo) {
$E = Monkeyman_FixedImageCroppper_createElement;
this.pxiWidth = pxiWidth;
this.pxiHeight = pxiHeight;
this.urlImage = urlImage;
this.pxiCropCenterX = pxiWidth / 2;
this.pxiCropCenterY = pxiHeight / 2;
this.pxcWidth = this.pxiWidth;
this.pxcHeight = this.pxiHeight;
this.pxcLeft = 0;
this.pxcTop = 0;
this.cropInfo = null;
this.setCropInfo = function(cropInfo)
{
this.cropInfo = cropInfo;
}
if (cropInfo) {
this.setCropInfo(cropInfo);
}
this.elImage = null;
this.createElement = function(elCropArea)
{
this.elImage = $E('img', {
'src': this.urlImage
,'width': this.pxiWidth
,'height': this.pxiHeight
,'id': 'fixed-image-cropper-image'
}).appendTo(elCropArea);
return this.elImage;
}
this.scale = 1;
this.scaleImage = function(scale)
{
this.scale = scale;
this.pxcWidth = Math.floor(this.pxiWidth * scale);
this.pxcHeight = Math.floor(this.pxiHeight * scale);
var pxcCropCenterX = Math.floor(this.pxiCropCenterX * scale);
var pxcCropCenterY = Math.floor(this.pxiCropCenterY * scale);
this.pxcLeft = (this.cropInfo.pxcWidth / 2) - pxcCropCenterX;
this.pxcTop = (this.cropInfo.pxcHeight / 2) - pxcCropCenterY;
this.setImageDimensions({
'pxcWidth': this.pxcWidth
,'pxcHeight': this.pxcHeight
,'pxcLeft': this.pxcLeft
,'pxcTop': this.pxcTop
});
}
this.startImagePosition = null;
this.moveImage = function(moveInfo)
{
this.setImageDimensions({
'pxcLeft': this.startImagePosition.pxcLeft + moveInfo.pxcDeltaLeft
,'pxcTop': this.startImagePosition.pxcTop + moveInfo.pxcDeltaTop
,'pxcWidth': this.pxcWidth
,'pxcHeight': this.pxcHeight
});
}
this.startImageMove = function()
{
this.startImagePosition = {
'pxcLeft': this.elImage.position().left
,'pxcTop': this.elImage.position().top
};
}
this.stopImageMove = function()
{
this.pxiCropCenterX = (-this.pxcLeft + this.cropInfo.pxcWidth / 2) / this.scale;
this.pxiCropCenterY = (-this.pxcTop + this.cropInfo.pxcHeight / 2) / this.scale;
}
this.setImageDimensions = function(dim)
{
if (0 < dim.pxcLeft) {
dim.pxcLeft = 0;
}
if (0 < dim.pxcTop) {
dim.pxcTop = 0;
}
if (dim.pxcLeft < this.cropInfo.pxcWidth - this.pxcWidth) {
dim.pxcLeft = this.cropInfo.pxcWidth - this.pxcWidth;
if (0 < dim.pxcLeft) {
dim.pxcLeft = dim.pxcLeft / 2;
}
}
if (dim.pxcTop < this.cropInfo.pxcHeight - this.pxcHeight) {
dim.pxcTop = this.cropInfo.pxcHeight - this.pxcHeight;
if (0 < dim.pxcTop) {
dim.pxcTop = dim.pxcTop / 2;
}
}
this.pxcWidth = dim.pxcWidth;
this.pxcHeight = dim.pxcHeight;
this.pxcLeft = dim.pxcLeft;
this.pxcTop = dim.pxcTop;
this.elImage.css({
width: dim.pxcWidth
,height: dim.pxcHeight
,left: dim.pxcLeft
,top: dim.pxcTop
});
}
this.getSizeInfo = function()
{
return {
'pxiCropWidth': Math.floor(this.cropInfo.pxcWidth / this.scale)
,'pxiCropHeight': Math.floor(this.cropInfo.pxcHeight / this.scale)
,'pxiCropLeft': Math.floor(-this.pxcLeft / this.scale)
,'pxiCropTop': Math.floor(-this.pxcTop / this.scale)
,'pxcCropWidth': this.cropInfo.pxcWidth
,'pxcCropHeight': this.cropInfo.pxcHeight
}
}
this.getSizeString = function()
{
var sizeInfo = this.getSizeInfo();
var sizeString = 'c' + sizeInfo.pxiCropWidth + 'x' + sizeInfo.pxiCropHeight;
var cropStartString = '';
if (0 <= sizeInfo.pxiCropLeft) {
cropStartString += sizeInfo.pxiCropLeft;
}
cropStartString += 'x';
if (0 <= sizeInfo.pxiCropTop) {
cropStartString += sizeInfo.pxiCropTop;
}
if ('x' != cropStartString) {
sizeString += 's' + cropStartString;
}
sizeString += '-' + sizeInfo.pxcCropWidth + 'x' + sizeInfo.pxcCropHeight;
return sizeString;
}
}
Monkeyman_FixedImageCropper = function(elContainer, cropInfo, imageInfo, currentCropInfo)
{
$E = Monkeyman_FixedImageCroppper_createElement;
elContainer = jQuery(elContainer);
cropInfo.pxcLeft = (elContainer.innerWidth() - cropInfo.pxcWidth) / 2;
cropInfo.pxcTop = (elContainer.innerHeight() - cropInfo.pxcHeight) / 2;
var scaleInfo = {
'max': 1
,'min': Math.max(cropInfo.pxcWidth / imageInfo.pxiWidth, cropInfo.pxcHeight / imageInfo.pxiHeight)
};
if ( 1 < scaleInfo.min ) {
// Image is smaller than thumbnail size
scaleInfo = {
'max': scaleInfo.min
,'min': scaleInfo.max
};
}
// Set up preview thingie
var elPreview = $E('#fixed-image-cropper-preview').appendTo(elContainer);
var elCropArea = $E('#fixed-image-cropper-crop-area').css({'left': cropInfo.pxcLeft, 'top': cropInfo.pxcTop}).appendTo(elPreview);
// Set up overlay
var elCropOverlay = $E('#fixed-image-cropper-overlay').css({'width': cropInfo.pxcWidth, 'height': cropInfo.pxcHeight}).appendTo(elCropArea);
var elShading = $E('#fixed-image-cropper-shading').appendTo(elPreview);
$E('#fixed-image-cropper-shading-top').css({
top: 0,
left: 0,
width: elContainer.innerWidth(),
height: cropInfo.pxcTop
}).appendTo(elShading);
$E('#fixed-image-cropper-shading-bottom').css({
top: cropInfo.pxcTop + cropInfo.pxcHeight,
left: 0,
width: elContainer.innerWidth(),
height: cropInfo.pxcTop
}).appendTo(elShading);
$E('#fixed-image-cropper-shading-left').css({
top: cropInfo.pxcTop,
left: 0,
width: cropInfo.pxcLeft,
height: cropInfo.pxcHeight
}).appendTo(elShading);
$E('#fixed-image-cropper-shading-right').css({
top: cropInfo.pxcTop,
left: cropInfo.pxcLeft + cropInfo.pxcWidth,
width: cropInfo.pxcLeft,
height: cropInfo.pxcHeight
}).appendTo(elShading);
// Set up resize slider
var elSlider = $E('#fixed-image-cropper-slider').appendTo(elContainer);
var elSliderTrack = $E('#fixed-image-cropper-slider-track').appendTo(elSlider);
var elSliderHandle = $E('#fixed-image-cropper-slider-handle').appendTo(elSlider);
// TODO: Replace this with a jQuery thingie
var slider = new Control.Slider(
elSliderHandle[0].id,
elSliderTrack[0].id,
{
'onSlide': function(v) {
imageInfo.scaleImage(scaleInfo.min + ((scaleInfo.max - scaleInfo.min) * v));
}
}
);
// Set up draggable
elDraggable = $E('#fixed-image-cropper-draggable').appendTo(elPreview);
startHelperPosition = null;
elDraggable.draggable({
start: function(event, ui) {
startHelperPosition = ui.helper.position();
imageInfo.startImageMove();
}
,stop: function(event, ui) {
ui.helper.css({
left: 0,
top: 0
});
imageInfo.stopImageMove();
}
,drag: function(event, ui) {
imageInfo.moveImage({
'pxcDeltaLeft': ui.position.left - startHelperPosition.left
,'pxcDeltaTop': ui.position.top - startHelperPosition.top
});
}
});
// Set up image
imageInfo.createElement(elCropArea);
var curScale = scaleInfo.min;
if ( currentCropInfo.pxiCropWidth && currentCropInfo.pxcCropWidth ) {
curScale = currentCropInfo.pxcCropWidth / currentCropInfo.pxiCropWidth;
}
imageInfo.scaleImage(curScale);
slider.setValue((curScale - scaleInfo.min) / (scaleInfo.max - scaleInfo.min));
if ( currentCropInfo.pxiCropLeft || currentCropInfo.pxiCropTop ) {
imageInfo.setImageDimensions({
'pxcLeft': -currentCropInfo.pxiCropLeft * curScale
,'pxcTop': -currentCropInfo.pxiCropTop * curScale
,'pxcWidth': imageInfo.pxcWidth
,'pxcHeight': imageInfo.pxcHeight
});
imageInfo.stopImageMove();
}
}
var imageEdit;
(function($) {
imageEdit = {
open: function(postid, nonce) {
var data, elem = $('#image-editor-' + postid), head = $('#media-head-' + postid),
btn = $('#imgedit-open-btn-' + postid), spin = btn.siblings('img');
btn.attr('disabled', 'disabled');
spin.css('visibility', 'visible');
data = {
'action': 'image-editor',
'_ajax_nonce': nonce,
'postid': postid,
'do': 'open'
};
var imgEdit = this;
elem.load(ajaxurl, data, function() {
elem.fadeIn('fast', function() {
imgEdit.init(imgEdit.imageInfo);
});
head.fadeOut('fast', function(){
btn.removeAttr('disabled');
spin.css('visibility', 'hidden');
});
});
}
,init: function(imageInfo) {
var cropInfo = {
'pxcWidth': imageInfo.cropInfo.width
,'pxcHeight': imageInfo.cropInfo.height
}
this.imageObject = new Monkeyman_FixedImageCropper_ImageInfo(imageInfo.width, imageInfo.height, imageInfo.url, cropInfo);
var currentCropInfo = {};
var cropInfoProps = ['pxiCropWidth', 'pxiCropHeight', 'pxiCropLeft', 'pxiCropTop', 'pxcCropWidth', 'pxcCropHeight'];
for ( var pi in cropInfoProps ) {
var prop = cropInfoProps[pi];
if ( imageInfo[prop] ) {
currentCropInfo[prop] = imageInfo[prop];
}
}
this.cropperInstance = new Monkeyman_FixedImageCropper('#fixed-image-cropper-container-' + imageInfo.id, cropInfo, this.imageObject, currentCropInfo);
}
,close : function(postid, warn) {
//warn = warn || false;
//if ( warn && this.notsaved(postid) )
// return false;
this.iasapi = {};
this.hold = {};
$('#image-editor-' + postid).fadeOut('fast', function() {
$('#media-head-' + postid).fadeIn('fast');
$(this).empty();
});
}
,save : function(postid, nonce) {
var sizeString = this.imageObject.getSizeString();
// this.toggleEditor(postid, 1);
data = {
'action': 'image-editor'
,'_ajax_nonce': nonce
,'postid': postid
,'sizeString': sizeString
,'sizeInfo': this.imageObject.getSizeInfo()
,'do': 'save'
};
$.post(ajaxurl, data, function(r) {
var ret = JSON.parse(r);
if ( ret.error ) {
$('#imgedit-response-' + postid).html('<div class="error"><p>' + ret.error + '</p><div>');
imageEdit.close(postid);
return;
}
if ( ret.thumbnail )
$('.thumbnail', '#thumbnail-head-' + postid).attr('src', ''+ret.thumbnail);
if ( ret.msg )
$('#imgedit-response-' + postid).html('<div class="updated"><p>' + ret.msg + '</p></div>');
imageEdit.close(postid);
});
}
/* ,toggleEditor : function(postid, toggle) {
var wait = $('#imgedit-wait-' + postid);
if ( toggle )
wait.height( $('#imgedit-panel-' + postid).height() ).fadeIn('fast');
else
wait.fadeOut('fast');
}
*/};
})(jQuery);
<?php
/*
Plugin Name: Fixed image cropper
Plugin URI: http://wordpress.stackexchange.com/questions/1733/user-friendly-cropping-of-post-thumbnails
Description: GUI for cropping an image to a fixed size
Version: 1.0
Author: Jan Fabry
*/
// For development, when __FILE__ does not work with symlinks
define( 'MONKEYMAN_FIXEDIMAGECROPPER_PATH', ABSPATH . 'wp-content/plugins/monkeyman-fixed-image-cropper/fixed-image-cropper.php' );
class Monkeyman_FixedImageCropper
{
protected $cropSize = 'thumbnail';
public function __construct()
{
add_action( 'admin_init', array( &$this, 'hijack_image_editor' ) );
add_action( 'admin_enqueue_scripts', array( &$this, 'register_scripts' ) );
}
public function register_scripts()
{
wp_deregister_script( 'image-edit' );
wp_register_script( 'image-edit', plugins_url( 'fixed-image-cropper.js', MONKEYMAN_FIXEDIMAGECROPPER_PATH ), array( 'jquery-ui-draggable', 'scriptaculous-slider' ) );
if ( wp_script_is( 'image-edit' ) ) {
wp_enqueue_style( 'fixed-image-cropper', plugins_url( 'style.css', MONKEYMAN_FIXEDIMAGECROPPER_PATH ) );
}
}
public function hijack_image_editor()
{
if ( defined( 'DOING_AJAX' ) && DOING_AJAX && 'image-editor' == $_REQUEST['action'] ) {
$attachment_id = intval( $_POST['postid'] );
if ( empty( $attachment_id ) || ! current_user_can( 'edit_post', $attachment_id ) ) {
die( '-1' );
}
check_ajax_referer( 'image_editor-' .$attachment_id );
$this->image_editor( $attachment_id );
die();
}
}
public function image_editor( $attachment_id )
{
$nonce = wp_create_nonce( 'image_editor-' . $attachment_id );
$meta = wp_get_attachment_metadata( $attachment_id );
$cropSizeName = 'post-thumbnail';
$cropInfo = $GLOBALS['_wp_additional_image_sizes'][$cropSizeName];
if ( 'save' == $_REQUEST['do'] ) {
$msg = array();
$pathInfo = pathinfo($meta['file']);
$imgFilename = $pathInfo['filename'];
if ( $_REQUEST['sizeString'] ) {
$imgFilename .= '-' . $_REQUEST['sizeString'];
}
$imgFilename .= '.' . $pathInfo['extension'];
$meta['sizes'][$cropSizeName] = array_merge( array(
'width' => $cropInfo['width'],
'height' => $cropInfo['height'],
'file' => $imgFilename,
), $_REQUEST['sizeInfo'] );
wp_update_attachment_metadata( $attachment_id, $meta );
$newImageSrc = wp_get_attachment_image_src( $attachment_id, 'post-thumbnail' );
if ( $newImageSrc ) {
$msg['thumbnail'] = $newImageSrc[0];
}
$msg = json_encode( $msg );
die( $msg );
}
// Start from the existing cropped image, to get previous crop info
$imageInfo = ( isset( $meta['sizes'] ) && isset( $meta['sizes'][$cropSizeName] ) ? $meta['sizes'][$cropSizeName] : array() );
$imageInfo['id'] = $attachment_id;
$imageInfo['url'] = wp_get_attachment_url( $attachment_id );
$imageInfo['width'] = $meta['width'];
$imageInfo['height'] = $meta['height'];
$imageInfo['cropInfo'] = $cropInfo;
$jsImageInfo = json_encode( $imageInfo );
// $curImage = wp_get_attachment_image( $attachment_id, $cropSizeName );
echo <<<HTML
<div class="imgedit-wrap">
<div id="imgedit-panel-{$attachment_id}">
<div id="fixed-image-cropper-container-{$attachment_id}" class="fixed-image-cropper-container">
</div>
<script type="text/javascript">imageEdit.imageInfo = {$jsImageInfo};</script>
<div class="imgedit-submit">
<input type="button" onclick="imageEdit.close({$attachment_id}, 1)" class="button" value="Cancel" />
<input type="button" onclick="imageEdit.save({$attachment_id}, '{$nonce}')" class="button-primary imgedit-submit-btn" value="Save" />
</div>
</div>
<div class="imgedit-wait" id="imgedit-wait-{$attachment_id}"></div>
</div>
HTML;
}
}
$monkeyman_FixedImageCropper_instance = new Monkeyman_FixedImageCropper();
.fixed-image-cropper-container
{
width: 400px;
height: 400px;
border: 1px solid gray;
position: relative;
margin-bottom: 30px; /* Room for the slider */
z-index: 150;
}
#fixed-image-cropper-preview
{
width: 100%;
height: 100%;
overflow: hidden;
position: relative;
z-index: 100;
}
#fixed-image-cropper-overlay
{
position: relative;
/* background: url(http://demos.spindrop.us/image_cropper/images/overlay.png);*/
border: 1px solid black;
margin: -1px; /* Compensate for border */
left: 0;
top: 0;
z-index: 5;
}
#fixed-image-cropper-draggable
{
width: 100%;
height: 100%;
position: relative;
z-index: 50;
cursor: move;
}
#fixed-image-cropper-crop-area
{
position: absolute;
z-index: -1000;
}
#fixed-image-cropper-image
{
position: absolute;
z-index: -1000;
}
#fixed-image-cropper-shading div
{
position: absolute;
z-index: 20;
background-color: white;
-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";
filter: alpha(opacity=50);
opacity: 0.5;
}
#fixed-image-cropper-slider-track
{
width:400px;
background-color:#ccc;
height:10px;
}
#fixed-image-cropper-slider-handle
{
width:10px;
height:15px;
background-color:#f00;
cursor:move;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment