Konwertuj URI danych na plik, a następnie dołącz do FormData

283

Próbowałem ponownie wdrożyć program do przesyłania obrazów HTML5, taki jak ten na stronie Mozilla Hacks , ale działa on z przeglądarkami WebKit. Częścią zadania jest wyodrębnienie pliku obrazu z canvasobiektu i dołączenie go do obiektu FormData w celu przesłania.

Problem polega na tym, że chociaż canvasma toDataURLfunkcję zwracania reprezentacji pliku obrazu, obiekt FormData akceptuje tylko obiekty File lub Blob z interfejsu API File .

W rozwiązaniu Mozilla zastosowano następującą funkcję tylko dla Firefoksa canvas:

var file = canvas.mozGetAsFile("foo.png");

... który nie jest dostępny w przeglądarkach WebKit. Najlepszym rozwiązaniem, jakie mogłem wymyślić, było znalezienie sposobu na konwersję identyfikatora URI danych na obiekt File, który moim zdaniem może być częścią interfejsu API File, ale przez całe życie nie mogę znaleźć czegoś takiego.

Czy to możliwe? Jeśli nie, jakieś alternatywy?

Dzięki.

Stoive
źródło
Jeśli chcesz zapisać DataURI obrazu na serwerze: stackoverflow.com/a/50131281/5466401
Sibin John Mattappallil

Odpowiedzi:

471

Po zabawie z kilkoma rzeczami sam to odkryłem.

Przede wszystkim spowoduje to konwersję dataURI na obiekt Blob:

function dataURItoBlob(dataURI) {
    // convert base64/URLEncoded data component to raw binary data held in a string
    var byteString;
    if (dataURI.split(',')[0].indexOf('base64') >= 0)
        byteString = atob(dataURI.split(',')[1]);
    else
        byteString = unescape(dataURI.split(',')[1]);

    // separate out the mime component
    var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];

    // write the bytes of the string to a typed array
    var ia = new Uint8Array(byteString.length);
    for (var i = 0; i < byteString.length; i++) {
        ia[i] = byteString.charCodeAt(i);
    }

    return new Blob([ia], {type:mimeString});
}

Stamtąd dołączanie danych do formularza, który zostanie przesłany jako plik, jest łatwe:

var dataURL = canvas.toDataURL('image/jpeg', 0.5);
var blob = dataURItoBlob(dataURL);
var fd = new FormData(document.forms[0]);
fd.append("canvasImage", blob);
Stoive
źródło
29
Dlaczego to się zawsze zdarza ... Próbujesz rozwiązać problem godzinami, szukając SO tutaj i tam. Następnie opublikujesz pytanie. W ciągu godziny otrzymasz odpowiedź z innego pytania. Nie żebym narzekał ... stackoverflow.com/questions/9388412/
syaz
@stoive jestem w stanie contruct Blob ale można wyjaśnić w jaki sposób skonstruować POSTlub PUTdo S3?
Gaurav Shah
1
@mimo - Wskazuje na bazę ArrayBuffer, która jest następnie zapisywana w BlobBuilderinstancji.
Stoive
2
@stoive W takim przypadku dlaczego nie jest to bb.append (ia)?
Mimo
4
Dzięki! To rozwiązało mój problem z małą poprawkąvar file = new File( [blob], 'canvasImage.jpg', { type: 'image/jpeg' } ); fd.append("canvasImage", file);
Prasad19sara,
141

BlobBuilder i ArrayBuffer są teraz przestarzałe, oto kod najwyższego komentarza zaktualizowany o konstruktor Blob:

function dataURItoBlob(dataURI) {
    var binary = atob(dataURI.split(',')[1]);
    var array = [];
    for(var i = 0; i < binary.length; i++) {
        array.push(binary.charCodeAt(i));
    }
    return new Blob([new Uint8Array(array)], {type: 'image/jpeg'});
}
vava720
źródło
2
Pomysł: array=[]; array.length=binary.length;... array[i]=bina... itd. Tak więc tablica jest wstępnie przydzielona. Oszczędza to push () konieczności rozszerzania tablicy przy każdej iteracji, a my przetwarzamy tutaj prawdopodobnie miliony elementów (= bajtów), więc to ma znaczenie.
DDS
2
Również mi się nie udaje w Safari. @WilliamT. Odpowiedź działa jednak dla Firefox / Safari / Chrome.
ObscureRobot 10.04.13
„binarny” jest nieco mylącą nazwą, ponieważ nie jest to tablica bitów, ale tablica bajtów.
Niels Abildgaard
1
„type: 'image / jpeg'” - co jeśli jest to obraz png LUB jeśli nie znasz wcześniej rozszerzenia obrazu?
Jasper
Utworzenie Uint8Array na początku jest lepsze niż utworzenie Array, a następnie konwersja do Uint8Array.
cuixiping
52

