Image generation for Android applications from SVG

Introduction


Hello. Not so long ago, I decided to try myself as a developer for the well-known Android platform. On the development path, I ran into many problems and one of them turned out to be creating pictures for different screen resolutions. At first I tried to create pictures in PhotoShop, but it turned out to be chore, you need to save 5 pictures in different resolutions for any change, and I'm not a genius of this program. And then it occurred to me to draw pictures in SVG, before that I had experience working with this format, I did visualization for kursurs on queuing systems (emoticons ran to the cashier and sometimes accumulated in lines).

Implementation


So, in Sublime Text I compiled the necessary pictures, everything is cool and scalable, but as if to automatically save these SVGs in different resolutions. For Java, there are libraries that convert this kind of graphics, but it turned out they do not understand tags for example. As a result, I thought and decided to write an application myself that would save everything in the necessary permissions in its folders. The language I chose is JavaScript and the Node.js platform, since I have the main employment with them.

I started by looking for a module for Node.js that could convert SVG to regular PNG images, but the modules found saved the image only in the resolution that was set for the image with the width and height parameters. Spitting, I decided to write everything myself. A small list of tasks set before the application:

  • The client part converting pictures
  • A server that provides the necessary information to the client and saves pictures
  • Description in json format, which image in which resolutions to save
  • After converting all the images, the page in the browser should close and the server should stop


Client part

The following is the algorithm of the client part.

  1. Download information about the pictures and the resolutions to which they need to be converted
  2. We load the original SVG in the img tag and hang on the load event
  3. After loading, we draw this picture on the element
  4. We take out the picture from in text format by calling the toDataURL function ('image / png')
  5. We send the text of the image to the server and, if we still have something to convert, we return to step 2 or 3, depending on whether you need to upload another picture, otherwise we close the page


(function() {
  var DRAWABLE_RES_TYPES = [ 'ldpi', 'mdpi', 'hdpi', 'xhdpi', 'xxhdpi' ]
    , SVG_IMAGE_NAME_REGEXP = /[.*]*\/([^\/]+)\.svg$/i;
  var canvas = document.querySelector('canvas')
    , context = canvas.getContext('2d')
    , image = document.getElementById('origin_image');
  sendMsg('get_image_list', {}, function (imageInfo) {
    imageInfo.forEachAsync(function(group, callback) {
      group.imageList.forEachAsync(function(imagePath, callback) {
        var imageName = imagePath.match(SVG_IMAGE_NAME_REGEXP)[1];
        image.setAttribute('src', imagePath);
        image.addEventListener('load', function () {
          image.removeEventListener('load', arguments.callee);
          DRAWABLE_RES_TYPES.forEachAsync(function(drawableType, callback) {
            if (!(drawableType in group.sizes))
              return callback();
            canvas.width = group.sizes[drawableType].width;
            canvas.height = group.sizes[drawableType].height;
            context.drawImage(image, 0, 0, canvas.width, canvas.height);
            sendMsg('save_image', {
              name: imageName,
              type: drawableType,
              body: canvas.toDataURL('image/png')
            }, callback);
          }, callback);
        });
      }, callback);
    }, function() {
      sendMsg('all_is_done', {}, function() {
        window.close();
      });
    });
  });
})();

Secondary functions
function sendMsg(action, data, callback) {
  var xhr = new XMLHttpRequest();
  xhr.open('POST', 'http://127.0.0.1:1337/' + action, true);
  xhr.addEventListener('readystatechange', function () {
    if (xhr.readyState === xhr.DONE) {
      if (xhr.status === 200) {
        try {
          var parsedAnswer = JSON.parse(xhr.responseText);
        } catch (e) {
          console.log(e);
        }
        callback(parsedAnswer);
      } else
        console.log('Error with code ' + xhr.status + ': ' + xhr.statusText);
    }
  });
  xhr.send(JSON.stringify(data));
}
Array.prototype.forEachAsync = function (handler, callback) {
  var self = this
    , index = -1
    , tempCallback = function () {
      index++;
      if (self.length === index)
        callback && callback();
      else
        handler(self[index], tempCallback);
    };
  tempCallback();
}


Server side

Everything is clear here, a regular server on Node.js, which responds to client requests and saves pictures that come from the client. Attention, in my opinion, only the image saving function deserves, the rest can be viewed on GitHub, a link to the repository will be posted below. So the save function is as follows:

function saveImage(options, callback) {
  var regex = /^data:.+\/(.+);base64,(.*)$/
    , matches = options.body.match(regex)
    , imageExt = matches[1]
    , imageBody = new Buffer(matches[2], 'base64')
    , imagePath = config.outputFolder + '\\drawable-' + options.type + '\\' + options.name + '.' + imageExt;
  if (!fs.existsSync(config.outputFolder + '\\drawable-' + options.type))
    fs.mkdirSync(config.outputFolder + '\\drawable-' + options.type);
  fs.writeFile(imagePath, imageBody, function(err) {
    if (err)
      throw err;
    var msg = 'File ' + imagePath + ' is created.';
    console.log(msg);
    callback(err, { msg: msg });
  });
}


Everything is quite simple here, in the options parameter, parameters come from the client containing the text of the image, the name and type of resources for Android. Data is processed and written to a file. On the server side, the fs, http module and one third-party module called open are used , which provides a function for opening a link in a default browser.

config.json

This file stores basic information. Server information with port and hostname fields. The path to the application resource folder for which you want to generate pictures. And of course, information about SVG, their path and sizes for different resolutions. For example, there is only one object in which the list of files is placed, information about permissions and a description that is not used anywhere, but serves simply for convenience. If other images need their resolution, we simply create a new object in the input. Example:

{
  "server": {
    "port": 1337,
    "hostname": "127.0.0.1"
  },
  "input": [{
    "description": "Action bar icons",
    "imageList": [
      "/svg/ic_bar_add.svg",
      "/svg/other_picture.scg"
    ],
    "sizes": {
      "ldpi": { "width": 24, "height": 24 },
      "mdpi": { "width": 32, "height": 32 },
      "hdpi": { "width": 48, "height": 48 },
      "xhdpi": { "width": 64, "height": 64 },
      "xxhdpi": { "width": 96, "height": 96 }
    }
  }],
  "outputFolder": "C:\\svg-android\\out"
}


Conclusion


The project can be downloaded from GitHub at this link .

To start, enter the following line in the console:
node index.js

Do not forget to specify the folder where you want to save the pictures, it must exist.

Also popular now: