Convertir l'URI de données en fichier puis l'ajouter à FormData

283

J'ai essayé de réimplémenter un téléchargeur d'images HTML5 comme celui du site Mozilla Hacks , mais cela fonctionne avec les navigateurs WebKit. Une partie de la tâche consiste à extraire un fichier image de l' canvasobjet et à l'ajouter à un objet FormData pour le télécharger.

Le problème est que, bien qu'il canvasait la toDataURLfonction de renvoyer une représentation du fichier image, l'objet FormData accepte uniquement les objets File ou Blob de l' API File .

La solution Mozilla a utilisé la fonction Firefox uniquement sur canvas:

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

... qui n'est pas disponible sur les navigateurs WebKit. La meilleure solution à laquelle je pourrais penser est de trouver un moyen de convertir un URI de données en un objet File, que je pensais pouvoir faire partie de l'API File, mais je ne peux pas pour la vie de moi trouver quelque chose à faire.

C'est possible? Sinon, des alternatives?

Merci.

Stoive
la source
Si vous souhaitez enregistrer le DataURI d'une image dans le serveur: stackoverflow.com/a/50131281/5466401
Sibin John Mattappallil

Réponses:

471

Après avoir joué avec quelques choses, j'ai réussi à comprendre cela moi-même.

Tout d'abord, cela convertira un dataURI en un 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});
}

À partir de là, il est facile d'ajouter les données à un formulaire afin qu'elles soient téléchargées sous forme de fichier:

var dataURL = canvas.toDataURL('image/jpeg', 0.5);
var blob = dataURItoBlob(dataURL);
var fd = new FormData(document.forms[0]);
fd.append("canvasImage", blob);
Stoive
la source
29
Pourquoi cela se produit-il toujours ... Vous essayez de résoudre un problème pendant des heures et des heures avec des recherches SO ici et là. Ensuite, vous postez une question. En une heure, vous obtenez la réponse d'une autre question. Pas que je me plaigne ... stackoverflow.com/questions/9388412/…
syaz
@stoive Je suis capable de construire Blob mais pouvez-vous expliquer comment construire le POSTou PUTvers S3?
Gaurav Shah du
1
@mimo - Il pointe vers le sous-jacent ArrayBuffer, qui est ensuite écrit dans l' BlobBuilderinstance.
Stoive
2
@stoive Dans ce cas, pourquoi ce n'est pas bb.append (ia)?
Mimo
4
Merci! Cela a résolu mon problème avec une petite correctionvar file = new File( [blob], 'canvasImage.jpg', { type: 'image/jpeg' } ); fd.append("canvasImage", file);
Prasad19sara
141

BlobBuilder et ArrayBuffer sont désormais obsolètes, voici le code du premier commentaire mis à jour avec le constructeur 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
la source
2
Juste une idée: array=[]; array.length=binary.length;... array[i]=bina... etc. Le tableau est donc pré-alloué. Cela économise un push () devant étendre le tableau à chaque itération, et nous traitons peut-être des millions d'éléments (= octets) ici, donc c'est important.
DDS
2
Échoue également pour moi sur Safari. @WilliamT. Cependant, la réponse fonctionne pour Firefox / Safari / Chrome.
ObscureRobot
"binaire" est un nom légèrement trompeur, car ce n'est pas un tableau de bits, mais un tableau d'octets.
Niels Abildgaard
1
"type: 'image / jpeg'" - et si c'est une image png OU si vous ne connaissez pas l'extension d'image à l'avance?
Jasper
Créer Uint8Array au début est préférable à créer un tableau, puis à le convertir en Uint8Array.
cuixiping
52

Celui-ci fonctionne dans iOS et Safari.

Vous devez utiliser la solution ArrayBuffer de Stoive mais vous ne pouvez pas utiliser BlobBuilder, comme l'indique vava720, alors voici le mashup des deux.

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.
la source
11
Génial! Mais vous pouvez toujours garder la chaîne de mime dynamique, comme dans la solution de Stoive, je suppose? // séparer le composant mime var mimeString = dataURI.split (',') [0] .split (':') [1] .split (';') [0]
Par Quested Aronsson
Quelle est la solution de repli pour iOS6 avec le préfixe webkit? Comment gérez-vous cela?
confile
30

Firefox possède les méthodes canvas.toBlob () et canvas.mozGetAsFile () .

Mais les autres navigateurs ne le font pas.

Nous pouvons obtenir dataurl du canevas, puis convertir dataurl en objet blob.

Voici ma dataURLtoBlob()fonction. C'est très court.

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

Utilisez cette fonction avec FormData pour gérer votre canevas ou dataurl.

Par exemple:

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

En outre, vous pouvez créer une HTMLCanvasElement.prototype.toBlobméthode pour le navigateur de moteur 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);
    };
}

Fonctionne maintenant canvas.toBlob()pour tous les navigateurs modernes, pas seulement Firefox. Par exemple:

canvas.toBlob(
    function(blob){
        var fd = new FormData();
        fd.append("myFile", blob, "thumb.jpg");
        //continue do something...
    },
    'image/jpeg',
    0.8
);
cuixiping
la source
1
Le polyfill pour canvas.toBlob mentionné ici est la bonne façon de gérer ce problème à mon humble avis.
Jakob Kruse
2
Je voudrais souligner la dernière chose dans cet article: "Maintenant canvas.toBlob () fonctionne pour tous les navigateurs modernes."
Eric Simonton
25