Ten działa w iOS i Safari.

Musisz użyć rozwiązania ArrayBuffer firmy Stoive, ale nie możesz używać BlobBuilder, jak wskazuje vava720, więc oto połączenie obu.

function dataURItoBlob(dataURI) {
    var byteString = atob(dataURI.split(',')[1]);
    var ab = new ArrayBuffer(byteString.length);
    var ia = new Uint8Array(ab);
    for (var i = 0; i < byteString.length; i++) {
        ia[i] = byteString.charCodeAt(i);
    }
    return new Blob([ab], { type: 'image/jpeg' });
}
William T.
źródło
11
Wspaniały! Ale nadal możesz utrzymać dynamikę łańcucha mime, jak podejrzewam w rozwiązaniu Stoive? // oddziel element mime var mimeString = dataURI.split (',') [0] .split (':') [1] .split (';') [0]
Per Quested Aronsson
Co to jest awaryjne na iOS6 z prefiksem webkit? Jak sobie z tym poradzisz?
confile
30

Firefox ma metody canvas.toBlob () i canvas.mozGetAsFile () .

Ale inne przeglądarki nie.

Możemy pobrać dataurl z kanwy, a następnie przekonwertować dataurl na obiekt blob.

Oto moja dataURLtoBlob()funkcja. Jest bardzo krótki.

function dataURLtoBlob(dataurl) {
    var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
        bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
    while(n--){
        u8arr[n] = bstr.charCodeAt(n);
    }
    return new Blob([u8arr], {type:mime});
}

Użyj tej funkcji z FormData do obsługi obszaru roboczego lub bazy danych.

Na przykład:

var dataurl = canvas.toDataURL('image/jpeg',0.8);
var blob = dataURLtoBlob(dataurl);
var fd = new FormData();
fd.append("myFile", blob, "thumb.jpg");

Możesz także utworzyć HTMLCanvasElement.prototype.toBlobmetodę dla przeglądarki silnika innego niż gecko.

if(!HTMLCanvasElement.prototype.toBlob){
    HTMLCanvasElement.prototype.toBlob = function(callback, type, encoderOptions){
        var dataurl = this.toDataURL(type, encoderOptions);
        var bstr = atob(dataurl.split(',')[1]), n = bstr.length, u8arr = new Uint8Array(n);
        while(n--){
            u8arr[n] = bstr.charCodeAt(n);
        }
        var blob = new Blob([u8arr], {type: type});
        callback.call(this, blob);
    };
}

Teraz canvas.toBlob()działa dla wszystkich nowoczesnych przeglądarek nie tylko Firefox. Na przykład:

canvas.toBlob(
    function(blob){
        var fd = new FormData();
        fd.append("myFile", blob, "thumb.jpg");
        //continue do something...
    },
    'image/jpeg',
    0.8
);
cuixiping
źródło
1
Wymieniony tutaj polyfill dla canvas.toBlob jest poprawnym sposobem obsługi tego problemu IMHO.
Jakob Kruse,
2
Chciałbym podkreślić ostatnią rzecz w tym poście: „Teraz canvas.toBlob () działa we wszystkich nowoczesnych przeglądarkach”.
Eric Simonton,
25

Mój preferowany sposób to canvas.toBlob ()

Ale w każdym razie tutaj jest jeszcze inny sposób przekonwertowania base64 na obiekt blob przy użyciu funkcji pobierania ^^,

var url = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="

fetch(url)
.then(res => res.blob())
.then(blob => {
  var fd = new FormData()
  fd.append('image', blob, 'filename')
  
  console.log(blob)

  // Upload
  // fetch('upload', {method: 'POST', body: fd})
})

Nieskończony
źródło
co to jest pobieranie i jakie ma znaczenie?
Ricardo Freitas
Pobierz to nowoczesna metoda ajax, której możesz użyć zamiast, XMLHttpRequestponieważ adres danych jest tylko adresem URL. Możesz użyć ajax, aby pobrać ten zasób i masz możliwość samodzielnego wyboru, czy chcesz go jako blob, bufor bufora czy tekst
Endless
1
@ Endless 'fetch ()' lokalny ciąg base64 ... naprawdę sprytny hack!
Diego ZoracKy,
1
Należy pamiętać, że blob:i data:nie są one powszechnie obsługiwane przez wszystkie implementacje pobierania. Używamy tego podejścia, ponieważ wiemy, że będziemy zajmować się tylko przeglądarkami mobilnymi (WebKit), ale na przykład Edge nie obsługuje go: developer.mozilla.org/en-US/docs/Web/API/…
oligofren
19

