Convierta el URI de datos a archivo y luego añádalo a FormData

283

He estado tratando de volver a implementar un cargador de imágenes HTML5 como el del sitio Mozilla Hacks , pero eso funciona con los navegadores WebKit. Parte de la tarea es extraer un archivo de imagen del canvasobjeto y agregarlo a un objeto FormData para cargarlo.

El problema es que, si bien canvastiene la toDataURLfunción de devolver una representación del archivo de imagen, el objeto FormData solo acepta objetos File o Blob de File API .

La solución de Mozilla utilizó la siguiente función exclusiva de Firefox en canvas:

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

... que no está disponible en los navegadores WebKit. La mejor solución que se me ocurre es encontrar alguna forma de convertir un URI de datos en un objeto de archivo, que pensé que podría ser parte de la API de archivo, pero por mi vida no puedo encontrar algo para hacer eso.

¿Es posible? Si no, ¿alguna alternativa?

Gracias.

Stoive
fuente
Si desea guardar el DataURI de una imagen en el servidor: stackoverflow.com/a/50131281/5466401
Sibin John Mattappallil

Respuestas:

471

Después de jugar con algunas cosas, me las arreglé para resolver esto.

En primer lugar, esto convertirá 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});
}

A partir de ahí, es fácil agregar los datos a un formulario para que se carguen como un archivo:

var dataURL = canvas.toDataURL('image/jpeg', 0.5);
var blob = dataURItoBlob(dataURL);
var fd = new FormData(document.forms[0]);
fd.append("canvasImage", blob);
Stoive
fuente
29
¿Por qué siempre sucede esto? Intenta resolver un problema durante horas con SO busca aquí y allá. Luego publicas una pregunta. En una hora obtienes la respuesta de otra pregunta. No es que me queje ... stackoverflow.com/questions/9388412/…
syaz
@stoive Soy capaz de construir Blob, pero ¿puedes explicar cómo construyes el POSTo PUTpara S3?
Gaurav Shah
1
@mimo: apunta al subyacente ArrayBuffer, que luego se escribe en la BlobBuilderinstancia.
Stoive
2
@stoive En ese caso, ¿por qué no es bb.append (ia)?
Mimo
44
¡Gracias! Esto resolvió mi problema con una pequeña correcciónvar file = new File( [blob], 'canvasImage.jpg', { type: 'image/jpeg' } ); fd.append("canvasImage", file);
Prasad19sara
141

BlobBuilder y ArrayBuffer ahora están en desuso, aquí está el código del comentario superior actualizado con Blob constructor:

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
fuente
2
Solo una idea: array=[]; array.length=binary.length;... array[i]=bina... etc. Entonces la matriz está preasignada. Ahorra un push () al tener que extender la matriz en cada iteración, y estamos procesando posiblemente millones de elementos (= bytes) aquí, por lo que es importante.
DDS
2
También me falla en Safari. @WilliamT. Sin embargo, la respuesta funciona para Firefox / Safari / Chrome.
ObscureRobot
"binario" es un nombre ligeramente engañoso, ya que no es una matriz de bits, sino una matriz de bytes.
Niels Abildgaard
1
"type: 'image / jpeg'": ¿qué sucede si se trata de una imagen png O si no conoce la extensión de la imagen de antemano?
Jasper
Crear Uint8Array al principio es mejor que crear una matriz y luego convertirla a Uint8Array.
Cuixiping
52

Este funciona en iOS y Safari.

Debe usar la solución ArrayBuffer de Stoive, pero no puede usar BlobBuilder, como indica vava720, así que aquí está el mashup de ambos.

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.
fuente
11
¡Excelente! Pero todavía podría mantener la cadena de mimo dinámica, como en la solución de Stoive, supongo. // separe el componente mime var mimeString = dataURI.split (',') [0] .split (':') [1] .split (';') [0]
Por Quested Aronsson
¿Qué pasa con el respaldo para iOS6 con el prefijo webkit? Como manejas esto?
Confile
30

Firefox tiene los métodos canvas.toBlob () y canvas.mozGetAsFile () .

Pero otros navegadores no lo hacen.

Podemos obtener dataurl del lienzo y luego convertir dataurl a objeto blob.

Aquí está mi dataURLtoBlob()función. Es muy corto.

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

Use esta función con FormData para manejar su lienzo o dataurl.

Por ejemplo:

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

Además, puede crear un HTMLCanvasElement.prototype.toBlobmétodo para el navegador no gecko engine.

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

Ahora canvas.toBlob()funciona para todos los navegadores modernos, no solo Firefox. Por ejemplo:

canvas.toBlob(
    function(blob){
        var fd = new FormData();
        fd.append("myFile", blob, "thumb.jpg");
        //continue do something...
    },
    'image/jpeg',
    0.8
);
cuixiping
fuente
1
El polyfill para canvas.toBlob mencionado aquí es la forma correcta de manejar este problema en mi humilde opinión.
Jakob Kruse
2
Me gustaría enfatizar lo último en esta publicación: "Ahora canvas.toBlob () funciona para todos los navegadores modernos".
Eric Simonton
25

