Konversikan Data URI ke File lalu tambahkan ke FormData

283

Saya telah mencoba menerapkan kembali pengunggah gambar HTML5 seperti yang ada di situs Mozilla Hacks , tetapi itu bekerja dengan browser WebKit. Bagian dari tugas adalah untuk mengekstrak file gambar dari canvasobjek dan menambahkannya ke objek FormData untuk diunggah.

Masalahnya adalah bahwa sementara canvasmemiliki toDataURLfungsi untuk mengembalikan representasi file gambar, objek FormData hanya menerima objek File atau Blob dari File API .

Solusi Mozilla menggunakan fungsi khusus Firefox berikut ini pada canvas:

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

... yang tidak tersedia di browser WebKit. Solusi terbaik yang dapat saya pikirkan adalah menemukan beberapa cara untuk mengubah URI Data menjadi objek File, yang saya pikir mungkin menjadi bagian dari File API, tetapi saya tidak dapat seumur hidup menemukan sesuatu untuk melakukan itu.

Apa itu mungkin? Jika tidak, ada alternatif lain?

Terima kasih.

Stoive
sumber
Jika Anda ingin menyimpan DataURI dari sebuah gambar di server: stackoverflow.com/a/50131281/5466401
Sibin John Mattappallil

Jawaban:

471

Setelah bermain-main dengan beberapa hal, saya berhasil mencari tahu sendiri.

Pertama-tama, ini akan mengubah dataURI menjadi gumpalan:

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

Dari sana, menambahkan data ke formulir sedemikian rupa sehingga mudah diunggah sebagai file:

var dataURL = canvas.toDataURL('image/jpeg', 0.5);
var blob = dataURItoBlob(dataURL);
var fd = new FormData(document.forms[0]);
fd.append("canvasImage", blob);
Stoive
sumber
29
Mengapa ini selalu terjadi ... Anda mencoba memecahkan masalah selama berjam-jam dengan pencarian SO di sana-sini. Kemudian Anda memposting pertanyaan. Dalam satu jam Anda mendapatkan jawaban dari pertanyaan lain. Bukannya aku mengeluh ... stackoverflow.com/questions/9388412/…
syaz
@stoive Saya dapat membuat Blob tetapi bisakah Anda menjelaskan bagaimana Anda membuat POSTatau PUTke S3?
Gaurav Shah
1
@mimo - Ini menunjuk pada yang mendasarinya ArrayBuffer, yang kemudian ditulis ke BlobBuilderinstance.
Stoive
2
@stoive Dalam hal itu mengapa itu bukan bb.append (ia)?
Mimo
4
Terima kasih! Ini menyelesaikan masalah saya dengan koreksi kecilvar file = new File( [blob], 'canvasImage.jpg', { type: 'image/jpeg' } ); fd.append("canvasImage", file);
Prasad19sara
141

BlobBuilder dan ArrayBuffer sekarang tidak digunakan lagi, berikut ini adalah kode komentar teratas yang diperbarui dengan 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
sumber
2
Hanya sebuah ide: array=[]; array.length=binary.length;... array[i]=bina... dll. Jadi array sudah dialokasikan sebelumnya. Menghemat push () harus memperpanjang array setiap iterasi, dan kami sedang memproses jutaan item (= byte) di sini, jadi itu penting.
DDS
2
Juga gagal untuk saya di Safari. @ Willill. Jawabannya bekerja untuk Firefox / Safari / Chrome.
ObscureRobot
"biner" adalah nama yang sedikit menyesatkan, karena itu bukan array bit, tetapi array byte.
Niels Abildgaard
1
"type: 'image / jpeg'" - bagaimana jika ini adalah gambar png ATAU jika Anda tidak tahu ekstensi gambar sebelumnya?
Jasper
Buat Uint8Array pada awalnya lebih baik daripada membuat Array dan kemudian dikonversi ke Uint8Array.
berbagi
52

Yang ini berfungsi di iOS dan Safari.

Anda perlu menggunakan solusi ArrayBuffer Stoive tetapi Anda tidak dapat menggunakan BlobBuilder, seperti yang ditunjukkan oleh vava720, jadi inilah gabungan keduanya.

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.
sumber
11
Bagus! Tapi Anda masih bisa menjaga string mime tetap dinamis, seperti dalam solusi Stoive, saya kira? // pisahkan komponen mime var mimeString = dataURI.split (',') [0] .split (':') [1] .split (';') [0]
Per Quested Aronsson
Ada apa dengan fallback untuk iOS6 dengan awalan webkit? bagaimana kamu menangani ini?
confile
30

Firefox memiliki metode canvas.toBlob () dan canvas.mozGetAsFile () .

Tetapi browser lain tidak.

Kita bisa mendapatkan dataurl dari kanvas dan kemudian mengkonversi dataurl ke objek gumpalan.

Inilah dataURLtoBlob()fungsi saya . Ini sangat singkat.

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

Gunakan fungsi ini dengan FormData untuk menangani kanvas atau dataurl Anda.

Sebagai contoh:

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

Anda juga dapat membuat HTMLCanvasElement.prototype.toBlobmetode untuk browser engine non 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);
    };
}

Sekarang canvas.toBlob()berfungsi untuk semua browser modern, tidak hanya Firefox. Sebagai contoh:

canvas.toBlob(
    function(blob){
        var fd = new FormData();
        fd.append("myFile", blob, "thumb.jpg");
        //continue do something...
    },
    'image/jpeg',
    0.8
);
cuixiping
sumber
1
Polyfill untuk canvas.toBlob yang disebutkan di sini adalah cara yang tepat untuk menangani masalah ini IMHO.
Jakob Kruse
2
Saya ingin menekankan hal terakhir dalam posting ini: "Sekarang canvas.toBlob () berfungsi untuk semua browser modern."
Eric Simonton
25

