NodeJS: guardar una imagen codificada en base64 en el disco

162

Mi aplicación Express está recibiendo un PNG codificado en base64 del navegador (generado a partir del lienzo con toDataURL ()) y lo está escribiendo en un archivo. Pero el archivo no es un archivo de imagen válido, y la utilidad "archivo" simplemente lo identifica como "datos".

var body = req.rawBody,
  base64Data = body.replace(/^data:image\/png;base64,/,""),
  binaryData = new Buffer(base64Data, 'base64').toString('binary');

require("fs").writeFile("out.png", binaryData, "binary", function(err) {
  console.log(err); // writes out file without error, but it's not a valid image
});
mahemoff
fuente
1
Actualicé la respuesta, que creo que es lo que necesitabas en primer lugar;)
Alfred
Obviamente, esto no es lo que pediste, pero (en mi caso) me di cuenta de que el mejor enfoque era almacenar toda la cadena codificada en mi base de datos (siempre puedes cargarla usando <img src="data:image/png;base64,..." />). Solo es una opción a considerar para otros que usan este hilo como referencia.
JSideris

Respuestas:

324

Creo que está convirtiendo los datos un poco más de lo necesario. Una vez que cree el búfer con la codificación adecuada, solo necesita escribir el búfer en el archivo.

var base64Data = req.rawBody.replace(/^data:image\/png;base64,/, "");

require("fs").writeFile("out.png", base64Data, 'base64', function(err) {
  console.log(err);
});

El nuevo Buffer (..., 'base64') convertirá la cadena de entrada en un Buffer, que es solo una matriz de bytes, interpretando la entrada como una cadena codificada en base64. Luego puede escribir esa matriz de bytes en el archivo.

Actualizar

Como se menciona en los comentarios, req.rawBodyya no es una cosa. Si está usando express/ connect, debe usar el bodyParser()middleware y usar req.body, y si está haciendo esto usando el Nodo estándar, entonces necesita agregar los objetos de dataeventos entrantes Buffery analizar esta información de la imagen en la enddevolución de llamada.

loganfsmyth
fuente
2
Además, hay un ligero error tipográfico en el argumento writeFile en su ejemplo: "bufferData" -> "dataBuffer".
mahemoff
@RJ. req.rawBodycontiene los datos de solicitud que están codificados como URL de datos: developer.mozilla.org/en-US/docs/data_URIs . Por lo tanto, debe quitar la parte inicial para obtener solo los datos de base64 para guardar.
loganfsmyth
2
Esto es excelente, gracias! Para aquellos que encuentren esto en el futuro, rawBody ya no es una propiedad de req. Debe usar el middleware express body parser para obtener los datos.
DigitalDesignDj
10
var base64Data = req.rawBody.split (',') [1];
Anja Ishmukhametova
@notgiorgi Es mejor hacer una nueva pregunta con suficientes detalles para reproducir su problema, y ​​vincular a esta pregunta diciendo que no puede hacer que funcione.
loganfsmyth
22

Esta es mi solución completa que leería cualquier formato de imagen base64 y lo guardaría en el formato adecuado en la base de datos:

    // Save base64 image to disk
    try
    {
        // Decoding base-64 image
        // Source: http://stackoverflow.com/questions/20267939/nodejs-write-base64-image-file
        function decodeBase64Image(dataString) 
        {
          var matches = dataString.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/);
          var response = {};

          if (matches.length !== 3) 
          {
            return new Error('Invalid input string');
          }

          response.type = matches[1];
          response.data = new Buffer(matches[2], 'base64');

          return response;
        }

        // Regular expression for image type:
        // This regular image extracts the "jpeg" from "image/jpeg"
        var imageTypeRegularExpression      = /\/(.*?)$/;      

        // Generate random string
        var crypto                          = require('crypto');
        var seed                            = crypto.randomBytes(20);
        var uniqueSHA1String                = crypto
                                               .createHash('sha1')
                                                .update(seed)
                                                 .digest('hex');

        var base64Data = 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAZABkAAD/4Q3zaHR0cDovL25zLmFkb2JlLmN...';

        var imageBuffer                      = decodeBase64Image(base64Data);
        var userUploadedFeedMessagesLocation = '../img/upload/feed/';

        var uniqueRandomImageName            = 'image-' + uniqueSHA1String;
        // This variable is actually an array which has 5 values,
        // The [1] value is the real image extension
        var imageTypeDetected                = imageBuffer
                                                .type
                                                 .match(imageTypeRegularExpression);

        var userUploadedImagePath            = userUploadedFeedMessagesLocation + 
                                               uniqueRandomImageName +
                                               '.' + 
                                               imageTypeDetected[1];

        // Save decoded binary image to disk
        try
        {
        require('fs').writeFile(userUploadedImagePath, imageBuffer.data,  
                                function() 
                                {
                                  console.log('DEBUG - feed:message: Saved to disk image attached by user:', userUploadedImagePath);
                                });
        }
        catch(error)
        {
            console.log('ERROR:', error);
        }

    }
    catch(error)
    {
        console.log('ERROR:', error);
    }