Mi forma preferida es canvas.toBlob ()

Pero de todos modos, aquí hay otra forma de convertir base64 en un blob usando 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})
})

Sin fin
fuente
¿Qué es fetch y cómo es relevante?
Ricardo Freitas
Fetch es un método moderno de ajax que puede usar en lugar de, XMLHttpRequestya que la url de datos es solo una url, puede usar ajax para recuperar ese recurso y tiene una opción para decidir si lo desea como blob, arraybuffer o texto
Endless
1
@Endless 'fetch ()' una cadena base64 local ... ¡un truco realmente inteligente!
Diego ZoracKy
1
Tenga en cuenta que blob:y data:no son universalmente compatibles con todas las implementaciones de recuperación. Utilizamos este enfoque, ya que sabemos que solo trataremos con navegadores móviles (WebKit), pero Edge, por ejemplo, no lo admite itt: developer.mozilla.org/en-US/docs/Web/API/…
oligofren
19

Gracias a @Stoive y @ vava720 combiné los dos de esta manera, evitando usar BlobBuilder y ArrayBuffer en desuso

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

El estándar en evolución parece ser canvas.toBlob () no canvas.getAsFile () como Mozilla se atrevió a adivinar.

Todavía no veo ningún navegador que lo admita :(

Gracias por este gran hilo!

Además, cualquiera que intente la respuesta aceptada debe tener cuidado con BlobBuilder ya que encuentro que el soporte es limitado (y espacio de nombres):

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

¿Estaba utilizando el polyfill de otra biblioteca para BlobBuilder?

Chris Bosco
fuente
Estaba usando Chrome sin polyfills, y no recuerdo haber encontrado espacios de nombres. Espero ansiosamente canvas.toBlob(), parece mucho más apropiado que getAsFile.
Stoive
1
BlobBuilderparece estar en desuso a favor deBlob
sandstrom
BlobBuilder está en desuso y este patrón es horrible. Mejor sería: window.BlobBuilder = (window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder); las capturas de prueba anidadas son realmente feas y ¿qué sucede si ninguno de los constructores está disponible?
Chris Hanson el
¿Cómo es esto horrible? Si se produce una excepción y 1) BlobBuilder no existe, no pasa nada y se ejecuta el siguiente bloque. 2) Si existe, pero se lanza una excepción, entonces está en desuso y no debe usarse de todos modos, por lo que continúa en el siguiente bloque de prueba. Lo ideal sería verificar si Blob es compatible primero, e incluso antes de esta comprobación para toBlob
TaylorMac
5
var BlobBuilder = (window.MozBlobBuilder || window.WebKitBlobBuilder || window.BlobBuilder);

se puede usar sin el intento de captura.

Gracias a check_ca. Buen trabajo.

Nafis Ahmad
fuente
1
Esto aún arrojará un error si el navegador admite el BlobBuilder en desuso. El navegador utilizará el método anterior si lo admite, incluso si admite el nuevo método. Esto no es deseable, vea el enfoque de Chris Bosco a continuación
TaylorMac
4

La respuesta original de Stoive se puede corregir fácilmente cambiando la última línea para acomodar 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
fuente
3

Aquí hay una versión ES6 de la respuesta 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});
  }
}

Uso:

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

¡Gracias! @steovi para esta solución.

He agregado soporte a la versión ES6 y he cambiado de unescape a dataURI (unescape está en desuso).

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

hazlo simple: 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
fuente
-2

toDataURL te da una cadena y puedes poner esa cadena en una entrada oculta.

Gato Chen
fuente
¿Podría por favor dar un ejemplo? No quiero cargar una cadena base64 (que es lo <input type=hidden value="data:..." />que haría), quiero cargar los datos del archivo (como lo que <input type="file" />hace, excepto que no puedes establecer la valuepropiedad en estos).
Stoive
Esto debería ser un comentario en lugar de una respuesta. Por favor, elabore la respuesta con la explicación adecuada. @Cat Chen
Lucky
-5

Tuve exactamente el mismo problema que Ravinder Payal, y encontré la respuesta. Prueba esto:

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

var name = "image.jpg";
var parseFile = new Parse.File(name, {base64: dataURL.substring(23)});
feng
fuente
2
¿Realmente estás sugiriendo usar Parse.com? ¡Debe mencionar que su respuesta requiere dependencias!
Pierre Maoui
2
WTF? ¿Por qué alguien cargará el código de imagen base64 al servidor PARSE y luego lo descargará? Cuando podemos cargar directamente base64 en nuestros servidores y lo principal es que se necesitan los mismos datos para cargar la cadena base64 o el archivo de imagen. Y si solo quiere permitir que el usuario descargue la imagen, puede usar estowindow.open(canvas.toDataURL("image/jpeg"))
Ravinder Payal