Cara pilihan saya adalah canvas.toBlob ()

Namun bagaimanapun juga di sini adalah cara lain untuk mengubah base64 menjadi gumpalan menggunakan fetch ^^,

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

Tak berujung
sumber
apa yang diambil dan bagaimana itu relevan?
Ricardo Freitas
Fetch adalah metode ajax modern yang dapat Anda gunakan alih-alih XMLHttpRequestkarena url data hanyalah url, Anda dapat menggunakan ajax untuk mengambil sumber daya itu dan Anda memiliki opsi sendiri untuk memutuskan apakah Anda menginginkannya sebagai gumpalan, arraybuffer atau teks
Endless
1
@Endless 'fetch ()' string base64 lokal ... hack yang sangat pintar!
Diego ZoracKy
1
Ingatlah itu blob:dan data:tidak didukung secara universal oleh semua implementasi pengambilan. Kami menggunakan pendekatan ini, karena kami tahu kami hanya akan berurusan dengan peramban seluler (WebKit), tetapi Edge misalnya, tidak mendukungnya: developer.mozilla.org/en-US/docs/Web/API/…
oligofren
19

Berkat @Stoive dan @ vava720 saya menggabungkan keduanya dengan cara ini, menghindari menggunakan BlobBuilder dan ArrayBuffer yang sudah usang.

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
sumber
12

Standar yang berkembang tampaknya kanvas.toBlob () bukan canvas.getAsFile () seperti yang bisa ditebak oleh Mozilla.

Saya belum melihat browser yang mendukungnya :(

Terima kasih untuk utas yang luar biasa ini!

Juga, siapa pun yang mencoba jawaban yang diterima harus berhati-hati dengan BlobBuilder karena saya menemukan dukungan terbatas (dan namespace):

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

Apakah Anda menggunakan polyfill perpustakaan lain untuk BlobBuilder?

Chris Bosco
sumber
Saya menggunakan Chrome tanpa polyfill, dan tidak ingat menemukan namespacing. Saya bersemangat mengantisipasi canvas.toBlob()- tampaknya jauh lebih tepat daripada getAsFile.
Stoive
1
BlobBuildertampaknya ditinggalkan dalam mendukungBlob
sandstrom
BlobBuilder sudah usang dan pola ini buruk. Lebih baik adalah: window.BlobBuilder = (window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder); tangkapan percobaan bersarang benar-benar jelek dan apa yang terjadi jika tidak ada pembangun yang tersedia?
Chris Hanson
Bagaimana ini mengerikan? Jika pengecualian dilemparkan dan 1) BlobBuilder tidak ada maka tidak ada yang terjadi dan blok berikutnya dijalankan. 2) Jika memang ada, tetapi pengecualian dilempar maka itu sudah usang dan tidak boleh digunakan, jadi itu berlanjut ke blok coba berikutnya. Idealnya Anda akan memeriksa apakah Blob didukung terlebih dahulu, dan bahkan sebelum cek ini untuk dukungan toBlob
TaylorMac
5
var BlobBuilder = (window.MozBlobBuilder || window.WebKitBlobBuilder || window.BlobBuilder);

dapat digunakan tanpa try catch.

Terima kasih untuk check_ca. Kerja bagus.

Nafis Ahmad
sumber
1
Ini masih akan menimbulkan kesalahan jika browser mendukung BlobBuilder yang sudah tidak digunakan lagi. Browser akan menggunakan metode lama jika mendukungnya, bahkan jika itu mendukung metode baru. Ini tidak diinginkan, lihat pendekatan Chris Bosco di bawah ini
TaylorMac
4

Jawaban asli oleh Stoive mudah diperbaiki dengan mengubah baris terakhir untuk mengakomodasi 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
sumber
3

Ini adalah versi ES6 dari jawaban Stoive :

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

Pemakaian:

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
sumber
3

Terima kasih! @steovi untuk solusi ini.

Saya telah menambahkan dukungan ke versi ES6 dan berubah dari unescape ke dataURI (unescape sudah usang).

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
sumber
1

sederhanakan: 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
sumber
-2

toDataURL memberi Anda sebuah string dan Anda dapat meletakkan string itu ke input tersembunyi.

Kucing Chen
sumber
Bisakah Anda memberi contoh? Saya tidak ingin mengunggah string base64 (yang melakukan apa yang <input type=hidden value="data:..." />akan dilakukan), saya ingin mengunggah data file (seperti apa <input type="file" />, kecuali Anda tidak diizinkan untuk mengatur valueproperti pada ini).
Stoive
Ini harus menjadi komentar daripada jawaban. Tolong jelaskan jawabannya dengan penjelasan yang tepat. @Cat Chen
Lucky
-5

Saya memiliki masalah yang sama persis dengan Ravinder Payal, dan saya telah menemukan jawabannya. Coba ini:

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

var name = "image.jpg";
var parseFile = new Parse.File(name, {base64: dataURL.substring(23)});
feng
sumber
2
Apakah Anda benar-benar menyarankan untuk menggunakan Parse.com? Anda harus menyebutkan bahwa jawaban Anda memerlukan dependensi!
Pierre Maoui
2
WTF? Mengapa ada yang akan mengunggah kode gambar base64 ke server PARSE dan kemudian mengunduh? Ketika kita bisa langsung mengunggah base64 di server kami dan yang utama adalah bahwa dibutuhkan data yang sama untuk mengunggah string base64 atau file gambar. Dan jika Anda hanya ingin mengubah pengguna untuk mengunduh gambar, Anda dapat menggunakan iniwindow.open(canvas.toDataURL("image/jpeg"))
Ravinder Payal