Marcador de posición
fuente
alguien aquí para responderme? ¿¿con respecto a este??
iam
Acabo de modificar tu código. fs.writeFile ("test.jpg", imageBuffer.data, function (err) {json_response ['success'] = true; res.json (json_response);}); la imagen está cargada pero el resultado no me gusta mucho ... error: 502 Bad Gateway en realidad es un problema en res.json, por qué esto no se imprime ...
iam
18

ACTUALIZAR

Encontré este interesante enlace sobre cómo resolver su problema en PHP . Creo que se olvidó de reemplazar spacepor +como se muestra en el enlace.

Tomé este círculo de http://images-mediawiki-sites.thefullwiki.org/04/1/7/5/6204600836255205.png como muestra que se parece a:

http://images-mediawiki-sites.thefullwiki.org/04/1/7/5/6204600836255205.png

Luego lo puse a través de http://www.greywyvern.com/code/php/binary2base64 que me devolvió:

data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAAAAACPAi4CAAAAB3RJTUUH1QEHDxEhOnxCRgAAAAlwSFlzAAAK8AAACvABQqw0mAAAAXBJREFUeNrtV0FywzAIxJ3+K/pZyctKXqamji0htEik9qEHc3JkWC2LRPCS6Zh9HIy/AP4FwKf75iHEr6eU6Mt1WzIOFjFL7IFkYBx3zWBVkkeXAUCXwl1tvz2qdBLfJrzK7ixNUmVdTIAB8PMtxHgAsFNNkoExRKA+HocriOQAiC+1kShhACwSRGAEwPP96zYIoE8Pmph9qEWWKcCWRAfA/mkfJ0F6dSoA8KW3CRhn3ZHcW2is9VOsAgoqHblncAsyaCgcbqpUZQnWoGTcp/AnuwCoOUjhIvCvN59UBeoPZ/AYyLm3cWVAjxhpqREVaP0974iVwH51d4AVNaSC8TRNNYDQEFdlzDW9ob10YlvGQm0mQ+elSpcCCBtDgQD7cDFojdx7NIeHJkqi96cOGNkfZOroZsHtlPYoR7TOp3Vmfa5+49uoSSRyjfvc0A1kLx4KC6sNSeDieD1AWhrJLe0y+uy7b9GjP83l+m68AJ72AwSRPN5g7uwUAAAAAElFTkSuQmCC

guardé esta cadena en la base64que leí en mi código.

var fs      = require('fs'),
data        = fs.readFileSync('base64', 'utf8'),
base64Data,
binaryData;

base64Data  =   data.replace(/^data:image\/png;base64,/, "");
base64Data  +=  base64Data.replace('+', ' ');
binaryData  =   new Buffer(base64Data, 'base64').toString('binary');

fs.writeFile("out.png", binaryData, "binary", function (err) {
    console.log(err); // writes out file without error, but it's not a valid image
});

Me devuelve un círculo, pero lo curioso es que el tamaño del archivo ha cambiado:) ...

FINAL

Cuando lees la imagen, creo que necesitas configurar los encabezados

Tome por ejemplo imagepng de la página PHP:

<?php
$im = imagecreatefrompng("test.png");

header('Content-Type: image/png');

imagepng($im);
imagedestroy($im);
?>

Creo que la segunda línea header('Content-Type: image/png');es importante; de ​​lo contrario, su imagen no se mostrará en el navegador, sino que solo se mostrarán un montón de datos binarios en el navegador.

En Express , simplemente usaría algo como a continuación. Voy a mostrar su gravatar que se encuentra en http://www.gravatar.com/avatar/cabf735ce7b8b4471ef46ea54f71832d?s=32&d=identicon&r=PG y es un archivo jpeg cuando usted curl --head http://www.gravatar.com/avatar/cabf735ce7b8b4471ef46ea54f71832d?s=32&d=identicon&r=PG. Solo solicito encabezados porque, de lo contrario, curl mostrará un montón de cosas binarias (Google Chrome va a descargar inmediatamente) a la consola:

