Jak zapisać płótno HTML5 jako obraz na serwerze?

260

Pracuję nad generatywnym projektem artystycznym, w którym chciałbym umożliwić użytkownikom zapisywanie powstałych obrazów z algorytmu. Ogólna idea to:

  • Utwórz obraz na kanwie HTML5 przy użyciu algorytmu generatywnego
  • Po ukończeniu obrazu zezwól użytkownikom na zapisanie płótna jako pliku obrazu na serwerze
  • Pozwól użytkownikowi pobrać obraz lub dodać go do galerii sztuk wyprodukowanych przy użyciu algorytmu.

Utknąłem jednak na drugim etapie. Po pomocy Google znalazłem ten post na blogu , który wydawał się być dokładnie tym, czego chciałem:

Co doprowadziło do kodu 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);
}

i odpowiedni PHP (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);
}
?>

Ale to wydaje się w ogóle nic nie robić.

Więcej Googling pokazuje ten post na blogu, który jest oparty na poprzednim samouczku. Nie bardzo różne, ale być może warte spróbowania:

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

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

Ten tworzy plik (tak), ale jest uszkodzony i wydaje się, że nic nie zawiera. Wygląda również na pusty (rozmiar pliku 0).

Czy jest coś naprawdę oczywistego, że robię źle? Ścieżka, w której przechowuję mój plik, jest zapisywalna, więc to nie jest problem, ale wydaje się, że nic się nie dzieje i nie jestem pewien, jak to debugować.

Edytować

Po linku Salvidora Dali zmieniłem żądanie AJAX na:

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

A teraz plik obrazu został utworzony i nie jest pusty! Wygląda na to, że typ zawartości ma znaczenie i że zmiana go w celu x-www-form-urlencodedumożliwienia wysłania danych obrazu.

Konsola zwraca (raczej duży) ciąg kodu base64, a plik danych ma wielkość ~ 140 kB. Jednak nadal nie mogę go otworzyć i wydaje się, że nie jest sformatowany jako obraz.

Nathan Lachenmyer
źródło
Pierwszy post na blogu, z ajax.send(canvasData );którego korzystasz, korzysta z niego podczas korzystania z niego ajax.send("imgData="+canvasData);. Dlatego $GLOBALS["HTTP_RAW_POST_DATA"]nie będzie to, czego oczekujesz, prawdopodobnie powinieneś użyć $_POST['imgData'].
Matteus Hemström,
stackoverflow.com/questions/3592164/…
Diodeus - James MacFarlane
Diodeus: Już używam techniki sugerowanej w tym wątku; jednak nie podali żadnych dalszych szczegółów na temat implementacji i właśnie w tym utknąłem.
Nathan Lachenmyer,
Kiedy odbijam informacje o pliku ( $dataw drugim kodzie php), wszystko, co otrzymuję, to pusta linia. Dlaczego miałoby to być? Wydaje się, że być może przesyłane dane nie są poprawne, ale wydaje się, że przesyłam je tak, jak pokazują to przykłady ...
Nathan Lachenmyer,
Kod PHP można uprościć i uczynić bardziej niezawodnym, używając zamiast tego PHP-FileUpload ze swoim DataUriUploadkomponentem. Jest to udokumentowane tutaj i zawiera kilka dodatkowych środków sprawdzania poprawności i egzekwowania bezpieczeństwa.
caw,

Odpowiedzi:

241

Oto przykład, jak osiągnąć to, czego potrzebujesz:

1) Narysuj coś (wzięte z samouczka na płótnie )

<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) Konwertuj obraz na płótnie do formatu URL (base64)

var dataURL = canvas.toDataURL();

3) Wyślij go na swój serwer przez 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) Zapisz base64 na serwerze jako obraz (oto jak to zrobić w PHP , te same pomysły są w każdym języku. Po stronie serwera w PHP można znaleźć tutaj ):

Salvador Dali
źródło
1
Mam na myśli, że plik tam jest, ale jeśli go pobiorę, nie otwiera się on w mojej przeglądarce lub przeglądarce zdjęć.
nathan lachenmyer
You send it to your server as an ajax requestczy ta metoda wymaga potwierdzenia użytkownika? Czy mogę cicho wysłać obraz z płótna na serwer?
MyTitle
W konsoli internetowej pojawia się błąd. [16: 53: 43.227] SecurityError: Operacja jest niepewna. @ sharevi.com/staging/canvas.html:43 To połączenie jest niepewne. Czy jest coś, co należy zrobić? /// AKTUALIZACJA
Wydaje
1
ale alfa wynosi 0, co powoduje, że nie wypełnia się, co jest całkowicie przezroczyste. bez względu na pierwsze trzy wartości.
Abel D
68

Grałem z tym dwa tygodnie temu, jest to bardzo proste. Jedynym problemem jest to, że wszystkie samouczki mówią tylko o lokalnym zapisywaniu obrazu. Oto jak to zrobiłem:

1) Skonfigurowałem formularz, aby móc użyć metody POST.

2) Po zakończeniu rysowania użytkownik może kliknąć przycisk „Zapisz”.

3) Po kliknięciu przycisku biorę dane obrazu i umieszczam je w ukrytym polu. Następnie przesyłam formularz.

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

4) Po przesłaniu formularza mam ten mały skrypt 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']);
?>
użytkownik568021
źródło
1
czy mogę używać canvas.toDataURL ()? Mam dynamiczne rozszerzenie pliku, takie jak jpeg, png, gif. Próbowałem canvas.toDataURL ('image / jpg'), ale to nie działa. Co jest nie tak?
Ivo San,
Otrzymałem błąd 413 (żądanie zbyt duże) podczas próby wysłania obrazu z płótna przez AJAX. To podejście pomogło mi przejść przez obrazy.
Raiyan
1
Czy jest jakiś powód, dla którego ten plik mi daje plik png 0kb?
prismspecs
3
W przypadku jpg musisz zrobić canvas.toDataURL ('image / jpeg') - przynajmniej to zrobiło dla mnie w Chrome.
Chris
Dziękuję, właśnie tego potrzebowałem: D
FosAvance 13.03.19
21

Myślę, że powinieneś przenieść obraz w base64 do obrazu z blobem, ponieważ kiedy używasz obrazu base64, potrzeba dużo linii dziennika lub dużo linii zostanie wysłanych na serwer. W przypadku obiektu blob jest to tylko plik. Możesz użyć tego kodu poniżej:

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

I kod płótna tutaj:

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

Następnie możesz używać ajax z 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

Ten kod używa składni CoffeeScript.

jeśli chcesz użyć javascript, wklej kod do http://js2.coffee

ThienSuBS
źródło
12

Wyślij obraz na płótnie do PHP:

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

Oto skrypt 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
źródło
9

Jeśli chcesz zapisać dane pochodzące z canvas.toDataURL()funkcji Javascript , musisz przekonwertować spacje na plusy. Jeśli tego nie zrobisz, zdekodowane dane są uszkodzone:

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

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

MaloMax
źródło
7

Pracowałem nad czymś podobnym. Musiałem przekonwertować obraz w formacie Canvas w formacie 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.
źródło
4

Oprócz odpowiedzi Salvadora Dali:

po stronie serwera nie zapominaj, że dane są w formacie ciągu base64 . Jest to ważne, ponieważ w niektórych językach programowania musisz wyraźnie powiedzieć, że ten ciąg powinien być traktowany jako bajty, a nie zwykły ciąg Unicode.

W przeciwnym razie dekodowanie nie zadziała: obraz zostanie zapisany, ale będzie to plik nieczytelny.

Ardine
źródło