Проблемы с обрезкой и изменением размера изображения

#php #gd #crop #jcrop

#php #gd #обрезка #jcrop

Вопрос:

Я пытаюсь создать уменьшенное изображение для большего изображения. Я использую Jcrop для получения координат обрезки, но я не могу превратить это в правильно обрезанное уменьшенное изображение. Я правильно настроил Jcrop, и он отправляет координаты x-y вместе с размером поля, но я не могу понять, как обрезать из этого.

У меня есть путь к изображению на моем сервере, а также 4 координаты, а также ширина и высота квадратного прямоугольника, который они создают (я зафиксировал соотношение сторон 1: 1, поскольку мне нужны квадратные эскизы). Затем я отправляю это через Ajax в свой скрипт обрезки PHP, но я не могу заставить его обрезать на основе того, что установлено.

Это то, что у меня есть до сих пор:

 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';

}
  

Я пробовал изменять всевозможные вещи, imagecopyresampled но не могу заставить его обрезать в соответствии с тем, что отправляет Jcrop. Если я использую $srcWidth , и $srcHeight он сохраняет соотношение сторон исходного изображения и сжимает его в квадрат 180 пикселей. Но, похоже, он не изменяет его размер или не обрезает его из нужного места.

Может кто-нибудь, пожалуйста, помочь мне определить, какие цифры я должен использовать где? Я весь день бился об это головой.


Редактировать

Вот остальная часть кода. Сначала JavaScript, который запускает JCrop. Он запускается после загрузки файла Ajax для создания миниатюры этого изображения:

Это загрузка функции Ajax, которая в конце вызывает обрезку.

 $(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();

    });

}
  

Это рабочий, который фактически создает содержимое в лайтбоксе и инициирует 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;
    }

}
  

Обновить

Похоже, частью проблемы было изменение размера изображения. Я менял размер изображения, чтобы оно соответствовало экрану, я думал, что JCrop позаботится об этом для меня, но, похоже, вы должны сообщить JCrop исходные размеры изображения. Я добавил параметр true size при инициализации JCrop, и, похоже, мы почти на месте.

Для изображений меньшего размера, размером менее 1000 пикселей, обрезка, похоже, работает отлично. Но для больших (1000 пикселей ) он создает черные изображения. Этого не происходит, когда я использую его с демонстрационным скриптом JCrop, но единственное различие между ними заключается в том, что один выводит файл на экран, а другой сохраняет его. Я не вижу никакой другой разницы или не понимаю, почему это может произойти.

ОБНОВЛЕНИЕ 2 Похоже, это влияет только в том случае, если я запускаю код через Ajax. Если я запускаю точно такую же функцию, просто размещая на странице и запуская ее вверху, эскиз создается идеально каждый раз, независимо от размера исходного изображения или размера рамки, которую я рисую.

Ответ №1:

В вашем коде $crop['w'] и $crop['h'] для источника;

$tmpWidth и $tmpHeight предназначены для назначения;

поэтому вы должны переключать их в соответствии с imagecopyresampled функцией (http://php.net//manual/fr/function.imagecopyresampled.php ).

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

редактировать 2

Ваше целевое изображение должно быть установлено не с $crop данными, а с нужным размером :

 $dstRes = imagecreatetruecolor(180, 180);
  

И я не уверен, какое значение у вас есть, $tmpWidth и $tmpHeight но они оба должны быть 180, так как это нужный вам размер :

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

Редактировать 3

Хорошо, последняя попытка, у меня есть рабочий код для jcrop, но я не использую его внутри функции, он все еще работает так же. Я проверил построчно ваш код, и вот что у меня есть. Я удалил все, что касается ratio, Jcrop управляет этим, нет необходимости делать это с помощью PHP. Правильное dest-изображение, правильное imagecopyresampled , выглядит хорошо для меня :/

 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';

}
  

Комментарии:

1. Это не работает. Это не изменяет размер изображения, поэтому обрезка сильно увеличивается. Если я выбираю обрезку 300 x 300, она должна обрезать ее, а затем изменить ее размер до 180 x 180, но это не так.

2. Это тоже не работает. Верхний левый угол не совпадает с тем местом, где я размещаю его на кадрировщике, и он также не обрезается при правильном увеличении.

3. Я скопировал ваш код и вставил его, чтобы убедиться, что он правильный, и все равно получил то же самое. Нет ли какой-либо команды, в которой я мог бы ввести 4 координаты (верхний левый, верхний правый, нижний левый, нижний правый) и получить их, а затем изменить размер до 180?

4. Также мне интересно, связано ли это с масштабированием изображения? Jcrop должен быть в состоянии справиться с этим, но мне интересно, правильно ли я это делаю?

5. Ну, разве я не чувствую себя глупо. Я фильтровал целые числа, но он отправляет значения с плавающей запятой. Я не знаю, почему это работало для изображений меньшего размера, они должны отправлять целые числа, а не числа с плавающей запятой, или фильтр округлил их или что-то в этом роде. Но теперь он отсортирован. Это работает. Спасибо за всю вашу помощь.

Ответ №2:

Попробуйте использовать эту функцию во время изменения размера изображения

Просто вызовите функцию следующим образом:

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

Эта функция также включает в себя функции обрезки. Вы можете включить или отключить параметр обрезки, передав в него значение true или false ($crop = true / false).

 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 amp;amp; $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;
}
  

Комментарии:

1. Как мне указать, откуда обрезать с помощью этого скрипта? Похоже, он не принимает никаких параметров x, y.

2. Для обрезки вы должны установить $destination_filename в качестве абсолютного пути, и он автоматически обрезает изображение из центра основного изображения.

3. Я не хочу, чтобы он обрезался из центра основного изображения, я хочу указать координаты места для обрезки.