curl --head "http://www.gravatar.com/avatar/cabf735ce7b8b4471ef46ea54f71832d?s=32&d=identicon&r=PG"
HTTP/1.1 200 OK
Server: nginx
Date: Wed, 03 Aug 2011 12:11:25 GMT
Content-Type: image/jpeg
Connection: keep-alive
Last-Modified: Mon, 04 Oct 2010 11:54:22 GMT
Content-Disposition: inline; filename="cabf735ce7b8b4471ef46ea54f71832d.jpeg"
Access-Control-Allow-Origin: *
Content-Length: 1258
X-Varnish: 2356636561 2352219240
Via: 1.1 varnish
Expires: Wed, 03 Aug 2011 12:16:25 GMT
Cache-Control: max-age=300
Source-Age: 1482

$ mkdir -p ~/tmp/6922728
$ cd ~/tmp/6922728/
$ touch app.js

app.js

var app = require('express').createServer();

app.get('/', function (req, res) {
    res.contentType('image/jpeg');
    res.sendfile('cabf735ce7b8b4471ef46ea54f71832d?s=32&d=identicon&r=PG');
});

app.get('/binary', function (req, res) {
    res.sendfile('cabf735ce7b8b4471ef46ea54f71832d?s=32&d=identicon&r=PG');
});

app.listen(3000);

$ wget "http://www.gravatar.com/avatar/cabf735ce7b8b4471ef46ea54f71832d?s=32&d=identicon&r=PG"
$ node app.js
Alfredo
fuente
Gracias Alfred, pero en este caso de prueba mínimo, no estoy enviando nada desde el servidor. Simplemente estoy escribiendo el archivo en el disco en el servidor, y parece que el archivo en sí no es una imagen válida. Estoy bastante seguro de que base64 es correcto, pero parece haber un problema al escribirlo como binario.
mahemoff
1
Lo siento, no entiendo la pregunta: $. Intentaré de nuevo.
Alfred
1
Gracias por la actualización, pero la sustitución de espacio no funcionó para mí, y en realidad no fue necesaria cuando apliqué la solución de Logan. Como referencia, el lienzo es muy simple en mi caso de prueba: var context = canvas.getContext ('2d'); context.fillStyle = "# f89"; context.fillRect (50,50,100,100);
mahemoff
Está bien porque recuperé la imagen cuando hice esto, pero al menos tu problema se ha resuelto: P
Alfred
Interesante, no estoy seguro de por qué toString ("binario") no lo estropeó en su caso. En cualquier caso, los espacios no deberían aparecer naturalmente en base64 de todos modos, por lo que el reemplazo debería ser discutible. Es con el ejemplo que he proporcionado de todos modos. (Intenté una variante con líneas nuevas insertadas manualmente, después de leer la especificación MIME requiere líneas de no más de 72 caracteres, en su mayoría por paranoia ... resulta que funciona con o sin líneas nuevas, siempre que toString ("binario" ) se descarta.)
mahemoff
6

También tuve que guardar las imágenes codificadas en Base64 que son parte de las URL de datos, así que terminé haciendo un pequeño módulo npm para hacerlo en caso de que yo (u otra persona) tuviera que volver a hacerlo en el futuro. Se llama ba64 .

En pocas palabras, toma una URL de datos con una imagen codificada en Base64 y guarda la imagen en su sistema de archivos. Se puede guardar de forma sincrónica o asincrónica. También tiene dos funciones auxiliares, una para obtener la extensión del archivo de la imagen y la otra para separar la codificación Base64 del data:prefijo del esquema.

Aquí hay un ejemplo:

var ba64 = require("ba64"),
    data_url = "data:image/jpeg;base64,[Base64 encoded image goes here]";

// Save the image synchronously.
ba64.writeImageSync("myimage", data_url); // Saves myimage.jpeg.

// Or save the image asynchronously.
ba64.writeImage("myimage", data_url, function(err){
    if (err) throw err;

    console.log("Image saved successfully");

    // do stuff
});

Instalarlo: npm i ba64 -S. Repo está en GitHub: https://github.com/HarryStevens/ba64 .

