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

Resuelto nathan lachenmyer asked hace 54 años • 8 respuestas

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 Canvas HTML5 usando un algoritmo generativo
  • Cuando la imagen esté completa, 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 estancado 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 llevó 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.

Si buscas más en Google, aparece esta publicación de blog que se basa en el tutorial anterior. No 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 almaceno mi archivo se puede escribir, por lo que eso no es un problema, pero parece que no sucede nada y no estoy realmente seguro de cómo depurarlo.

Editar

Siguiendo el enlace de Salvidor Dalí cambié la solicitud AJAX para que sea:

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 importa y que cambiarlo x-www-form-urlencodedpermite enviar los datos de la imagen.

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

nathan lachenmyer avatar Jan 01 '70 08:01 nathan lachenmyer
Aceptado

A continuación se muestra 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>
Expandir fragmento

  1. Convertir imagen de lienzo a formato URL (base64)

        // script
    
        var dataURL = canvas.toDataURL();
    
  2. Envíalo a tu servidor vía 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.
    });
Expandir fragmento

  1. Guarde base64 en su servidor como una imagen (aquí se explica 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 Dali avatar Nov 02 '2012 15:11 Salvador Dali

Jugué con esto hace dos semanas, es muy simple. El único problema es que todos los tutoriales solo hablan de 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 envío 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']);
?>
user568021 avatar Nov 02 '2012 16:11 user568021

Creo que deberías convertir la imagen a base64 y luego a Blob y enviarla al servidor. Cuando utiliza imágenes base64, se enviarán muchas líneas al servidor. Con blob, es solo el archivo.

Puede utilizar este código a continuación:

function dataURLtoBlob(dataURL) {
  let array, binary, i, len;
  binary = atob(dataURL.split(',')[1]);
  array = [];
  i = 0;
  len = binary.length;
  while (i < len) {
    array.push(binary.charCodeAt(i));
    i++;
  }
  return new Blob([new Uint8Array(array)], {
    type: 'image/png'
  });
};

Y el código del lienzo aquí:

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

Después de eso puedes usar ajax con el formulario:

const fd = new FormData;

fd.append('image', file);

$.ajax({
  type: 'POST',
  url: '/url-to-save',
  data: fd,
  processData: false,
  contentType: false
});

El código en la sintaxis de CoffeeScript :

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 el formulario:

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
ThienSuBS avatar Sep 19 '2016 16:09 ThienSuBS