¿Cómo guardar un lienzo HTML5 como imagen en un servidor?

261

Estoy trabajando en un proyecto de arte generativo en el que me gustaría permitir a los usuarios guardar las imágenes resultantes de un algoritmo. La idea general es:

  • Cree una imagen en un lienzo HTML5 utilizando un algoritmo generativo
  • Cuando se completa la imagen, permita a los usuarios guardar el lienzo como un archivo de imagen en el servidor
  • Permita al usuario descargar la imagen o agregarla a una galería de piezas producidas utilizando el algoritmo.

Sin embargo, estoy atrapado en el segundo paso. Después de un poco de ayuda de Google, encontré esta publicación de blog , que parecía ser exactamente lo que quería:

Lo que condujo al código 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);
}

y PHP correspondiente (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);
}
?>

Pero esto no parece hacer nada en absoluto.

Más Google muestra esta publicación de blog que se basa en el tutorial anterior. No es muy diferente, pero quizás valga la pena intentarlo:

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

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

Este crea un archivo (yay) pero está dañado y no parece contener nada. También parece estar vacío (tamaño de archivo de 0).

¿Hay algo realmente obvio que estoy haciendo mal? La ruta donde estoy almacenando mi archivo es editable, por lo que no es un problema, pero parece que no está sucediendo nada y no estoy realmente seguro de cómo depurar esto.

Editar

Siguiendo el enlace de Salvidor Dali, cambié la solicitud de AJAX para que fuera:

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

¡Y ahora el archivo de imagen está creado y no está vacío! Parece que el tipo de contenido es importante y que cambiarlo para x-www-form-urlencodedpermitir que se envíen los datos de la imagen.

La consola devuelve la cadena (bastante grande) de código base64 y el archivo de datos es de ~ 140 kB. Sin embargo, todavía no puedo abrirlo y parece que no está formateado como una imagen.

Nathan Lachenmyer
fuente
La primera publicación de blog que usa ajax.send(canvasData );mientras la usa como me gusta ajax.send("imgData="+canvasData);. Por $GLOBALS["HTTP_RAW_POST_DATA"]lo tanto , no será lo que espera, probablemente debería usar $_POST['imgData'].
Matteus Hemström
stackoverflow.com/questions/3592164/…
Diodeus - James MacFarlane
Diodeus: ya estoy usando la técnica que se sugirió en ese hilo; sin embargo, no pudieron proporcionar más detalles sobre la implementación y ahí es donde me estoy atorando.
nathan lachenmyer
Cuando hago eco de la información del archivo ( $dataen el segundo código php), todo lo que me devuelve es una línea en blanco. ¿Por qué sería eso? Parece como si ser quizás los datos enviados no es correcto, pero parece que yo estoy enviando al igual que los ejemplos muestran ...
Nathan lachenmyer
El código PHP puede simplificarse y hacerse más robusto usando PHP-FileUpload con su DataUriUploadcomponente. Está documentado aquí y viene con varios medios adicionales de validación y aplicación de seguridad.
caw

Respuestas:

242

Aquí hay un ejemplo de cómo lograr lo que necesita:

1) Dibuja algo (tomado del tutorial de lienzo )

<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 imagen de lienzo a formato URL (base64)

var dataURL = canvas.toDataURL();

3) Envíalo a tu servidor a través de 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) Guarde base64 en su servidor como una imagen (aquí está cómo hacer esto en PHP , las mismas ideas están en todos los idiomas. El lado del servidor en PHP se puede encontrar aquí ):

Salvador Dalí
fuente
1
Quiero decir que el archivo está allí, pero no se abre en mi navegador o en ningún visor de imágenes si lo descargo.
nathan lachenmyer
You send it to your server as an ajax request¿Este método requiere la confirmación del usuario? ¿O puedo enviar silenciosamente la imagen del lienzo al servidor?
MyTitle
Me sale un error en la consola web. [16: 53: 43.227] SecurityError: la operación es insegura. @ sharevi.com/staging/canvas.html:43 the Esta conexión no es segura . ¿Hay algo que deba hacerse? /// ACTUALIZACIÓN Creo que sé por qué, estaba usando imágenes de dominio cruzado
Nikolaos Vassos
1
pero alfa es 0, lo que lo hace sin relleno, que es completamente transparente. no importa cuáles son los tres primeros valores.
Abel D
68

Jugué con esto hace dos semanas, es muy simple. El único problema es que todos los tutoriales solo hablan sobre guardar la imagen localmente. Así es como lo hice:

1) Configuré un formulario para poder usar un método POST.

2) Cuando el usuario termina de dibujar, puede hacer clic en el botón "Guardar".

3) Cuando se hace clic en el botón, tomo los datos de la imagen y los coloco en un campo oculto. Después de eso presento el formulario.

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

4) Cuando se envía el formulario, tengo este pequeño 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']);
?>
usuario568021
fuente
1
¿está bien si uso canvas.toDataURL ()? Tengo una extensión de archivo dinámica, como jpeg, png, gif. Intenté canvas.toDataURL ('image / jpg') pero no funciona. Que pasa
Ivo San
Estaba recibiendo un error 413 (solicitud demasiado grande) al intentar enviar una imagen desde el lienzo por AJAX. Entonces este enfoque me ayudó a obtener las imágenes.
Raiyan
1
¿Alguna razón por la que este me está dando un archivo png de 0kb?
prismspecs
3
Para jpg, necesitarías hacer canvas.toDataURL ('image / jpeg') , al menos eso fue lo que me hizo en Chrome.
Chris
Gracias, eso es lo que necesitaba: D
FosAvance
21

Creo que debe transferir la imagen en base64 a la imagen con blob, porque cuando usa la imagen base64, se necesitan muchas líneas de registro o se enviará mucha línea al servidor. Con blob, solo es el archivo. Puede usar este código a continuación:

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

Y el código del lienzo aquí:

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

Después de eso, puedes usar ajax con 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

Este código utiliza la sintaxis de CoffeeScript.

si desea usar javascript, pegue el código en http://js2.coffee

ThienSuBS
fuente
12

Enviar imagen de lienzo a PHP:

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

Aquí está el 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
fuente
9

Si desea guardar datos derivados de una canvas.toDataURL()función Javascript , debe convertir los espacios en blanco en más. Si no lo hace, los datos decodificados están dañados:

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

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

MaloMax
fuente
7

He trabajado en algo similar. Tuve que convertir la imagen de Canvas codificada en Base64Uint8Array 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.
fuente
4

Además de la respuesta de Salvador Dalí:

en el lado del servidor, no olvide que los datos vienen en formato de cadena base64 . Es importante porque en algunos lenguajes de programación debes decir explícitamente que esta cadena debe considerarse como bytes no como una cadena Unicode simple.

De lo contrario, la decodificación no funcionará: la imagen se guardará pero será un archivo ilegible.

Ardine
fuente