PD: Más tarde se me ocurrió que ba64 es probablemente un mal nombre para el módulo, ya que la gente puede suponer que codifica y decodifica Base64, lo que no hace (hay muchos módulos que ya lo hacen). Oh bien.

Harry Stevens
fuente
2

Esto lo hizo por mí simple y perfectamente.

Excelente explicación de Scott Robinson

De imagen a cadena base64

let buff = fs.readFileSync('stack-abuse-logo.png');
let base64data = buff.toString('base64');

De cadena base64 a imagen

let buff = new Buffer(data, 'base64');
fs.writeFileSync('stack-abuse-logo-out.png', buff);
dovk
fuente
1

Manera fácil de convertir la imagen base64 en archivo y guardarla como una identificación o nombre aleatorio.

// to create some random id or name for your image name
const imgname = new Date().getTime().toString();

// to declare some path to store your converted image
const path = yourpath.png    

// image takes from body which you uploaded
const imgdata = req.body.image;    

// to convert base64 format into random filename
const base64Data = imgdata.replace(/^data:([A-Za-z-+/]+);base64,/, '');
fs.writeFile(path, base64Data, 'base64', (err) => {
    console.log(err);
});

// assigning converted image into your database
req.body.coverImage = imgname
Carlos
fuente
1

Conversión de archivo con cadena base64 a imagen png.

4 variantes que funcionan.

var {promisify} = require('util');
var fs = require("fs");

var readFile = promisify(fs.readFile)
var writeFile = promisify(fs.writeFile)

async function run () {

  // variant 1
  var d = await readFile('./1.txt', 'utf8')
  await writeFile("./1.png", d, 'base64')

  // variant 2
  var d = await readFile('./2.txt', 'utf8')
  var dd = new Buffer(d, 'base64')
  await writeFile("./2.png", dd)

  // variant 3
  var d = await readFile('./3.txt')
  await writeFile("./3.png", d.toString('utf8'), 'base64')

  // variant 4
  var d = await readFile('./4.txt')
  var dd = new Buffer(d.toString('utf8'), 'base64')
  await writeFile("./4.png", dd)

}

run();
Vladimir Buskin
fuente
1

Debajo de la función para guardar archivos, simplemente pase su archivo base64, devolverá el nombre del archivo y guárdelo en la base de datos.

import fs from 'fs';
 const uuid = require('uuid/v1');

/*Download the base64 image in the server and returns the filename and path of image.*/
function saveImage(baseImage) {
    /*path of the folder where your project is saved. (In my case i got it from config file, root path of project).*/
    const uploadPath = "/home/documents/project";
    //path of folder where you want to save the image.
    const localPath = `${uploadPath}/uploads/images/`;
    //Find extension of file
    const ext = baseImage.substring(baseImage.indexOf("/")+1, baseImage.indexOf(";base64"));
    const fileType = baseImage.substring("data:".length,baseImage.indexOf("/"));
    //Forming regex to extract base64 data of file.
    const regex = new RegExp(`^data:${fileType}\/${ext};base64,`, 'gi');
    //Extract base64 data.
    const base64Data = baseImage.replace(regex, "");
    const filename = `${uuid()}.${ext}`;

    //Check that if directory is present or not.
    if(!fs.existsSync(`${uploadPath}/uploads/`)) {
        fs.mkdirSync(`${uploadPath}/uploads/`);
    }
    if (!fs.existsSync(localPath)) {
        fs.mkdirSync(localPath);
    }
    fs.writeFileSync(localPath+filename, base64Data, 'base64');
    return filename;
}
Shaik Matheen
fuente
1
Trabajó para mi. Y se puede usar para cualquier conversión de base64. Trata cada archivo de forma genérica. ¡Gracias!
Guilherme Sampaio
1

Puede usar una biblioteca de terceros como base64-img o base64-to-image .

  1. base64-img
const base64Img = require('base64-img');

const data = 'data:image/png;base64,...';
const destpath = 'dir/to/save/image';
const filename = 'some-filename';

base64Img.img(data, destpath, filename, (err, filepath) => {}); // Asynchronous using

const filepath = base64Img.imgSync(data, destpath, filename); // Synchronous using
  1. base64 a imagen
const base64ToImage = require('base64-to-image');

const base64Str = 'data:image/png;base64,...';
const path = 'dir/to/save/image/'; // Add trailing slash
const optionalObj = { fileName: 'some-filename', type: 'png' };

const { imageType, fileName } = base64ToImage(base64Str, path, optionalObj); // Only synchronous using
ns16
fuente