Ma façon préférée est canvas.toBlob ()

Mais de toute façon, voici encore une autre façon de convertir base64 en un blob en utilisant 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})
})

Interminable
la source
qu'est-ce que fetch et comment est-il pertinent?
Ricardo Freitas
La récupération est une méthode ajax moderne que vous pouvez utiliser au lieu de, XMLHttpRequestcar l'url de données n'est qu'une URL, vous pouvez utiliser ajax pour récupérer cette ressource et vous avez vous-même une option pour décider si vous la voulez en tant que blob, arraybuffer ou texte
sans fin
1
@Endless 'fetch ()' une chaîne base64 locale ... un hack vraiment intelligent!
Diego ZoracKy
1
Gardez à l'esprit que blob:et data:ne sont pas universellement pris en charge par toutes les implémentations de récupération. Nous utilisons cette approche, car nous savons que nous ne traiterons que des navigateurs mobiles (WebKit), mais Edge par exemple, ne le prend pas en charge: developer.mozilla.org/en-US/docs/Web/API/…
oligofren
19

Grâce à @Stoive et @ vava720, j'ai combiné les deux de cette manière, en évitant d'utiliser les BlobBuilder et ArrayBuffer obsolètes

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
la source
12

La norme évolutive semble être canvas.toBlob () et non canvas.getAsFile () comme Mozilla risquait de le deviner.

Je ne vois aucun navigateur le supportant encore :(

Merci pour ce super fil!

En outre, quiconque essaie la réponse acceptée doit être prudent avec BlobBuilder car je trouve que le support est limité (et espacé):

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

Utilisiez-vous le polyfill d'une autre bibliothèque pour BlobBuilder?

Chris Bosco
la source
J'utilisais Chrome sans polyfills, et je ne me souviens pas d'avoir rencontré un espace de noms. J'anticipe avec impatience canvas.toBlob()- cela semble beaucoup plus approprié que getAsFile.
Stoive
1
BlobBuildersemblent dépréciés en faveur deBlob
sandstrom
BlobBuilder est obsolète et ce modèle est horrible. Mieux serait: window.BlobBuilder = (window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder); les captures d'essais imbriquées sont vraiment laides et que se passe-t-il si aucun des constructeurs n'est disponible?
Chris Hanson
Comment est-ce affreux? Si une exception est levée et 1) BlobBuilder n'existe pas, alors rien ne se passe et le bloc suivant est exécuté. 2) Si elle existe, mais qu'une exception est levée, elle est obsolète et ne devrait pas être utilisée de toute façon, donc elle continue dans le bloc try suivant. Idéalement, vous devriez vérifier si Blob est pris en charge en premier, et même avant cette vérification de la prise en charge de toBlob
TaylorMac
5
var BlobBuilder = (window.MozBlobBuilder || window.WebKitBlobBuilder || window.BlobBuilder);

peut être utilisé sans la prise d'essai.

Merci à check_ca. Bon travail.

Nafis Ahmad
la source
1
Cela générera toujours une erreur si le navigateur prend en charge le BlobBuilder obsolète. Le navigateur utilisera l'ancienne méthode s'il la prend en charge, même s'il prend en charge la nouvelle méthode. Ce n'est pas souhaité, voir l'approche de Chris Bosco ci
TaylorMac
4

La réponse originale de Stoive est facilement réparable en modifiant la dernière ligne pour accueillir 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
la source
3

Voici une version ES6 de la réponse de 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});
  }
}

Usage:

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
la source
3

Merci! @steovi pour cette solution.

J'ai ajouté le support à la version ES6 et je suis passé de unescape à dataURI (unescape est déconseillé).

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
la source
1

simplifiez: 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
la source
-2

toDataURL vous donne une chaîne et vous pouvez la mettre dans une entrée cachée.

Cat Chen
la source
Pourriez-vous donner un exemple? Je ne veux pas télécharger une chaîne base64 (ce que <input type=hidden value="data:..." />ferait), je veux télécharger les données du fichier (comme quoi <input type="file" />, sauf que vous n'êtes pas autorisé à définir la valuepropriété sur celles-ci).
Stoive
Cela devrait être un commentaire plutôt qu'une réponse. Veuillez élaborer la réponse avec une explication appropriée. @Cat Chen
Lucky
-5

J'ai eu exactement le même problème que Ravinder Payal, et j'ai trouvé la réponse. Essaye ça:

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

var name = "image.jpg";
var parseFile = new Parse.File(name, {base64: dataURL.substring(23)});
feng
la source
2
Suggérez-vous vraiment d'utiliser Parse.com? Vous devez mentionner que votre réponse nécessite des dépendances!
Pierre Maoui
2
WTF? Pourquoi quelqu'un va télécharger le code d'image base64 sur le serveur PARSE puis télécharger? Quand nous pouvons télécharger directement base64 sur nos serveurs et l'essentiel est qu'il faut les mêmes données pour télécharger la chaîne ou le fichier image base64. Et si vous voulez simplement permettre à l'utilisateur de télécharger l'image, vous pouvez l'utiliserwindow.open(canvas.toDataURL("image/jpeg"))
Ravinder Payal