Генерация картинок для Android приложения из SVG

в 13:00, , рубрики: android, javascript, node.js, svg, обработка изображений

Введение

Здравствуйте. Не так давно я решил попробовать себя в качестве разработчика под известную каждому платформу Android. На пути разработки я столкнулся с многими проблемами и одной из них оказалась создание картинок под разные разрешения экранов. Сначала я пробовал создать картинки в PhotoShop, но это оказалось муторно, нужно сохранять при любом изменении 5 картинок в разных разрешениях, да и не гений я этой программы. И тут мне пришла в голову мысль нарисовать картинки в SVG, до этого у меня был опыт работы с данным форматом, я делал визуализацию к курсачу по системам массового обслуживания (смайлики бегали в кассу и иногда скапливались в очереди).

Реализация

Итак, я в Sublime Text накодил нужные картинки, всё здорово и масштабируемо, но как бы автоматически сохранить в разных разрешениях эти SVG. Для Java есть библиотечки, которые конвертируют данный вид графики, но оказалось они не понимают к примеру теги <use>. В итоге, я подумал и решил сам написать приложение, которое бы сохраняло всё в нужных разрешениях по своим папкам. Язык на который пал мой выбор, это JavaScript и платформа Node.js, так как с ними у меня связана основная занятость.

Начал я с поиска модуля для Node.js, который мог бы преобразовывать SVG в обычные PNG картинки, но найденные модули, сохраняли картинку только в том разрешении, которое было задано у картинки параметрами width и height. Поплевавшись, я решил написать всё сам. Небольшой список задач поставленный перед приложением:

  • Клиентская часть, преобразовывающая картинки
  • Сервер, который даёт нужную информацию клиенту и сохраняет картинки
  • Описание в формате json, какую картинку в каких разрешениях сохранить
  • После преобразования всех картинок должна закрыться страница в браузере и остановиться сервер
Клиентская часть

Далее представлен алгоритм работы клиентской части.

  1. Загружаем информацию о картинках и о разрешениях, в которые их нужно преобразовать
  2. Загружаем в теге img исходную SVG и вешаемся на событие load
  3. После загрузки, рисуем эту картинку на элементе <canvas>
  4. Вытаскиваем картинку из <canvas> в текстовом формате с помощью вызова функции toDataURL('image/png')
  5. Отправляем на сервер текст картинки и, если у нас есть еще что преобразовать, возвращаемся к пункту 2 или 3, в зависимости о того нужно ли загрузить другую картинку, иначе закрываем страницу
(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();
      });
    });
  });
})();

Вспомогательные функции

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();
}
Серверная часть

Тут всё понятно, обычный сервер на Node.js, который отвечает на запросы клиента и сохраняет картинки, пришедшие с клиента. Внимания, на мой взгляд заслуживает только функция сохранения картинки, остальное можно будет посмотреть на GitHub, ссылка на репозиторий будет размещена ниже. Итак функция сохранения выглядит следующим образом:

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

Тут всё достаточно просто, в параметре options приходят параметры с клиента, содержащие текст картинки, название и тип ресурсов для Android. Данные обрабатываются и записываются в файл. На серверной стороне используются модуль fs, http и один сторонний под названием open, который предоставляет функцию для открытия ссылки в дефолтном браузере.

config.json

В данном файле хранится основная информация. Информация о сервере с полями port и hostname. Путь к папке с ресурсами приложения, для которого нужно генерировать картинки. И конечно же информация о SVG, их путь и размеры для разных разрешений. Для примера приведен всего один объект, в который помещен список файлов, информация о разрешениях и описание, которое нигде не используется, а служит просто для удобства. Если для других картинок нужно своё разрешение, мы просто создаем новый объект во входных данных. Пример:

{
  "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"
}

Заключение

Проект можно скачать с GitHub по данной ссылке.

Для запуска ввести в консоли следующую строку:
node index.js

Не забываем указать папку, в которую нужно сохранить картинки, она должна существовать.

Автор: Archi009

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js