Trouble cropping and resizing image

2019-07-14 06:13发布

问题:

I'm trying to create a thumbnail image for a larger image. I'm using Jcrop to get the co-ordinates of the crop but I can't turn this into a properly cropped thumbnail image. I've set up Jcrop properly and it's sending through x-y co-ordinates along with the size of the box but I can't work out how to crop from that.

What I have is the path to an image on my server, along with 4 co-ordinates and the width and height of the square box they create (I've locked the aspect ratio to 1:1 as I want square thumbnails). I then send this via Ajax to my PHP cropping script, but I cannot get it to crop the based on what is set.

This is what I have so far:

public function Crop($file, $crop) {

    $height = $width = 180;
    $ratio = $width / $height;
    $pos = strrpos($file, '.');
    $name = substr($file, 0, $pos);
    $ext = strtolower(substr($file, $pos));

    if( ! in_array($ext, array('.gif', '.jpg', '.jpeg', '.png'))) {
        return 'INVALID_EXT';
    }

    // When loading the image we check to see if the first character in file is a slash, and if so remove it as the last character of root is a slash.
    $src = ROOT . (in_array(substr($file, 0, 1), array('/', '\\')) ? substr($file, 1) : $file);
    $srcRes = imagecreatefromstring(file_get_contents($src));
    if( ! $srcRes) {
        return 'INVALID_FILE';
    }
    $srcWidth = imagesx($srcRes);
    $srcHeight = imagesy($srcRes);
    $srcRatio = $srcWidth / $srcHeight;
    $dstRes = imagecreatetruecolor($crop['w'], $crop['h']);

    if($ext == '.gif') {
        $dstBg = imagecolorallocate($dstRes, 0, 0, 0);
        imagecolortransparent($dstRes, $dstBg);
    } elseif($ext == '.png') {
        $dstBg = imagecolorallocate($dstRes, 0, 0, 0);
        imagecolortransparent($dstRes, $dstBg);
        imagealphablending($dstRes, FALSE);
        imagesavealpha($dstRes, TRUE);
    }

    $srcX = 0;
    $srcY = 0;
    if($srcRatio > $ratio) {
        $tmpWidth = $srcHeight * $ratio;
        $tmpHeight = $srcHeight;
        $srcX = ($srcWidth - $tmpWidth) / 2;
        $srcY = 0;
    } else {
        $tmpWidth = $srcWidth;
        $tmpHeight = $srcWidth / $ratio;
        $srcX = 0;
        $srcY = ($srcHeight - $tmpHeight) / 2;
    }

    imagecopyresampled($dstRes, $srcRes, 0, 0, $crop['x'], $crop['y'], $crop['w'], $crop['h'], $tmpWidth, $tmpHeight);

    $dst = ROOT . (in_array(substr($name, 0, 1), array('/', '\\')) ? substr($name, 1) : $name) . '-thumb' . $ext;
    if($ext == '.gif') {
        $try = imagegif($dstRes, $dst);
    } elseif($ext == '.jpg' || $ext == '.jpeg') {
        $try = imagejpeg($dstRes, $dst, 80);
    } elseif($ext == '.png') {
        $try = imagepng($newThumbImageResource, $dst);
    }

    if( ! $try) {
        return 'CREATE_ERR';
    }

    return 'SUCCESS';

}

I've tried changing all sorts of things in imagecopyresampled but cannot get it to crop as per what Jcrop is sending through. If I use $srcWidth and $srcHeight it maintains the aspect ratio of the original image and squishes it into the 180px square. But it doesn't seem to be resizing it or cropping it from the right place.

Can someone please help me work out what figures I should be using where? I've been banging my head against this all day.


EDIT

Here is the rest of the code. First the JavaScript that runs JCrop. It's triggered after an Ajax file upload to create a thumbnail of that image:

This is the Ajax function upload that at the end calls the cropper.

$(function () {

    'use strict';
    // Change this to the location of your server-side upload handler:
    var url = '/eshop/library/ajax/ajax.file-upload.php';
    var uploadDir = 'prodimages/';

    $('.listing-image').fileupload({
        url: url,
        dataType: 'json',
        autoUpload: true,
        acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i,
        maxFileSize: 1000000, // 1 MB
        // Enable image resizing, except for Android and Opera,
        // which actually support image resizing, but fail to
        // send Blob objects via XHR requests:
        disableImageResize: /Android(?!.*Chrome)|Opera/
            .test(window.navigator.userAgent),
        previewMaxWidth: 120,
        previewMaxHeight: 120,
        previewCrop: true,
        paramName: 'files[]',
        formData: {uploadDir: uploadDir}
    })/*.on('fileuploadprocessalways', function (e, data) {
        var index = data.index;
        var file = data.files[index];
        $(this).html(file.preview);
    })*/.on('fileuploadprogressall', function (e, data) {
        var progress = parseInt(data.loaded / data.total * 100, 10);
        $('.listing-progress', this).css(
            'width',
            progress + '%'
        );
    }).on('fileuploaddone', function (e, data) {
        var file = data.result.files[0];
        var html = '<div class="listing-preview">\
            <img src="' + file.thumbnailUrl + '" data-name="' + file.name + '">\
            <div class="listing-preview-delete">Delete</div>\
        </div>';
        $(this).html(html).data('delete-url', file.deleteUrl).css('padding', 0);

        Crop('/' + uploadDir + file.name, $(this).prop('id'));

    });

});

This sends the crop details through to the PHP script above via Ajax.

$(document).on('click', '.crop-btn', function() {

    var data = {
        file: $(this).data('src'),
        crop: jcrop.tellSelect()
    }
    ShowLoadingById('crop-loading');
    AjaxHandler('/eshop/library/ajax/ajax.product-crop-thumb.php', data, 'POST', true);

});

The crop window is a lightbox, this function vertically centres it and resizes the image if it's vertically bigger than the available space.

function CentreCrop() {

    var m = ($(window).height() - ($('.crop > div').height() + 60)) / 2;
    $('.crop > div').css('margin-top', m);

    if($('#crop-img').height() > $('.crop > div').height() - 30) {
        $('#crop-img-container').height($('.crop > div').height() - 30);
    }

}

This is the initial function that stores the file to be cropped and calls the worker if it's not running.

var toBeCropped = [];
var working = false;
function Crop(file, id) {

    toBeCropped.push({path: file, id: id});

    if( ! working) {
        working = true;
        CropWorker();
    }

}

This is the function I run when the crop has finished to destroy jcrop and clear the crop lightbox ready for the next image to be cropped.

function CropSuccess() {

    $('.crop').fadeOut(250, function() {

        jcrop.destroy();
        $(this).html('');
        CropWorker();

    });

}

This is the worker that actually creates the content in the lightbox and initiates jcrop.

function CropWorker() {

    if(toBeCropped.length > 0) {
        file = toBeCropped.shift();
        html = '<div>\
            <div id="crop-img-container" class="row-fluid">\
                <img id="crop-img" src="' + file.path + '">\
            </div>\
            <div class="row-fluid">\
                <div class="span3 offset9">\
                    <button class="span12 btn crop-btn" data-id="' + file.id + '" data-src="' + file.path + '">Create Thumb</button>\
                </div>\
            </div>\
            <div class="row-fluid loading-screen" id="crop-loading">\
                <div>\
                    <h4>Cropping...</h4>\
                    <img src="/img/loading.gif">\
                </div>\
            </div>\
        </div>';
        $('.crop').html(html);
        $('.crop').fadeIn(250);
        $('#crop-img').load(function() {
            CentreCrop();
            $('#crop-img').Jcrop({
                aspectRatio: 1/1,
                bgColor: 'black',
                bgOpacity: 0.4,
                boxWidth: $('#crop-img').width(), // Only just recently added boxWidth and height to see if that would fix it, no difference with or without.
                boxHeight: $('#crop-img').height(),
                //maxSize: [300,300],
                minSize: [180,180]
            }, function() {
                jcrop = this;
                jcrop.setSelect([0,0,180,180]);
            });
        });
    } else {
        working = false;
    }

}

UPDATE

Part of the problem it seems was the image resizing. I was changing the image size to fit the screen, I though JCrop would take care of that for me but it seems you have to tell JCrop the original dimensions of the image. I've added the true size option when initializing JCrop and it seems we're almost there.

For smaller images, those under 1000px the cropper seems to be working great. But for larger ones (1000px +) it's producing black images. It doesn't do this when I use it with the JCrop demo script but the only difference between the two is one is outputting the file to the screen and the other is saving it. I can't see any other difference or figure out why this would happen.

UPDATE 2 It seems to only be affected if I'm running the code through Ajax. If I run the exact same function just posting to a page and running it at the top, the thumbnail is created perfectly every time, no matter what the size of the original image or the size of the box I draw.

回答1:

In your code $crop['w'] and $crop['h'] are for the source;

$tmpWidth and $tmpHeight are for the destination;

so you should switch them according to imagecopyresampled function (http://php.net//manual/fr/function.imagecopyresampled.php).

imagecopyresampled($dstRes, $srcRes, 0, 0, $crop['x'], $crop['y'], $tmpWidth, $tmpHeight, $crop['w'], $crop['h']);

edit 2

Your destination image shouldn't be set with $crop data, but with the size you want :

$dstRes = imagecreatetruecolor(180, 180);

And i'm not sure what value you have from $tmpWidth and $tmpHeight but they should be both 180 since it's the size you want :

imagecopyresampled($dstRes, $srcRes, 0, 0, $crop['x'], $crop['y'], 180, 180, $crop['w'], $crop['h']);

Edit 3

Ok last try, i've a working code for jcrop but i don't use it inside a function, it's still working the same way. I checked line by line your code and here is what i have. I removed everything about ratio, Jcrop manage it, no need to do it with PHP. Proper dest image, proper imagecopyresampled, looks good to me :/

public function Crop($file, $crop) {

    $height = $width = 180;

    // your file type checking
    $pos = strrpos($file, '.');
    $name = substr($file, 0, $pos);
    $ext = strtolower(substr($file, $pos));

    if( ! in_array($ext, array('.gif', '.jpg', '.jpeg', '.png'))) {
        return 'INVALID_EXT';
    }

    // source image
    $src = ROOT . (in_array(substr($file, 0, 1), array('/', '\\')) ? substr($file, 1) : $file);
    $srcRes = imagecreatefromstring(file_get_contents($src));
    if( ! $srcRes) {
        return 'INVALID_FILE';
    }

    // destination image
    $dstRes = imagecreatetruecolor($width, $height);

    // file type transparence
    if($ext == '.gif') {
        $dstBg = imagecolorallocate($dstRes, 0, 0, 0);
        imagecolortransparent($dstRes, $dstBg);
    } elseif($ext == '.png') {
        $dstBg = imagecolorallocate($dstRes, 0, 0, 0);
        imagecolortransparent($dstRes, $dstBg);
        imagealphablending($dstRes, FALSE);
        imagesavealpha($dstRes, TRUE);
    }

    // bool imagecopyresampled ( resource $dst_image , resource $src_image , int $dst_x , int $dst_y , int $src_x , int $src_y , int $dst_w , int $dst_h , int $src_w , int $src_h )
    imagecopyresampled($dstRes, $srcRes, 0, 0, $crop['x'], $crop['y'], $width, $height, $crop['w'], $crop['h']);

    $dst = ROOT . (in_array(substr($name, 0, 1), array('/', '\\')) ? substr($name, 1) : $name) . '-thumb' . $ext;
    if($ext == '.gif') {
        $try = imagegif($dstRes, $dst);
    } elseif($ext == '.jpg' || $ext == '.jpeg') {
        $try = imagejpeg($dstRes, $dst, 80);
    } elseif($ext == '.png') {
        $try = imagepng($newThumbImageResource, $dst);
    }

    if( ! $try) {
        return 'CREATE_ERR';
    }

    return 'SUCCESS';

}


回答2:

Try to use this function at the time of resizing the image

Just call the function like this:

    resize_image($source_image, $destination_filename, 200, 200);

This function includes crop functionality as well. You can enable or disable the crop parameter by passing true or false ($crop = true/false) in it.

function resize_image($source_image, $destination_filename, $width, $height, $quality = 100, $crop = true)
{ 
        if( ! $image_data = getimagesize( $source_image ) )
        {
            return false;
        }

        switch( $image_data['mime'] )
        {
            case 'image/gif':
            $get_func = 'imagecreatefromgif';
            $suffix = ".gif";
            break;
            case 'image/jpeg';
            $get_func = 'imagecreatefromjpeg';
            $suffix = ".jpg";
            break;
            case 'image/png':
            $get_func = 'imagecreatefrompng';
            $suffix = ".png";
            break;
        }

        $img_original = call_user_func( $get_func, $source_image );
        $old_width = $image_data[0];
        $old_height = $image_data[1];
        $new_width = $width;
        $new_height = $height;
        $src_x = 0;
        $src_y = 0;
        $current_ratio = round( $old_width / $old_height, 2 );
        $desired_ratio_after = round( $width / $height, 2 );
        $desired_ratio_before = round( $height / $width, 2 );

        if( $old_width < $width || $old_height < $height )
        {
            /**
            * The desired image size is bigger than the original image.
            * Best not to do anything at all really.
            */
            return false;
        }


        /**
        * If the crop option is left on, it will take an image and best fit it
        * so it will always come out the exact specified size.
        */
        if( $crop )
        {
            /**
            * create empty image of the specified size
            */
            $new_image = imagecreatetruecolor( $width, $height );

            /**
            * Landscape Image
            */
            if( $current_ratio > $desired_ratio_after )
            {
                $new_width = $old_width * $height / $old_height;
            }

            /**
            * Nearly square ratio image.
            */
            if( $current_ratio > $desired_ratio_before && $current_ratio < $desired_ratio_after )
            {
                if( $old_width > $old_height )
                {
                    $new_height = ma(angry) $width, $height );
                    $new_width = $old_width * $new_height / $old_height;
                }
                else
                {
                    $new_height = $old_height * $width / $old_width;
                }
            }

            /**
            * Portrait sized image
            */
            if( $current_ratio < $desired_ratio_before )
            {
                $new_height = $old_height * $width / $old_width;
            }

            /**
            * Find out the ratio of the original photo to it's new, thumbnail-based size
            * for both the width and the height. It's used to find out where to crop.
            */
            $width_ratio = $old_width / $new_width;
            $height_ratio = $old_height / $new_height;

            /**
            * Calculate where to crop based on the center of the image
            */
            $src_x = floor( ( ( $new_width - $width ) / 2 ) * $width_ratio );
            $src_y = round( ( ( $new_height - $height ) / 2 ) * $height_ratio );
        }
        /**
        * Don't crop the image, just resize it proportionally
        */
        else
        {
            if( $old_width > $old_height )
            {
                $ratio = ma(angry) $old_width, $old_height ) / ma(angry) $width, $height );
            }else{
                $ratio = ma(angry) $old_width, $old_height ) / min( $width, $height );
            }

            $new_width = $old_width / $ratio;
            $new_height = $old_height / $ratio;

            $new_image = imagecreatetruecolor( $new_width, $new_height );
        }

        /**
        * Where all the real magic happens
        */
        imagecopyresampled( $new_image, $img_original, 0, 0, $src_x, $src_y, $new_width, $new_height, $old_width, $old_height );

        /**
        * Save it as a JPG File with our $destination_filename param.
        */
        imagejpeg( $new_image, $destination_filename, $quality );

        /**
        * Destroy the evidence!
        */
        imagedestroy( $new_image );
        imagedestroy( $img_original );

        /**
        * Return true because it worked and we're happy. Let the dancing commence!
        */
        return true;
}


标签: php gd crop jcrop