using media uploader to select image of specific size, enforce cropper

I’ve followed this article to use Media Uploader in my code. When I click the Browse button on my form, the media uploader opens and after selecting the image, I get the URL of that image added in my form field. No issues so far.

But I want to enforce the image to be of a specific size. If the size is different, user must crop it to match the required size (or aspect ratio) before he can select the image (and my mediaUploader.on('select', function() { is executed).

Please note that I don’t want to use add_image_size because that will create a thumbnail of the given size for each and every image uploaded (AFAIK… correct me if I’m wrong). Instead, only in a specific scenario I want the user to crop the image manually while uploading.

Basically my requirement is well described in a question on SO: wordpress-media-library-crop-image-on-insert-like-header-image-cropper

But there is no solution posted there. So I’m putting it here for the WP experts to guide me.

I’ve tried searching for ‘wordpress media uploader (enforce image size | image cropper | select image of specific size)’ and what not. But somehow not getting any link that describes the steps to enforce a given image size. Nor could I found any documentation for wp.media that describes all the options that can be passed.

I’ve seen following questions already but that doesn’t seem to do what I’m looking for:

  • Is is possible to crop an image after uploading
  • Way to force media uploader use custom image size
  • Image size filtering in Media uploader according to custom post type

Edit

I could find the use of WP_Customize_Cropped_Image_Control @ https://www.sitepoint.com/using-the-wordpress-customizer-media-controls/
But its in the context of Customizer. Is there any similar control that can be used in standard plugin?

And there is this wp.media.controller.Cropper defined inside wp-includes/js/media-views.js but how do I use it?

3 Answers
3

It looks like the select and crop functionality is currently only used in the WordPress Customizer.

However, after looking over the code in the Admin folder of WordPress I was able to get it working on my themes option page.

Here is my setting for a header picture:

function setting_heading_picture() { 
  $heading_picture = esc_attr(get_option( 'heading_picture' )); ?>
  <input type="hidden" name="heading_picture" id="heading_picture" value="https://wordpress.stackexchange.com/questions/281760/<?php echo $heading_picture; ?>" />
  <img id="heading_picture_preview" class="heading-picture" src="https://wordpress.stackexchange.com/questions/281760/<?php echo $heading_picture; ?>" />
  <button id="btn_heading_picture" name="btn_heading_picture" class="button default">Choose Picture</button>
}

Now here is the admin javascript required:

$(function() {

function myTheme_calculateImageSelectOptions(attachment, controller) {

    var control = controller.get( 'control' );

    var flexWidth = !! parseInt( control.params.flex_width, 10 );
    var flexHeight = !! parseInt( control.params.flex_height, 10 );

    var realWidth = attachment.get( 'width' );
    var realHeight = attachment.get( 'height' );

    var xInit = parseInt(control.params.width, 10);
    var yInit = parseInt(control.params.height, 10);

    var ratio = xInit / yInit;

    controller.set( 'canSkipCrop', ! control.mustBeCropped( flexWidth, flexHeight, xInit, yInit, realWidth, realHeight ) );

    var xImg = xInit;
    var yImg = yInit;

    if ( realWidth / realHeight > ratio ) {
        yInit = realHeight;
        xInit = yInit * ratio;
    } else {
        xInit = realWidth;
        yInit = xInit / ratio;
    }        

    var x1 = ( realWidth - xInit ) / 2;
    var y1 = ( realHeight - yInit ) / 2;        

    var imgSelectOptions = {
        handles: true,
        keys: true,
        instance: true,
        persistent: true,
        imageWidth: realWidth,
        imageHeight: realHeight,
        minWidth: xImg > xInit ? xInit : xImg,
        minHeight: yImg > yInit ? yInit : yImg,            
        x1: x1,
        y1: y1,
        x2: xInit + x1,
        y2: yInit + y1
    };

    return imgSelectOptions;
}  

function myTheme_setImageFromURL(url, attachmentId, width, height) {
    var choice, data = {};

    data.url = url;
    data.thumbnail_url = url;
    data.timestamp = _.now();

    if (attachmentId) {
        data.attachment_id = attachmentId;
    }

    if (width) {
        data.width = width;
    }

    if (height) {
        data.height = height;
    }

    $("#heading_picture").val( url );
    $("#heading_picture_preview").prop("src", url);        

}

function myTheme_setImageFromAttachment(attachment) {

    $("#heading_picture").val( attachment.url );
    $("#heading_picture_preview").prop("src", attachment.url);             

}

var mediaUploader;

$("#btn_heading_picture").on("click", function(e) {

    e.preventDefault(); 

    /* We need to setup a Crop control that contains a few parameters
       and a method to indicate if the CropController can skip cropping the image.
       In this example I am just creating a control on the fly with the expected properties.
       However, the controls used by WordPress Admin are api.CroppedImageControl and api.SiteIconControl
    */

   var cropControl = {
       id: "control-id",
       params : {
         flex_width : false,  // set to true if the width of the cropped image can be different to the width defined here
         flex_height : true, // set to true if the height of the cropped image can be different to the height defined here
         width : 300,  // set the desired width of the destination image here
         height : 200, // set the desired height of the destination image here
       }
   };

   cropControl.mustBeCropped = function(flexW, flexH, dstW, dstH, imgW, imgH) {

    // If the width and height are both flexible
    // then the user does not need to crop the image.

    if ( true === flexW && true === flexH ) {
        return false;
    }

    // If the width is flexible and the cropped image height matches the current image height, 
    // then the user does not need to crop the image.
    if ( true === flexW && dstH === imgH ) {
        return false;
    }

    // If the height is flexible and the cropped image width matches the current image width, 
    // then the user does not need to crop the image.        
    if ( true === flexH && dstW === imgW ) {
        return false;
    }

    // If the cropped image width matches the current image width, 
    // and the cropped image height matches the current image height
    // then the user does not need to crop the image.               
    if ( dstW === imgW && dstH === imgH ) {
        return false;
    }

    // If the destination width is equal to or greater than the cropped image width
    // then the user does not need to crop the image...
    if ( imgW <= dstW ) {
        return false;
    }

    return true;        

   };      

    /* NOTE: Need to set this up every time instead of reusing if already there
             as the toolbar button does not get reset when doing the following:

            mediaUploader.setState('library');
            mediaUploader.open();

    */       

    mediaUploader = wp.media({
        button: {
            text: 'Select and Crop', // l10n.selectAndCrop,
            close: false
        },
        states: [
            new wp.media.controller.Library({
                title:     'Select and Crop', // l10n.chooseImage,
                library:   wp.media.query({ type: 'image' }),
                multiple:  false,
                date:      false,
                priority:  20,
                suggestedWidth: 300,
                suggestedHeight: 200
            }),
            new wp.media.controller.CustomizeImageCropper({ 
                imgSelectOptions: myTheme_calculateImageSelectOptions,
                control: cropControl
            })
        ]
    });

    mediaUploader.on('cropped', function(croppedImage) {

        var url = croppedImage.url,
            attachmentId = croppedImage.attachment_id,
            w = croppedImage.width,
            h = croppedImage.height;

            myTheme_setImageFromURL(url, attachmentId, w, h);            

    });

    mediaUploader.on('skippedcrop', function(selection) {

        var url = selection.get('url'),
            w = selection.get('width'),
            h = selection.get('height');

            myTheme_setImageFromURL(url, selection.id, w, h);            

    });        

    mediaUploader.on("select", function() {

        var attachment = mediaUploader.state().get( 'selection' ).first().toJSON();

        if (     cropControl.params.width  === attachment.width 
            &&   cropControl.params.height === attachment.height 
            && ! cropControl.params.flex_width 
            && ! cropControl.params.flex_height ) {
                myTheme_setImageFromAttachment( attachment );
            mediaUploader.close();
        } else {
            mediaUploader.setState( 'cropper' );
        }

    });

    mediaUploader.open();

});
});

Don’t forget to adjust the desired width / height that you need in the code above.

Code to save the cropped image as an attachment has been omitted. The cropped images will still be in the usual place in wp-content/uploads but you won’t see the cropped images in the Media Library.

I don’t know how to enforce an exact size in the Cropper. Hopefully someone else can come along and help answer that.

Leave a Comment