234

My Express app is receiving a base64-encoded PNG from the browser (generated from canvas with toDataURL() ) and writing it to a file. But the file isn't a valid image file, and the "file" utility simply identifies it as "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
});
Liam
  • 27,717
  • 28
  • 128
  • 190
mahemoff
  • 44,526
  • 36
  • 160
  • 222
  • 1
    I updated answer which I think is what you needed in the first place ;) – Alfred Aug 03 '11 at 21:02
  • Obviously this this isn't what you asked for, but (in my case) I realized that the best approach was just to store the whole encoded string to my database (you can always load it using ``). Just an option to consider for others using this thread as a reference. – JSideris Sep 22 '16 at 00:48

10 Answers10

428

I think you are converting the data a bit more than you need to. Once you create the buffer with the proper encoding, you just need to write the buffer to the file.

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

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

new Buffer(..., 'base64') will convert the input string to a Buffer, which is just an array of bytes, by interpreting the input as a base64 encoded string. Then you can just write that byte array to the file.

Update

As mentioned in the comments, req.rawBody is no longer a thing. If you are using express/connect then you should use the bodyParser() middleware and use req.body, and if you are doing this using standard Node then you need to aggregate the incoming data event Buffer objects and do this image data parsing in the end callback.

loganfsmyth
  • 156,129
  • 30
  • 331
  • 251
  • 2
    Also, there's a slight typo in the writeFile argument in your example: "bufferData" -> "dataBuffer". – mahemoff Aug 04 '11 at 19:27
  • @RJ. `req.rawBody` contains the request data which is encoded as a data URL: https://developer.mozilla.org/en-US/docs/data_URIs. So you have to strip off the beginning part to get just the base64 data to save. – loganfsmyth Oct 19 '12 at 15:50
  • 2
    This is excellent stuff, thanks! For those that find this in the future, rawBody is no longer a property of req. You have to use the express body parser middleware to get the data. – DigitalDesignDj Dec 13 '12 at 22:48
  • 13
    var base64Data = req.rawBody.split(',')[1]; – Anja Ishmukhametova Jan 27 '14 at 20:16
  • @notgiorgi Best to ask a new question with enough details to reproduce your issue, and link to this one saying you couldn't get it to work. – loganfsmyth Aug 11 '16 at 17:33
  • What happens if the server gets a jpg file? – JSideris Sep 22 '16 at 00:24
  • @JSideris Best to ask a new question. – loganfsmyth Sep 22 '16 at 05:12
  • I couldn't get this to work for a long time. Eventually I figured out I need to do req.body.image.replace(/\s/g, '+');. Node converts plus to space as it converts it from a querystring to an object, which makes sense for text, but apparently is needed for encoded images sometimes. – stackers Apr 28 '18 at 18:15
  • Why do we remove ````/^data:image\/png;base64,/````? – Crupeng Dec 23 '20 at 15:15
  • @Crupeng The string after the `,` is the base64-encoded data of the image. Everything before that is metadata about that data, like the mimetype, charset and encoding. That stuff would ideally be saved somewhere too, or used to choose the file extension of the file, but it entirely depends on your own needs. – loganfsmyth Dec 23 '20 at 15:51
34

this is my full solution which would read any base64 image format and save it in the proper format in the database:

    // 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
  • 4,651
  • 6
  • 33
  • 35
  • any one here to answer me?? regarding this?? – iam Dec 08 '15 at 10:32
  • i just modified your code. fs.writeFile("test.jpg", imageBuffer.data, function(err ) { json_response['success'] = true; res.json(json_response); }); image is uploaded but result is not that liking to me.. error : 502 Bad Gateway actually problem in res.json , why this is not printing... – iam Dec 08 '15 at 10:33
  • This answer is a lifesaver! – Rahul Desai Dec 17 '20 at 01:53
31

This did it for me simply and perfectly.

Excellent explanation by Scott Robinson

From image to base64 string

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

From base64 string to image

let buff = Buffer.from(data, 'base64');
fs.writeFileSync('stack-abuse-logo-out.png', buff);
dovk
  • 493
  • 5
  • 7
  • 1
    updated the answer by replacing new with .from, to remove security warning – b26 Aug 15 '20 at 23:04
  • 5
    I suggest changing `let buff = new Buffer(data, 'base64);` to `let buff = Buffer.from(data, 'base64');` because `Buffer()` is deprecated due to security and usability issues. – Chrizzldi Apr 21 '22 at 13:10