Dzięki @Stoive i @ vava720 połączyłem je w ten sposób, unikając używania przestarzałych BlobBuilder i ArrayBuffer

function dataURItoBlob(dataURI) {
    'use strict'
    var byteString, 
        mimestring 

    if(dataURI.split(',')[0].indexOf('base64') !== -1 ) {
        byteString = atob(dataURI.split(',')[1])
    } else {
        byteString = decodeURI(dataURI.split(',')[1])
    }

    mimestring = dataURI.split(',')[0].split(':')[1].split(';')[0]

    var content = new Array();
    for (var i = 0; i < byteString.length; i++) {
        content[i] = byteString.charCodeAt(i)
    }

    return new Blob([new Uint8Array(content)], {type: mimestring});
}
Mimo
źródło
12

Ewoluujący standard wygląda na canvas.toBlob (), a nie canvas.getAsFile (), jak zgadywał Mozilla.

Nie widzę jeszcze żadnej przeglądarki, która to obsługuje :(

Dzięki za ten świetny wątek!

Ponadto każdy, kto próbuje zaakceptować odpowiedź, powinien zachować ostrożność w przypadku BlobBuilder, ponieważ uważam, że wsparcie jest ograniczone (i przestrzeń nazw):

    var bb;
    try {
        bb = new BlobBuilder();
    } catch(e) {
        try {
            bb = new WebKitBlobBuilder();
        } catch(e) {
            bb = new MozBlobBuilder();
        }
    }

Czy używałeś wypełniania innej biblioteki dla BlobBuilder?

Chris Bosco
źródło
Korzystałem z Chrome bez wypełnień i nie przypominam sobie, by natknąłem się na przestrzeń nazw. Z niecierpliwością przewiduję canvas.toBlob()- wydaje się to bardziej odpowiednie niż getAsFile.
Stoive
1
BlobBuilderwydają się być przestarzałe na korzyśćBlob
sandstrom 10.10 o
BlobBuilder jest przestarzały, a ten wzorzec jest okropny. Lepiej byłoby: window.BlobBuilder = (window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder); zagnieżdżone próby wypróbowania są naprawdę brzydkie, a co się stanie, jeśli żaden z konstruktorów nie będzie dostępny?
Chris Hanson
Jak to jest okropne? Jeśli zgłoszony zostanie wyjątek i 1) BlobBuilder nie istnieje, nic się nie dzieje i wykonywany jest następny blok. 2) Jeśli istnieje, ale zgłaszany jest wyjątek, jest on przestarzały i nie powinien być używany, więc przechodzi do następnego bloku try. Najlepiej byłoby sprawdzić, czy Blob jest obsługiwany jako pierwszy, a nawet przed tym sprawdzeniem obsługi toBlob
TaylorMac
5
var BlobBuilder = (window.MozBlobBuilder || window.WebKitBlobBuilder || window.BlobBuilder);

może być użyty bez try catch.

Podziękowania dla check_ca. Świetna robota.

Nafis Ahmad
źródło
1
Będzie to nadal zgłaszać błąd, jeśli przeglądarka obsługuje przestarzałe narzędzie BlobBuilder. Przeglądarka użyje starej metody, jeśli ją obsługuje, nawet jeśli obsługuje nową metodę. Nie jest to pożądane, patrz podejście Chrisa Bosco poniżej
TaylorMac
4

Oryginalną odpowiedź Stoive'a można łatwo naprawić, zmieniając ostatnią linię, aby pomieścić Blob:

function dataURItoBlob (dataURI) {
    // convert base64 to raw binary data held in a string
    // doesn't handle URLEncoded DataURIs
    var byteString;
    if (dataURI.split(',')[0].indexOf('base64') >= 0)
        byteString = atob(dataURI.split(',')[1]);
    else
        byteString = unescape(dataURI.split(',')[1]);
    // separate out the mime component
    var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];

    // write the bytes of the string to an ArrayBuffer
    var ab = new ArrayBuffer(byteString.length);
    var ia = new Uint8Array(ab);
    for (var i = 0; i < byteString.length; i++) {
        ia[i] = byteString.charCodeAt(i);
    }

    // write the ArrayBuffer to a blob, and you're done
    return new Blob([ab],{type: mimeString});
}
topkara
źródło
3

