Comment enregistrer une toile HTML5 en tant qu'image sur un serveur?

261

Je travaille sur un projet d'art génératif où j'aimerais permettre aux utilisateurs de sauvegarder les images résultantes d'un algorithme. L'idée générale est:

  • Créer une image sur un canevas HTML5 à l'aide d'un algorithme génératif
  • Une fois l'image terminée, autorisez les utilisateurs à enregistrer le canevas en tant que fichier image sur le serveur
  • Permettez à l'utilisateur de télécharger l'image ou de l'ajouter à une galerie de pièces produites à l'aide de l'algorithme.

Cependant, je suis coincé sur la deuxième étape. Après un peu d'aide de Google, j'ai trouvé ce billet de blog , qui semblait être exactement ce que je voulais:

Ce qui a conduit au code JavaScript:

function saveImage() {
  var canvasData = canvas.toDataURL("image/png");
  var ajax = new XMLHttpRequest();

  ajax.open("POST", "testSave.php", false);
  ajax.onreadystatechange = function() {
    console.log(ajax.responseText);
  }
  ajax.setRequestHeader("Content-Type", "application/upload");
  ajax.send("imgData=" + canvasData);
}

et PHP correspondant (testSave.php):

<?php
if (isset($GLOBALS["HTTP_RAW_POST_DATA"])) {
  $imageData = $GLOBALS['HTTP_RAW_POST_DATA'];
  $filteredData = substr($imageData, strpos($imageData, ",") + 1);
  $unencodedData = base64_decode($filteredData);
  $fp = fopen('/path/to/file.png', 'wb');

  fwrite($fp, $unencodedData);
  fclose($fp);
}
?>

Mais cela ne semble rien faire du tout.

Plus Google recherche ce blog qui est basé sur le tutoriel précédent. Pas très différent, mais peut-être mérite un essai:

$data = $_POST['imgData'];
$file = "/path/to/file.png";
$uri = substr($data,strpos($data, ",") + 1);

file_put_contents($file, base64_decode($uri));
echo $file;

Celui-ci crée un fichier (ouais) mais il est corrompu et ne semble rien contenir. Il semble également être vide (taille de fichier de 0).

Y a-t-il quelque chose de vraiment évident que je fais mal? Le chemin où je stocke mon fichier est accessible en écriture, donc ce n'est pas un problème, mais rien ne semble se produire et je ne sais pas vraiment comment déboguer cela.

Éditer

Suite au lien de Salvidor Dali, j'ai changé la demande AJAX pour qu'elle soit:

function saveImage() {
  var canvasData = canvas.toDataURL("image/png");
  var xmlHttpReq = false;

  if (window.XMLHttpRequest) {
    ajax = new XMLHttpRequest();
  }
  else if (window.ActiveXObject) {
    ajax = new ActiveXObject("Microsoft.XMLHTTP");
  }

  ajax.open("POST", "testSave.php", false);
  ajax.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
  ajax.onreadystatechange = function() {
    console.log(ajax.responseText);
  }
  ajax.send("imgData=" + canvasData);
}

Et maintenant, le fichier image est créé et n'est pas vide! Il semble que le type de contenu soit important et que le changer pour x-www-form-urlencodedautoriser l'envoi des données d'image.

La console renvoie la chaîne (plutôt volumineuse) de code base64 et le fichier de données est ~ 140 Ko. Cependant, je ne peux toujours pas l'ouvrir et il ne semble pas être formaté en tant qu'image.

nathan lachenmyer
la source
Le premier article de blog indique les utilisations ajax.send(canvasData );que vous avez fournies pendant que vous l'utilisez ajax.send("imgData="+canvasData);. Par conséquent, $GLOBALS["HTTP_RAW_POST_DATA"]ce ne sera pas ce que vous attendez, vous devriez probablement l'utiliser $_POST['imgData'].
Matteus Hemström
stackoverflow.com/questions/3592164/…
Diodeus - James MacFarlane
Diodeus: J'utilise déjà la technique suggérée dans ce fil; cependant, ils n'ont pas fourni de détails supplémentaires sur la mise en œuvre et c'est là que je suis bloqué.
nathan lachenmyer
Lorsque je fais écho aux informations du fichier ( $datadans le deuxième code php), tout ce que je reçois est une ligne vierge. Pourquoi serait-ce? Il semble que les données envoyées ne soient peut-être pas correctes, mais il semble que je les envoie comme le montrent les exemples ...
nathan lachenmyer
Le code PHP peut être simplifié et rendu plus robuste en utilisant PHP-FileUpload avec son DataUriUploadcomposant à la place. Il est documenté ici et est livré avec plusieurs moyens supplémentaires de validation et d'application de la sécurité.
caw

