Как обрезать изображение на стороне клиента с помощью jcrop и загрузить его?

Я пытаюсь выбрать изображение с помощью элемента управления загрузкой HTML файла, затем отрендерить изображение и обрезать его с помощью jcrop https://jsfiddle.net/govi20/spmc7ymp/

Идентификатор, использованный в коде:

target jcrop image target
фотография контроль загрузки файла
Предварительный просмотр Предварительный просмотр холста
кнопка clear_selection очистить выделенную область

<script src="./js/jquery.min.js"></script>
<script src="./js/jquery.Jcrop.js"></script>
<script src="./js/jquery.color.js"></script>

настройка jcrop.

<script type="text/javascript">
jQuery(function($){

var api;

$('#target').Jcrop({
  // start off with jcrop-light class
  bgOpacity: 0.5,
  keySupport: false,
  bgColor: 'black',
  minSize:[240,320],
  maxSize:[480,640],
  onChange : updatePreview,
  onSelect : updatePreview, 
  height:160,
  width:120,
  addClass: 'jcrop-normal'
},function(){
  api = this;
  api.setSelect([0,0,240,320]);
  api.setOptions({ bgFade: true });
  api.ui.selection.addClass('jcrop-selection');
  });

});

для очистки выбора.

jQuery('#clear_selection').click(function(){
  $('#target').Jcrop({    

      setSelect: [0,0,0,0],
    });
});

для отображения загруженного изображения на панели jcrop

function readURL(input) {

    if (input.files && input.files[0]) {
        var reader = new FileReader();
        reader.onload = function (e) {
            $('#target').attr('src', e.target.result);
            setProperties();       
        }
        reader.readAsDataURL(input.files[0]);
    }
}

function setProperties(){
   $('#target').Jcrop({         
              setSelect: [0,0,240,320]
        }); 
}
$("#photograph").change(function(){
    readURL(this);     
});

Я использую следующий код для обрезки и рендеринга изображения на холсте.

    function make_base() {
        console.log("make_base called");
        var base_image = new Image();
        base_image.src = '';
        base_image.onload = function () {
            context.drawImage(base_image, 0, 0);
        }
    }

    var canvas = document.getElementById('preview'),
    context = canvas.getContext('2d');

    make_base();
    function updatePreview(c) {
        console.log("called");
        if(parseInt(c.w) > 0) {
            // Show image preview
            var imageObj = $("#target")[0];
            var canvas = $("#preview")[0];
            var context = canvas.getContext("2d");
            context.drawImage(imageObj, c.x, c.y, c.w, c.h, 0, 0, canvas.width, canvas.height);
        }
    };

Список проблем, с которыми я здесь сталкиваюсь:

  1. Функция updatePreview не вызывается при выделении, поэтому холст не виден.
  2. Поле выбора обрезки не перетаскивается (я использую загрузочный CSS, я подозреваю проблемы с зависимостями).
  3. Canvas - это элемент HTML5, это означает, что у конечного пользователя должен быть браузер, совместимый с HTML5, я работаю над приложением, которое имеет миллионы пользователей. Принуждение пользователей к использованию новейшего браузера не является решением. Так какой же должен быть запасной механизм?

Ответ 1

Ответ Seahorsepip фантастический. Я сделал много улучшений в ответе без отступлений.

http://jsfiddle.net/w1Lh4w2t/

Я бы порекомендовал не делать эту странную скрытую вещь png, когда объект Image работает так же хорошо (если мы не поддерживаем запасные варианты).

var jcrop_api;
var canvas;
var context;
var image;
var prefsize;

Хотя и в этом случае нам лучше вывести эти данные из холста в конце и поместить их в это поле только в конце.

function loadImage(input) {
  if (input.files && input.files[0]) {
    var reader = new FileReader();
    reader.onload = function(e) {
      image = new Image();
      image.src = e.target.result;
      validateImage();
    }
    reader.readAsDataURL(input.files[0]);
  }
}

Но если вам нужно больше функций, чем просто обрезка, если мы прикрепим jcrop к вставленному холсту (который мы уничтожаем с помощью jcrop при обновлении). Мы можем легко сделать все, что мы можем сделать с холстом, затем снова выполнить validateImage() и увидеть обновленное изображение на месте.

function validateImage() {
  if (canvas != null) {
    image = new Image();
    image.src = canvas.toDataURL('image/png');
  }
  if (jcrop_api != null) {
    jcrop_api.destroy();
  }
  $("#views").empty();
  $("#views").append("<canvas id=\"canvas\">");
  canvas = $("#canvas")[0];
  context = canvas.getContext("2d");
  canvas.width = image.width;
  canvas.height = image.height;
  context.drawImage(image, 0, 0);
  $("#canvas").Jcrop({
    onSelect: selectcanvas,
    onRelease: clearcanvas,
    boxWidth: crop_max_width,
    boxHeight: crop_max_height
  }, function() {
    jcrop_api = this;
  });
  clearcanvas();
}