Oto wersja odpowiedzi Stoive na ES6 :

export class ImageDataConverter {
  constructor(dataURI) {
    this.dataURI = dataURI;
  }

  getByteString() {
    let byteString;
    if (this.dataURI.split(',')[0].indexOf('base64') >= 0) {
      byteString = atob(this.dataURI.split(',')[1]);
    } else {
      byteString = decodeURI(this.dataURI.split(',')[1]);
    }
    return byteString;
  }

  getMimeString() {
    return this.dataURI.split(',')[0].split(':')[1].split(';')[0];
  }

  convertToTypedArray() {
    let byteString = this.getByteString();
    let ia = new Uint8Array(byteString.length);
    for (let i = 0; i < byteString.length; i++) {
      ia[i] = byteString.charCodeAt(i);
    }
    return ia;
  }

  dataURItoBlob() {
    let mimeString = this.getMimeString();
    let intArray = this.convertToTypedArray();
    return new Blob([intArray], {type: mimeString});
  }
}

Stosowanie:

const dataURL = canvas.toDataURL('image/jpeg', 0.5);
const blob = new ImageDataConverter(dataURL).dataURItoBlob();
let fd = new FormData(document.forms[0]);
fd.append("canvasImage", blob);
vekerdyb
źródło
3

Dzięki! @steovi dla tego rozwiązania.

Dodałem obsługę wersji ES6 i zmieniłem z unescape na dataURI (unescape jest przestarzały).

converterDataURItoBlob(dataURI) {
    let byteString;
    let mimeString;
    let ia;

    if (dataURI.split(',')[0].indexOf('base64') >= 0) {
      byteString = atob(dataURI.split(',')[1]);
    } else {
      byteString = encodeURI(dataURI.split(',')[1]);
    }
    // separate out the mime component
    mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];

    // write the bytes of the string to a typed array
    ia = new Uint8Array(byteString.length);
    for (var i = 0; i < byteString.length; i++) {
      ia[i] = byteString.charCodeAt(i);
    }
    return new Blob([ia], {type:mimeString});
}
wilfredonoyola
źródło
1

uprość: D

function dataURItoBlob(dataURI,mime) {
    // convert base64 to raw binary data held in a string
    // doesn't handle URLEncoded DataURIs

    var byteString = window.atob(dataURI);

    // separate out the mime component


    // write the bytes of the string to an ArrayBuffer
    //var ab = new ArrayBuffer(byteString.length);
    var ia = new Uint8Array(byteString.length);
    for (var i = 0; i < byteString.length; i++) {
        ia[i] = byteString.charCodeAt(i);
    }

    // write the ArrayBuffer to a blob, and you're done
    var blob = new Blob([ia], { type: mime });

    return blob;
}
Sendy
źródło
-2

toDataURL daje ci ciąg, który możesz umieścić w ukrytym wejściu.

Cat Chen
źródło
Czy możesz podać przykład? Nie chcę przesyłać ciągu base64 (co to <input type=hidden value="data:..." />by zrobiłoby), chcę przesłać dane pliku (podobnie jak to <input type="file" />, co robi, z wyjątkiem tego , że nie wolno ci ustawiać dla nich valuewłaściwości).
Stoive
To powinien być komentarz, a nie odpowiedź. Proszę opracować odpowiedź z odpowiednim wyjaśnieniem. @Cat Chen
Lucky
-5

Miałem dokładnie taki sam problem jak Ravinder Payal i znalazłem odpowiedź. Spróbuj tego:

var dataURL = canvas.toDataURL("image/jpeg");

var name = "image.jpg";
var parseFile = new Parse.File(name, {base64: dataURL.substring(23)});
feng
źródło
2
Czy naprawdę sugerujesz korzystanie z Parse.com? Powinieneś wspomnieć, że twoja odpowiedź wymaga zależności!
Pierre Maoui,
2
WTF? Dlaczego ktoś przesyła kod obrazu base64 na serwer PARSE, a następnie pobiera? Kiedy możemy bezpośrednio wgrać base64 na nasze serwery, a najważniejsze jest to, że przesłanie ciągu base64 lub pliku obrazu wymaga tych samych danych. A jeśli chcesz tylko pozwolić użytkownikowi na pobranie obrazu, możesz użyć tegowindow.open(canvas.toDataURL("image/jpeg"))
Ravinder Payal