Réponses:

242

Voici un exemple de réalisation de ce dont vous avez besoin:

1) Dessinez quelque chose (tiré du tutoriel de toile )

<canvas id="myCanvas" width="578" height="200"></canvas>
<script>
    var canvas = document.getElementById('myCanvas');
    var context = canvas.getContext('2d');

    // begin custom shape
    context.beginPath();
    context.moveTo(170, 80);
    context.bezierCurveTo(130, 100, 130, 150, 230, 150);
    context.bezierCurveTo(250, 180, 320, 180, 340, 150);
    context.bezierCurveTo(420, 150, 420, 120, 390, 100);
    context.bezierCurveTo(430, 40, 370, 30, 340, 50);
    context.bezierCurveTo(320, 5, 250, 20, 250, 50);
    context.bezierCurveTo(200, 5, 150, 20, 170, 80);

    // complete custom shape
    context.closePath();
    context.lineWidth = 5;
    context.fillStyle = '#8ED6FF';
    context.fill();
    context.strokeStyle = 'blue';
    context.stroke();
</script>

2) Convertir l'image de canevas au format URL (base64)

var dataURL = canvas.toDataURL();

3) Envoyez-le à votre serveur via Ajax

$.ajax({
  type: "POST",
  url: "script.php",
  data: { 
     imgBase64: dataURL
  }
}).done(function(o) {
  console.log('saved'); 
  // If you want the file to be visible in the browser 
  // - please modify the callback in javascript. All you
  // need is to return the url to the file, you just saved 
  // and than put the image in your browser.
});

3) Enregistrez base64 sur votre serveur en tant qu'image (voici comment faire cela en PHP , les mêmes idées sont dans tous les langages. Côté serveur en PHP peut être trouvé ici ):

Salvador Dali
la source
1
Je veux dire que le fichier est là mais il ne s'ouvre pas dans mon navigateur ou dans une visionneuse d'images si je le télécharge.
nathan lachenmyer
You send it to your server as an ajax requestcette méthode nécessite-t-elle la confirmation de l'utilisateur? Ou puis-je envoyer silencieusement l'image de la toile au serveur?
MyTitle
Je reçois une erreur sur la console Web. [16: 53: 43.227] SecurityError: l'opération n'est pas sécurisée. @ sharevi.com/staging/canvas.html:43 la Cette connexion n'est pas sécurisée. Y a-t-il quelque chose à faire? /// MISE À JOUR Je pense que je sais pourquoi, j'utilisais des images
interdomaines
1
mais alpha est 0, ce qui le rend sans remplissage, c'est complètement transparent. quelle que soit la valeur des trois premiers.
Abel D
68

J'ai joué avec ça il y a deux semaines, c'est très simple. Le seul problème est que tous les tutoriels ne parlent que de l'enregistrement local de l'image. Voici comment je l'ai fait:

1) J'ai créé un formulaire pour pouvoir utiliser une méthode POST.

2) Lorsque l'utilisateur a fini de dessiner, il peut cliquer sur le bouton "Enregistrer".

3) Lorsque le bouton est cliqué, je prends les données d'image et les mets dans un champ caché. Après cela, je soumets le formulaire.

document.getElementById('my_hidden').value = canvas.toDataURL('image/png');
document.forms["form1"].submit();

4) Lorsque le formulaire est soumis, j'ai ce petit script php:

<?php 
$upload_dir = somehow_get_upload_dir();  //implement this function yourself
$img = $_POST['my_hidden'];
$img = str_replace('data:image/png;base64,', '', $img);
$img = str_replace(' ', '+', $img);
$data = base64_decode($img);
$file = $upload_dir."image_name.png";
$success = file_put_contents($file, $data);
header('Location: '.$_POST['return_url']);
?>
user568021
la source
1
est-ce correct si j'utilise canvas.toDataURL ()? J'ai une extension de fichier dynamique, comme jpeg, png, gif. J'ai essayé canvas.toDataURL ('image / jpg') mais cela ne fonctionne pas. Qu'est-ce qui ne va pas?
Ivo San
J'obtenais une erreur 413 (demande trop importante) lors de l'envoi de l'image à partir du canevas par AJAX. Ensuite, cette approche m'a aidé à faire passer les images.
Raiyan
1
Une raison pour laquelle celui-ci me donne un fichier png de 0 Ko?
prismspecs
3
Pour jpg, vous devez faire canvas.toDataURL ('image / jpeg') - au moins c'est ce qui m'a fait sur Chrome.
Chris
Merci, c'est ce dont j'avais besoin: D
FosAvance
21

Je pense que vous devriez transférer l'image en base64 vers l'image avec blob, car lorsque vous utilisez l'image base64, cela prend beaucoup de lignes de journal ou beaucoup de lignes seront envoyées au serveur. Avec blob, c'est seulement le fichier. Vous pouvez utiliser ce code ci-dessous:

dataURLtoBlob = (dataURL) ->
  # Decode the dataURL
  binary = atob(dataURL.split(',')[1])
  # Create 8-bit unsigned array
  array = []
  i = 0
  while i < binary.length
    array.push binary.charCodeAt(i)
    i++
  # Return our Blob object
  new Blob([ new Uint8Array(array) ], type: 'image/png')

Et le code canvas ici:

canvas = document.getElementById('canvas')
file = dataURLtoBlob(canvas.toDataURL())

Après cela, vous pouvez utiliser ajax avec Form:

  fd = new FormData
  # Append our Canvas image file to the form data
  fd.append 'image', file
  $.ajax
    type: 'POST'
    url: '/url-to-save'
    data: fd
    processData: false
    contentType: false

Ce code utilise la syntaxe CoffeeScript.

si vous souhaitez utiliser javascript, veuillez coller le code sur http://js2.coffee

ThienSuBS
la source
12

Envoyer une image de canevas à PHP:

var photo = canvas.toDataURL('image/jpeg');                
$.ajax({
  method: 'POST',
  url: 'photo_upload.php',
  data: {
    photo: photo
  }
});

Voici le script PHP:
photo_upload.php

<?php

    $data = $_POST['photo'];
    list($type, $data) = explode(';', $data);
    list(, $data)      = explode(',', $data);
    $data = base64_decode($data);

    mkdir($_SERVER['DOCUMENT_ROOT'] . "/photos");

    file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/photos/".time().'.png', $data);
    die;
?>
vikram jeet singh
la source
9

Si vous souhaitez enregistrer des données dérivées d'une canvas.toDataURL()fonction Javascript , vous devez convertir les blancs en plus. Si vous ne le faites pas, les données décodées sont corrompues:

<?php
  $encodedData = str_replace(' ','+',$encodedData);
  $decocedData = base64_decode($encodedData);
?>

http://php.net/manual/ro/function.base64-decode.php

MaloMax
la source
7

J'ai travaillé sur quelque chose de similaire. J'ai dû convertir une image encodée en Base64 sur toile Uint8Array Blob.

function b64ToUint8Array(b64Image) {
   var img = atob(b64Image.split(',')[1]);
   var img_buffer = [];
   var i = 0;
   while (i < img.length) {
      img_buffer.push(img.charCodeAt(i));
      i++;
   }
   return new Uint8Array(img_buffer);
}

var b64Image = canvas.toDataURL('image/jpeg');
var u8Image  = b64ToUint8Array(b64Image);

var formData = new FormData();
formData.append("image", new Blob([ u8Image ], {type: "image/jpg"}));

var xhr = new XMLHttpRequest();
xhr.open("POST", "/api/upload", true);
xhr.send(formData);
Joseph D.
la source
4

En plus de la réponse de Salvador Dali:

côté serveur, n'oubliez pas que les données sont au format de chaîne base64 . C'est important car dans certains langages de programmation, vous devez dire explicitement que cette chaîne doit être considérée comme des octets et non comme une simple chaîne Unicode.

Sinon, le décodage ne fonctionnera pas: l'image sera enregistrée mais ce sera un fichier illisible.

Ardine
la source