This page is about embedding raster images into svg or html as data URI string. Data URI being base64 string, we first need to convert our image into base64 strings. There I explain how to do it server side and client side.
Given image.png
as a valid image, let’s creates a *.b64 copy as a text file containing a valid base64 text string.
openssl base64 -in image.png -out image.b64
echo $(cat image.b64)
Alternatively, we can store it into a shell variable.
imgb64=$(cat image.png | openssl base64)
echo $imgb64
<img>
XML elements usually use the href
or xlink:href
attribute to link up to an external file such :
<image xlink:href="image.png" width="960" height="500" class="bg-image"></image>
Embedding requires to store the whole raster image as a data URI string. This data URI is made up with meta data plus the image base64 string, as shown below:
<image xlink:href="data:image/png;base64,<long-b64-data-URI-string-here>" width="300" height="100" class="bg-image"></image>
into which we got to inject the $imgb64
string.
As I build an online service, I can leverage the strength of serverside openssl
, NodeJS, JSdom, and D3js. My image.png
is converted into image.b64
itself loaded via native NodeJS fs.readFileSync()
into variable i64
.
var i64 = fs.readFileSync('./image.b64');
svg.append("image")
.attr("y","0")
.attr("x","0")
.attr("xlink:xlink:href", "data:image/png;base64,"+i64) // /!\
.attr("width", width)
.attr("height", height)
.attr("class", "bg-image");
A double xlink:xlink:href
or _:xlink:href
is needed[1]. The reason is that d3js used to infers the xlink
namespace from the href
attribute, and so delete the first xlink
[2]. When outputting standalone files, we do need xlink:href
which is required by various graphic softwares, so let’s put 2 of them, one will be delete, one will stay, so it finally works.
Similarly, broken xmlns
(XML namespaces[3]) needs to be hand-restored within to the svg elements. Something such :
var svg = window.d3.select("body").append("svg")
.attr(":xmlns","http://www.w3.org/2000/svg") // if not: file does not appear to have any style information
.attr(":xmlns:xlink","http://www.w3.org/1999/xlink") // if not: Namespace prefix xlink for href
I then print the whole using :
var svgheader = '<?xml version="1.0" encoding="utf-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'
fs.writeFileSync('image.svg', svgheader + window.d3.select("body").html());
I formerly though to work within web-browsers with my image.png, .html page, and *.js script on client side. In this case, I add to the d3js .js script the following:
// Convert raster data into base64 string
var converterEngine = function (input) {
var uInt8Array = new Uint8Array(input),
i = uInt8Array.length;
var biStr = []; //new Array(i);
while (i--) {
biStr[i] = String.fromCharCode(uInt8Array[i]);
}
var base64 = window.btoa(biStr.join(''));
console.log("2. base64 produced >>> " + base64); // check conversion result
return base64;
};
// Get the target raster file and convert it, return data
var getImageBase64 = function (url, callback) {
// 1. Loading file from url:
var xhr = new XMLHttpRequest(url);
xhr.open('GET', url, true);
xhr.responseType = 'arraybuffer'; // <= important!
xhr.callback = callback;
xhr.onload = function (e) {
if (this.status == 200) { // 2. When loaded, do:
console.log("1:Loaded response >>> " + this.response); // print-check xhr response
var imgBase64 = converterEngine(this.response); // convert BLOB to base64
this.callback(imgBase64); //execute callback function with data
}
};
xhr.send();
};
//SVG DOM injection
getImageBase64('./image.png', function (data) {
d3.selectAll("image") //select target
.attr("href", "data:image/png;base64," + data); // replace href link by data URI, d3js + client handle the missing xlink
})
[1]: Christophe Viau on xlink d3js hack/1/ /2/
[2]: Mike Bostock on xlink d3js management
[3]: D3js > Namespaces
[4]: Rabbits by Brad Ashburn, see gallery