Затем при отправке мы отправляем любые ожидающие операции, такие как applyCrop() или applyScale(), добавляя данные в скрытые поля для резервных элементов, если они нам нужны. Затем у нас есть система, в которой мы можем легко просто изменить холст любым способом, а затем, когда мы отправляем данные холста, они отправляются правильно.

function applyCrop() {
  canvas.width = prefsize.w;
  canvas.height = prefsize.h;
  context.drawImage(image, prefsize.x, prefsize.y, prefsize.w, prefsize.h, 0, 0, canvas.width, canvas.height);
  validateImage();
}

Холст добавляется в div view.

 <div id="views"></div>

Чтобы поймать прикрепленный файл в PHP (drupal), я использовал что-то вроде:

    function makeFileManaged() {
        if (!isset($_FILES['croppedfile']))
            return NULL;
        $path = $_FILES['croppedfile']['tmp_name'];
        if (!file_exists($path))
            return NULL;
        $result_filename = $_FILES['croppedfile']['name'];
        $uri = file_unmanaged_move($path, 'private://' . $result_filename, FILE_EXISTS_RENAME);
        if ($uri == FALSE)
            return NULL;
        $file = File::Create([
                    'uri' => $uri,
        ]);
        $file->save();
        return $file->id();
    }

Ответ 2

Вот основной код HTML 5:

https://jsfiddle.net/zm7e0jev/

Этот код обрезает изображение, показывает предварительный просмотр и устанавливает значение элемента ввода в обрезанное изображение в кодировке base64.

Вы можете получить файл изображения в php следующим образом:

//File destination
$destination = "/folder/cropped_image.png";
//Get convertable base64 image string
$image_base64 = $_POST["png"];
$image_base64 = str_replace("data:image/png;base64,", "", $image_base64);
$image_base64 = str_replace(" ", "+", $image_base64);
//Convert base64 string to image data
$image = base64_decode($image_base64);
//Save image to final destination
file_put_contents($destination, $image);

Отправка строки изображения base64 в качестве переменной записи имеет ограничения по размеру публикации на сервере, а кодировка base64 делает размер файла обрезанного изображения еще больше (~ 33%), чем необработанные данные обрезанного изображения, что увеличивает продолжительность загрузки..

Чтобы установить ограничение размера сообщения: Каков предел размера запроса на публикацию?

Имейте в виду, что в качестве примера DoS-атаки можно использовать ограничение на максимальный размер сообщения.

Вместо этого я предлагаю преобразовать обрезанное изображение base64 в большой двоичный объект, а затем добавить его в форму при отправке в виде файла:

https://jsfiddle.net/g3ysk6sf/

Затем вы можете получить файл изображения в php следующим образом:

//File destination
$destination = "/folder/cropped_image.png";
//Get uploaded image file it temporary name
$image_tmp_name = $_FILES["cropped_image"]["tmp_name"][0];
//Move temporary file to final destination
move_uploaded_file($image_tmp_name, $destination);

Обновление:

FormData() только частично поддерживается в IE10 и не поддерживается в более старых версиях IE

Поэтому я предлагаю отправить строку base64 в качестве запасного варианта, хотя это вызовет проблемы с большими изображениями, поэтому необходимо проверить размер файла и показать всплывающее окно с ошибкой, когда изображение превышает определенный размер.

Я опубликую обновление с резервным кодом ниже, когда он заработает.

Обновление 2:

Я добавил запасной вариант для IE10 и ниже:

https://jsfiddle.net/oupxo3pu/

Единственным ограничением является размер изображения, который может быть передан при использовании IE10 и ниже, в случае, если размер изображения слишком велик, js-код выдаст ошибку. Максимальный размер для значений записей различен для каждого сервера, в коде js есть переменная для установки максимального размера.

Приведенный ниже код php адаптирован для работы с указанным выше запасным вариантом:

//File destination
$destination = "/folder/cropped_image.png";
if($_POST["png"]) {//IE10 and below
    //Get convertable base64 image string
    $image_base64 = $_POST["png"];
    $image_base64 = str_replace("data:image/png;base64,", "", $image_base64);
    $image_base64 = str_replace(" ", "+", $image_base64);
    //Convert base64 string to image data
    $image = base64_decode($image_base64);
    //Save image to final destination
    file_put_contents($destination, $image);
} else if($_FILES["cropped_image"]) {//IE11+ and modern browsers
    //Get uploaded image file it temporary name
    $image_tmp_name = $_FILES["cropped_image"]["tmp_name"][0];
    //Move temporary file to final destination
    move_uploaded_file($image_tmp_name, $destination);
}
Для элемента canvas еще нет запасного кода, я его изучаю.
Ограничение размера записей в качестве запасного варианта для старых браузеров - одна из причин, по которой я сам отказался от поддержки старых браузеров.

Обновление 3:

Откат, который я рекомендую для элемента canvas в IE8:

http://flashcanvas.net/

Он поддерживает все функции холста, необходимые для обрезки кода.

Имейте в виду, это требует вспышки. Существует резервный холст (explorercanvas), для которого не требуется флэш, но он не поддерживает функцию toDataURL(), которая нам нужна для сохранения обрезанного изображения.