20

UPDATE

I found this interesting link how to solve your problem in PHP. I think you forgot to replace space by +as shown in the link.

I took this circle from http://images-mediawiki-sites.thefullwiki.org/04/1/7/5/6204600836255205.png as sample which looks like:

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

Next I put it through http://www.greywyvern.com/code/php/binary2base64 which returned me:

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

saved this string to base64 which I read from in my code.

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

I get a circle back, but the funny thing is that the filesize has changed :)...

END

When you read back image I think you need to setup headers

Take for example imagepng from PHP page:

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

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

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

I think the second line header('Content-Type: image/png');, is important else your image will not be displayed in browser, but just a bunch of binary data is shown to browser.

In Express you would simply just use something like below. I am going to display your gravatar which is located at http://www.gravatar.com/avatar/cabf735ce7b8b4471ef46ea54f71832d?s=32&d=identicon&r=PG and is a jpeg file when you curl --head http://www.gravatar.com/avatar/cabf735ce7b8b4471ef46ea54f71832d?s=32&d=identicon&r=PG. I only request headers because else curl will display a bunch of binary stuff(Google Chrome immediately goes to download) to 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
  • 60,935
  • 33
  • 147
  • 186
  • Thanks Alfred, but in this minimal test case, I'm not sending anything back from the server. I'm simply writing the file to disk on the server, and it seems that the file itself is not a valid image. I'm fairly certain the base64 is right, but there appears to be a problem writing it out as binary. – mahemoff Aug 03 '11 at 13:10
  • 1
    Sorry I misunderstand question :$. I'll try again. – Alfred Aug 03 '11 at 19:04
  • 1
    Thanks for the update, but the space substitution didn't work for me, and actually wasn't necessary when I applied Logan's solution. For reference, the canvas is very simple in my test case: var context = canvas.getContext('2d'); context.fillStyle = "#f89"; context.fillRect(50,50,100,100); – mahemoff Aug 04 '11 at 19:29
  • Okay because I got image back when I did this, but at least your problem has been solved :P – Alfred Aug 04 '11 at 19:31
  • Interesting, not sure why the toString("binary") didn't mess it up in your case. In any event, spaces shouldn't naturally appear in base64 anyway, so the replace should be moot. It is with the example I've provided anyway. (I did try a variant with manually inserted newlines, after reading the MIME spec requires lines no greater than 72 characters, mostly out of paranoia...it turns out to work with *or* without the newlines, as long as toString("binary") is dropped.) – mahemoff Aug 04 '11 at 19:40
  • I also don't know why, but it worked. But I keep this topic as reference if I ever need to do what you wanted the correct way... – Alfred Aug 04 '11 at 19:54
  • replacing all the spaces with `+` worked for me, while no other solution did, thank you! – Amin Jafari Mar 29 '17 at 22:13
  • This works somehow, didn't need the space convert though, and you can do replace with ```b64 = b64.replace(/^data:.+;base64,/, '')``` instead to make it general. – Vidar May 30 '18 at 11:28
7

I also had to save Base64 encoded images that are part of data URLs, so I ended up making a small npm module to do it in case I (or someone else) needed to do it again in the future. It's called ba64.

Simply put, it takes a data URL with a Base64 encoded image and saves the image to your file system. It can save synchronously or asynchronously. It also has two helper functions, one to get the file extension of the image, and the other to separate the Base64 encoding from the data: scheme prefix.

Here's an example:

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

Install it: npm i ba64 -S. Repo is on GitHub: https://github.com/HarryStevens/ba64.

P.S. It occurred to me later that ba64 is probably a bad name for the module since people may assume it does Base64 encoding and decoding, which it doesn't (there are lots of modules that already do that). Oh well.

Harry Stevens
  • 1,373
  • 1
  • 15
  • 18
5

Below function to save files, just pass your base64 file, it return filename save it in 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;
}
Danny Buonocore
  • 3,731
  • 3
  • 24
  • 46
Shaik Matheen
  • 1,233
  • 15
  • 14
3

You can use a third-party library like base64-img or 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-to-image
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
  • 1,322
  • 2
  • 17
  • 26
2

Converting from file with base64 string to png image.

4 variants which works.

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
  • 614
  • 4
  • 8
1

Easy way to convert base64 image into file and save as some random id or name.

// 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
  • 458
  • 12
  • 23
0

is very simple


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

})()