web-dev-qa-db-fra.com

Comment recadrer l'image côté client à l'aide de jcrop et la télécharger?

Ce que j'essaie de faire est de sélectionner l'image en utilisant le contrôle de téléchargement de fichier HTML, puis de rendre l'image et de la recadrer en utilisant jcrop https://jsfiddle.net/govi20/spmc7ymp/

Identifiant utilisé dans le code:

target jcrop image target
photographie contrôle de téléchargement de fichier
aperçu aperçu du canevas
Bouton clear_selection pour effacer la zone sélectionnée

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

configuration du 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');
  });

});

pour effacer la sélection.

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

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

afficher l'image téléchargée sur le panneau 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);     
});

J'utilise le code suivant pour recadrer et rendre une image sur la toile.

    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);
        }
    };

Voici la liste des problèmes auxquels je suis confronté:

  1. la fonction updatePreview n'est pas appelée lors de la sélection, donc le canevas n'est pas visible.
  2. la zone de sélection de recadrage n'est pas déplaçable (j'utilise bootstrap CSS, je soupçonne des problèmes de dépendance).
  3. La toile est HTML5 élément, cela signifie que l'utilisateur final doit avoir HTML5 navigateur compatible, je travaille sur une application qui compte des millions d'utilisateurs. Forcer les utilisateurs à utiliser le dernier navigateur n'est pas une solution. Alors, quel devrait être le mécanisme de secours?
12
Govinda Sakhare

La réponse de Seahorsepip est fantastique. J'ai apporté de nombreuses améliorations à la réponse non de secours.

http://jsfiddle.net/w1Lh4w2t/

Je recommanderais de ne pas faire cette étrange chose png cachée, lorsqu'un objet Image fonctionne aussi bien (tant que nous ne prenons pas en charge les solutions de secours).

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

Même si c'est le cas, vous feriez mieux de retirer ces données du canevas à la fin et de les mettre dans ce champ uniquement à la fin.

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]);
  }
}

Mais, si vous voulez plus de fonctions que simplement recadrer, si nous attachons le jcrop à un canevas inséré (que nous détruisons avec le jcrop lors de l'actualisation). Nous pouvons facilement faire tout ce que nous pouvons faire avec un canevas, puis valider à nouveauImage () et avoir l'image mise à jour visible en place.

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

Ensuite, lors de la soumission, nous soumettons toutes les opérations en attente, comme applyCrop () ou applyScale (), en ajoutant des données dans des champs cachés pour les trucs de secours, si nous avons ces choses nécessaires. Nous avons alors un système que nous pouvons facilement modifier simplement le canevas, de quelque manière que ce soit, puis lorsque nous soumettons les données du canevas sont envoyées correctement.

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

Le canevas est ajouté à une vue div.

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

Pour attraper le fichier joint en PHP (drupal), j'ai utilisé quelque chose comme:

    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();
    }
18
Tatarize

Voici le code html 5 de base:

https://jsfiddle.net/zm7e0jev/

Ce code recadre l'image, affiche un aperçu et définit la valeur d'un élément d'entrée sur l'image recadrée encodée en base64.

Vous pouvez récupérer le fichier image en php de la manière suivante:

//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);

La soumission de la chaîne d'image base64 en tant que variable de publication a ses limites de taille de publication de serveur et le codage base64 augmente la taille du fichier d'image recadrée (~ 33%), puis les données brutes de l'image recadrée seraient ce qui rend le téléchargement encore plus long.

Pour définir la taille limite de publication: Quelle est la taille limite d'une demande de publication?

Gardez à l'esprit qu'une limite de taille de publication accrue peut être utilisée abusivement pour une attaque DoS, par exemple.

Au lieu de cela, je suggère de convertir l'image recadrée base64 en un objet de données, puis de l'ajouter au formulaire lors de la soumission en tant que fichier:

https://jsfiddle.net/g3ysk6sf/

Ensuite, vous pouvez récupérer le fichier image en php de la manière suivante:

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

Mise à jour:

FormData () n'est que partiellement pris en charge dans IE10 et n'est pas pris en charge dans les anciennes versions d'IE

Je suggère donc d'envoyer la chaîne base64 en tant que solution de rechange, bien que cela cause des problèmes avec des images plus grandes, il doit donc vérifier la taille du fichier et afficher une fenêtre contextuelle d'erreur lorsque l'image dépasse une taille spécifique.

Je publierai une mise à jour avec le code de secours ci-dessous lorsque je le ferai fonctionner.

Mise à jour 2:

J'ai ajouté un repli pour IE10 et ci-dessous:

https://jsfiddle.net/oupxo3pu/

La seule limitation est la taille d'image qui peut être soumise lors de l'utilisation d'IE10 et en dessous, au cas où la taille de l'image serait trop grande, le code js générera une erreur. La taille maximale à utiliser pour les valeurs de publication est différente entre chaque serveur, le code js a une variable pour définir la taille maximale.

Le code php ci-dessous est adapté pour fonctionner avec le repli ci-dessus:

//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's temporary name
    $image_tmp_name = $_FILES["cropped_image"]["tmp_name"][0];
    //Move temporary file to final destination
    move_uploaded_file($image_tmp_name, $destination);
}
Il n'y a pas encore de code de secours pour l'élément canvas, j'y cherche.
La limitation de la taille des messages dans la solution de secours pour les anciens navigateurs est l'une des raisons pour lesquelles j'ai moi-même abandonné la prise en charge des anciens navigateurs.

Mise à jour 3:

Le repli que je recommande pour l'élément canvas dans IE8:

http://flashcanvas.net/

Il prend en charge toutes les fonctions de canevas dont le code de recadrage a besoin.

Gardez à l'esprit qu'il nécessite un flash. Il existe un repli de canevas (explorercanvas) qui ne nécessite pas de flash mais il ne prend pas en charge la fonction toDataURL () dont nous avons besoin pour enregistrer notre image recadrée.

18
seahorsepip