Como salvar uma tela HTML5 como uma imagem em um servidor?

261

Estou trabalhando em um projeto de arte generativo em que gostaria de permitir que os usuários salvem as imagens resultantes de um algoritmo. A ideia geral é:

  • Crie uma imagem em uma tela HTML5 usando um algoritmo generativo
  • Quando a imagem estiver concluída, permita aos usuários salvar a tela como um arquivo de imagem no servidor
  • Permita que o usuário baixe a imagem ou adicione-a a uma galeria de peças produzidas usando o algoritmo.

No entanto, estou preso no segundo passo. Depois de alguma ajuda do Google, encontrei esta postagem no blog , que parecia exatamente o que eu queria:

O que levou ao 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);
}

e PHP correspondente (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);
}
?>

Mas isso parece não fazer nada.

Mais pesquisas no Google aparecem nesta postagem do blog, baseada no tutorial anterior. Não é muito diferente, mas talvez vale a pena tentar:

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

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

Este cria um arquivo (yay), mas está corrompido e parece não conter nada. Também parece estar vazio (tamanho do arquivo 0).

Existe algo realmente óbvio que estou fazendo de errado? O caminho em que estou armazenando meu arquivo é gravável, portanto isso não é um problema, mas nada parece estar acontecendo e não tenho muita certeza de como depurar isso.

Editar

Seguindo o link de Salvidor Dali, alterei a solicitação do AJAX para:

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

E agora o arquivo de imagem é criado e não está vazio! Parece que o tipo de conteúdo é importante e que a alteração para x-www-form-urlencodedpermitir o envio dos dados da imagem.

O console retorna a sequência (bastante grande) do código base64 e o arquivo de dados é ~ 140 kB. No entanto, ainda não consigo abri-lo e parece não estar formatado como uma imagem.

nathan lachenmyer
fonte
A primeira postagem do blog que você usa ajax.send(canvasData );enquanto você usa ajax.send("imgData="+canvasData);. Portanto $GLOBALS["HTTP_RAW_POST_DATA"], não será o que você espera, você provavelmente deve usar $_POST['imgData'].
Matteus Hemström
stackoverflow.com/questions/3592164/...
Diodeus - James MacFarlane
Diodeus: Eu já estou usando a técnica sugerida nesse tópico; no entanto, eles falharam em fornecer mais detalhes sobre a implementação e é aí que estou ficando preso.
Nathan lachenmyer 02/11/12
Quando eu ecoo as informações do arquivo ( $datano segundo código php), tudo o que é retornado é uma linha em branco. Por que isso seria? Parece que talvez os dados enviados não é correto, mas parece que eu estou enviando-o assim como os exemplos mostram ...
Nathan lachenmyer
O código PHP pode ser simplificado e tornado mais robusto usando o PHP-FileUpload com seu DataUriUploadcomponente. Está documentado aqui e vem com vários meios adicionais de validação e reforço da segurança.
caw

Respostas:

242

Aqui está um exemplo de como conseguir o que você precisa:

1) Desenhe algo (retirado do tutorial da tela )

<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) Converta a imagem da tela para o formato URL (base64)

var dataURL = canvas.toDataURL();

3) Envie para o seu servidor 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) Salve o base64 no seu servidor como uma imagem (aqui está como fazer isso no PHP , as mesmas idéias estão em todas as línguas. O lado do servidor no PHP pode ser encontrado aqui ):

Salvador Dalí
fonte
1
Quero dizer que o arquivo está lá, mas não é aberto no meu navegador ou em qualquer visualizador de imagens, se eu baixá-lo.
Nathan lachenmyer 02/11/12
You send it to your server as an ajax requesteste método requer confirmação do usuário? Ou posso enviar silenciosamente a imagem da tela para o servidor?
MyTitle
Eu recebo um erro no console da Web. [16: 53: 43.227] SecurityError: a operação é insegura. @ sharevi.com/staging/canvas.html:43 the Esta conexão é insegura. Existe algo que precisa ser feito? /// ATUALIZAÇÃO Acho que sei porque, eu estava usando imagens de domínio cruzado
Nikolaos Vassos
1
mas alfa é 0, o que o torna sem preenchimento, que é completamente transparente. não importa qual seja o valor dos três primeiros.
Abel D
68

Eu brinquei com isso há duas semanas, é muito simples. O único problema é que todos os tutoriais apenas falam sobre como salvar a imagem localmente. Foi assim que eu fiz:

1) Configurei um formulário para poder usar um método POST.

2) Quando o usuário terminar de desenhar, ele poderá clicar no botão "Salvar".

3) Ao clicar no botão, pego os dados da imagem e os coloco em um campo oculto. Depois disso, envio o formulário.

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

4) Quando o formulário é enviado, tenho este pequeno 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
fonte
1
tudo bem se eu usar canvas.toDataURL ()? Tenho a extensão de arquivo dinâmico, como jpeg, png, gif. Eu tentei canvas.toDataURL ('image / jpg'), mas não está funcionando. O que há de errado?
Ivo San
Eu estava recebendo o erro 413 (solicitação muito grande) ao tentar enviar imagem da tela pelo AJAX. Então essa abordagem me ajudou a obter as imagens.
Raiyan
1
Algum motivo para este me fornecer um arquivo png de 0kb?
prismspecs
3
Para o jpg, você precisará do canvas.toDataURL ('image / jpeg') - pelo menos foi o que fez por mim no Chrome.
Chris
Obrigado, é disso que eu precisava: D
FosAvance 13/03/19
21

Eu acho que você deve transferir imagem na base64 para a imagem com blob, porque quando você usa a imagem base64, são necessárias muitas linhas de log ou muitas linhas serão enviadas para o servidor. Com blob, apenas o arquivo. Você pode usar este código abaixo:

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

E o código da tela aqui:

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

Depois disso, você pode usar o ajax com o 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 usando a sintaxe do CoffeeScript.

se você quiser usar javascript, cole o código em http://js2.coffee

ThienSuBS
fonte
12

Enviar imagem de tela para PHP:

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

Aqui está o 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
fonte
9

Se você deseja salvar dados derivados de uma canvas.toDataURL()função Javascript , é necessário converter espaços em branco em plusses. Se você não fizer isso, os dados decodificados estão corrompidos:

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

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

MaloMax
fonte
7

Eu trabalhei em algo semelhante. Teve que converter a imagem de tela codificada em Base64 para 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.
fonte
4

Além da resposta de Salvador Dali:

no lado do servidor, não esqueça que os dados são fornecidos no formato de sequência base64 . É importante porque, em algumas linguagens de programação, você precisa dizer explicitamente que essa sequência deve ser considerada como bytes, não como uma sequência Unicode simples.

Caso contrário, a decodificação não funcionará: a imagem será salva, mas será um arquivo ilegível.

Ardine
fonte