如何将base64编码的图片保存到磁盘?

237

我的Express应用程序从浏览器接收一个base64编码的PNG图片(由canvas的toDataURL()生成),并将其写入文件。但是这个文件不是一个有效的图片文件,“file”实用程序只是把它标识为“data”。

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

1
我更新了答案,我认为这就是你一开始需要的 ;) - Alfred
显然这不是你要求的,但(在我的情况下)我意识到最好的方法就是将整个编码字符串存储到我的数据库中(你总是可以使用<img src="data:image/png;base64,..." />来加载它)。对于其他使用此线程作为参考的人来说,这只是一个需要考虑的选项。 - JSideris
10个回答

431

我认为你转换数据的步骤有些多余了。一旦你使用正确的编码创建了缓冲区,你只需要将该缓冲区写入文件即可。

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

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

使用 new Buffer(..., 'base64') 可以将输入字符串转换为一个 Buffer,即一个字节的数组,通过将输入解释为 base64 编码的字符串。然后,您可以将该字节数组直接写入文件。

更新

如评论中所述,req.rawBody 已经不存在了。如果你正在使用 express/connect ,那么你应该使用 bodyParser() 中间件并使用 req.body,如果你使用的是标准的 Node,则需要聚合输入的 data 事件 Buffer 对象,并在 end 回调函数中进行图像数据解析。


2
另外,在你的示例中,writeFile参数中有一个轻微的拼写错误:“bufferData” -> “dataBuffer”。 - mahemoff
2
这是非常棒的内容,谢谢!对于未来发现此内容的人,rawBody不再是req的属性。您必须使用express body parser中间件来获取数据。 - DigitalDesignDj
14
变量 base64Data 的值是通过将 req.rawBody 字符串按逗号分隔后的第二部分得到的。 - Anja Ishmukhametova
@notgiorgi 最好提出一个新问题,并提供足够的细节以重现您的问题,并链接到此问题,说明您无法使其正常工作。 - loganfsmyth
如果服务器收到一个jpg文件会发生什么? - JSideris
显示剩余5条评论

34

这是我的完整解决方案,可以读取任何Base64图像格式并以正确的格式保存在数据库中:

    // Save base64 image to disk
    try
    {
        // Decoding base-64 image
        // Source: https://dev59.com/KWIj5IYBdhLWcg3wb0rX
        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);
    }

有人在吗?可以回答我的问题吗?关于这个问题。 - iam
我刚刚修改了你的代码。fs.writeFile("test.jpg", imageBuffer.data, function(err ) { json_response['success'] = true; res.json(json_response); });图片已上传,但结果并不令我满意。错误:502 Bad Gateway 实际问题在于res.json,为什么它没有打印出来... - iam
这个答案真是救命稻草! - Rahul Desai

31

这对我来说非常简单且完美。

Scott Robinson的出色解释

从图像转换为base64字符串

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

从base64字符串转换为图像

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

1
通过用 .from 替换 new 来更新答案,以消除安全警告。 - b26
5
我建议将 let buff = new Buffer(data, 'base64); 更改为 let buff = Buffer.from(data, 'base64');,因为 Buffer() 已经被弃用,会存在安全和可用性问题。请注意,不要改变原来的意思。 - Chrizzldi

20

更新

我在这个链接中找到了一个有趣的解决方案,可以用PHP解决你的问题。我认为你忘记了按照链接中所示用+替换space

我从http://images-mediawiki-sites.thefullwiki.org/04/1/7/5/6204600836255205.png中获取了这个圆形样本:

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

接下来,我将其通过http://www.greywyvern.com/code/php/binary2base64进行转换,得到:

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

我将这个字符串保存到base64中,然后在我的代码中读取它。

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

我得到了一个圆形的返回值,但有趣的是文件大小已经改变了 :)...

END

当您读取图像时,我认为您需要设置头部信息。

以PHP页面中的imagepng为例:

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

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

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

我认为第二行header('Content-Type: image/png');非常重要,否则你的图像将不会在浏览器中显示,而只是一堆二进制数据。

Express中,你只需使用以下代码即可。我将展示您的gravatar,它位于http://www.gravatar.com/avatar/cabf735ce7b8b4471ef46ea54f71832d?s=32&d=identicon&r=PG,是一个jpeg文件,当您运行curl --head http://www.gravatar.com/avatar/cabf735ce7b8b4471ef46ea54f71832d?s=32&d=identicon&r=PG时。我仅请求标题,因为否则curl将向控制台显示一堆二进制内容(Google Chrome会立即下载):

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的建议,但在这个最小化的测试案例中,我没有从服务器发送任何内容。我只是将文件写入服务器上的磁盘,并且似乎该文件本身不是有效的图像。我相当确定base64是正确的,但好像在以二进制形式写出时存在问题。 - mahemoff
1
抱歉我误解了问题 :$。我会再试一次。 - Alfred
1
谢谢您的更新,但是空格替换对我没有起作用,而且当我应用Logan的解决方案时实际上也不必要。供参考,我的测试案例中画布非常简单:var context = canvas.getContext('2d'); context.fillStyle = "#f89"; context.fillRect(50,50,100,100); - mahemoff
好的,因为我在这样做时得到了图像,但至少你的问题已经解决了:P - Alfred
我也不知道为什么,但它起作用了。但如果我需要以正确的方式做你想要的事情,我会把这个主题作为参考... - Alfred
显示剩余3条评论

7
我还需要保存Base64编码的图像,这些图像是数据URL的一部分,所以我最终制作了一个小的npm模块来完成此操作,以防将来我(或其他人)需要再次使用它。它叫做ba64
简单地说,它接收一个带有Base64编码图像的数据URL并将该图像保存到您的文件系统中。它可以同步或异步保存。它还有两个辅助函数,一个用于获取图像的文件扩展名,另一个用于从data:方案前缀中分离出Base64编码。
下面是一个例子:
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
});

安装它:npm i ba64 -S。仓库在GitHub上:https://github.com/HarryStevens/ba64

P.S. 后来我想到,ba64可能是该模块的一个不好的名字,因为人们可能会认为它执行Base64编码和解码操作,但实际上它并不是(已经有很多模块可以做到这一点)。哦,算了。


5
以下是用于保存文件的函数,只需传递您的Base64文件,它将返回文件名并保存在数据库中。
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;
}

1
对我很有用。它可以用于任何base64转换。它通用地处理每个文件。谢谢! - Guilherme Sampaio

3

你可以使用第三方库,例如base64-imgbase64-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转图片
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

2

将包含base64字符串的文件转换为PNG图片。

以下有4种可行的变体:

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

1

base64 图像转换为文件并保存为随机 id 或名称的简便方法。

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

0

非常简单


const path = require('path');
const { readFile, stat, writeFile } = require("fs/promises");

(async () => {

    try {

        const contents = await readFile(path.join(__dirname, 'clau.jpg'), { encoding: 'base64' });

        console.log(contents);

        await writeFile(path.join(__dirname, 'claumia.jpg'), Buffer.from(contents, 'base64'));

    } catch (error) {
        console.log(error)
    }

})()


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接