NodeJS: salvando uma imagem codificada em base64 no disco

162

Meu aplicativo Express está recebendo um PNG codificado em base64 do navegador (gerado a partir da tela com toDataURL ()) e gravando-o em um arquivo. Mas o arquivo não é um arquivo de imagem válido e o utilitário "arquivo" simplesmente o identifica como "dados".

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
fonte
1
Eu atualizei resposta que eu acho que é o que você precisava, em primeiro lugar;)
Alfred
Obviamente, isso não é o que você pediu, mas (no meu caso) percebi que a melhor abordagem era apenas armazenar toda a cadeia codificada no meu banco de dados (você sempre pode carregá-la usando <img src="data:image/png;base64,..." />). Apenas uma opção a considerar para outras pessoas que usam este segmento como referência.
JSideris

Respostas:

324

Eu acho que você está convertendo os dados um pouco mais do que precisa. Depois de criar o buffer com a codificação adequada, você só precisa gravar o buffer no arquivo.

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

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

O novo Buffer (..., 'base64') converterá a string de entrada em um Buffer, que é apenas uma matriz de bytes, interpretando a entrada como uma string codificada em base64. Em seguida, você pode simplesmente escrever essa matriz de bytes no arquivo.

Atualizar

Como mencionado nos comentários, req.rawBodynão é mais uma coisa. Se você estiver usando express/ connect, deverá usar o bodyParser()middleware e o uso e req.body, se estiver fazendo isso usando o Nó padrão, precisará agregar os objetos de dataeventos recebidos Buffere fazer a análise dos dados dessa imagem no endretorno de chamada.

loganfsmyth
fonte
2
Além disso, há um pequeno erro de digitação no argumento writeFile no seu exemplo: "bufferData" -> "dataBuffer".
mahemoff
@RJ. req.rawBodycontém os dados da solicitação que são codificados como um URL de dados: developer.mozilla.org/en-US/docs/data_URIs . Então você precisa retirar a parte inicial para obter apenas os dados da base64 para salvar.
loganfsmyth
2
Isso é excelente, obrigado! Para aqueles que encontrarem isso no futuro, o rawBody não é mais uma propriedade de req. Você precisa usar o middleware do analisador de corpo expresso para obter os dados.
precisa
10
var base64Data = req.rawBody.split (',') [1];
Anja Ishmukhametova
@notgiorgi É melhor fazer uma nova pergunta com detalhes suficientes para reproduzir seu problema e vincular a um deles dizendo que você não conseguiu fazê-lo funcionar.
Loganfsmyth 11/08/16
22

esta é a minha solução completa que lê qualquer formato de imagem base64 e o salva no formato apropriado no banco de dados:

    // 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);
    }
Placeholder
fonte
alguém aqui para me responder ?? em relação a este??
Iam
Acabei de modificar seu código. fs.writeFile ("test.jpg", imageBuffer.data, função (err) {json_response ['sucesso'] = verdadeiro; res.json (json_response);}); imagem é carregada, mas resultado não é que gostar de mim .. erro: 502 Bad Gateway de realmente problema em res.json, por que isso não está a imprimir ...
iam
18

ATUALIZAR

Encontrei este link interessante como resolver seu problema em PHP . Eu acho que você esqueceu de substituir spacepor, +como mostrado no link.

Peguei este círculo de http://images-mediawiki-sites.thefullwiki.org/04/1/7/5/6204600836255205.png como exemplo, que se parece com:

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

Em seguida, eu o coloquei em http://www.greywyvern.com/code/php/binary2base64, que me retornou:

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

salvei essa sequência na base64qual li no meu 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
});

Recebo um círculo de volta, mas o engraçado é que o tamanho do arquivo mudou :)

FIM

Quando você lê a imagem, acho que você precisa configurar os cabeçalhos

Tomemos, por exemplo, imagepng da página PHP:

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

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

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

