block by hugolpz 7a2e24688591887f75c3

Embedding raster image

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.

Convert png to base64 file

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)

Convert png to base64 variable

Alternatively, we can store it into a shell variable.

imgb64=$(cat image.png | openssl base64)
echo $imgb64

Image SVG element : linking and embedding syntaxes.

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

Server side approach

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

Client side approach

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

See also

Notes

[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

README.md~

image.b64

image.svg