Acho que a segunda linha header('Content-Type: image/png');é importante, caso contrário sua imagem não será exibida no navegador, mas apenas um monte de dados binários é mostrado no navegador.

No Express, você simplesmente usaria algo como abaixo. Vou exibir seu gravatar, que está localizado em http://www.gravatar.com/avatar/cabf735ce7b8b4471ef46ea54f71832d?s=32&d=identicon&r=PG e é um arquivo jpeg quando você curl --head http://www.gravatar.com/avatar/cabf735ce7b8b4471ef46ea54f71832d?s=32&d=identicon&r=PG. Só solicito cabeçalhos porque senão o curl exibirá um monte de coisas binárias (o Google Chrome baixará imediatamente) para o console:

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
Alfred
fonte
Obrigado Alfred, mas neste caso de teste mínimo, não estou enviando nada de volta do servidor. Estou simplesmente gravando o arquivo em disco no servidor e parece que o arquivo em si não é uma imagem válida. Estou bastante certo de que a base64 está certa, mas parece haver um problema ao escrevê-la como binária.
mahemoff
1
Desculpe, eu entendi mal a pergunta: $. Vou tentar de novo
Alfred
1
Obrigado pela atualização, mas a substituição de espaço não funcionou para mim e, na verdade, não era necessária quando apliquei a solução de Logan. Para referência, a tela é muito simples no meu caso de teste: var context = canvas.getContext ('2d'); context.fillStyle = "# f89"; context.fillRect (50,50,100,100);
mahemoff
Ok, porque eu recebi a imagem de volta quando fiz isso, mas pelo menos o seu problema foi resolvido: P
Alfred
Interessante, não sei por que o toString ("binário") não estragou tudo no seu caso. De qualquer forma, os espaços não devem aparecer naturalmente na base64, portanto a substituição deve ser discutida. É com o exemplo que forneci de qualquer maneira. (Tentei uma variante com novas linhas inseridas manualmente, depois de ler a especificação MIME requer linhas com no máximo 72 caracteres, principalmente por paranóia ... ela funciona com ou sem as novas linhas, desde que toString ("binário" ) é descartado.)
mahemoff
6

Também tive que salvar imagens codificadas em Base64 que fazem parte dos URLs de dados, então acabei criando um pequeno módulo npm para fazê-lo, caso eu (ou outra pessoa) precisasse fazê-lo novamente no futuro. Chama-se ba64 .

Simplificando, ele usa um URL de dados com uma imagem codificada em Base64 e salva a imagem no seu sistema de arquivos. Pode salvar de forma síncrona ou assíncrona. Ele também possui duas funções auxiliares, uma para obter a extensão do arquivo da imagem e a outra para separar a codificação Base64 do data:prefixo do esquema.

Aqui está um exemplo:

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

Instalá-lo: npm i ba64 -S. O repositório está no GitHub: https://github.com/HarryStevens/ba64 .

PS: Mais tarde, ocorreu-me que ba64 é provavelmente um nome ruim para o módulo, pois as pessoas podem presumir que ele codifica e decodifica Base64, o que não ocorre (existem muitos módulos que já fazem isso). Ah bem.

Harry Stevens
fonte
2

Isso fez isso por mim de forma simples e perfeita.

Excelente explicação de Scott Robinson

Da imagem para a sequência base64

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

Da base64 string para a imagem

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

Maneira fácil de converter a imagem base64 em arquivo e salvar como uma identificação ou nome aleatório.

// 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
fonte
1

Convertendo de arquivo com string base64 para imagem png.

4 variantes que funcionam.

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

Abaixo da função para salvar arquivos, basta passar seu arquivo base64, ele retorna o nome do arquivo e salva no DB.

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
fonte
1
Trabalhou para mim. E pode ser usado para qualquer conversão base64. Ele trata todos os arquivos genericamente. Obrigado!
Guilherme Sampaio
1

Você pode usar uma biblioteca de terceiros como base64-img ou 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 para imagem
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
fonte