English translation
Source Code
Эта моя стать изначально была опубликована на сайте The Code Project. Оригинал Codeproject.
Введение
Как вы знаете, Андроид не поддерживает формат SVG. Однако у него есть масса преимуществ. Во первых - масштабируемость. Отпадает необходимость держать картинки в разных разрешениях. Нет необходимости, к примеру, масштабировать битмэп с потерей качества. SVG может быть отмасштабирована в любое разрешение и качество картинки будет тем же. Во-вторых, SVG - это обычный xml-файл, что в итоге размер файла намного меньше, чем та же картинка в растровом формате. Более того, картинку сожно изменять "на лету". SVG файл можно открыть в обычном текстовом редакторе и помотреть, как картинка формируется. И так далее... Но, так как, к сожалению, Android не поддерживает SVG, то придется немного повозиться с NDK. Хорошая новоость заключается в том, что возни будет немного. У нас имеются готовые библиотеки с открытым исходным кодом для растеризации SVG.В интернете достаточно уроков о том, как работать с NDK. Не будем повторяться. Единственно, немного полезных замечаний:
- Во-первых, необходим эклипс. Загрузить можно здесь: [1].
- Или, как альтернативу, можно использовать [2]. Раньше я предпочитал ее, но в последнее время она без бубна работает не очень...
- После установки эклипса, в нем нужно установить CDT плагин.
- ADT плагин [3].
- После этого, добавляем Eclipse Sequoyah[4] плагин, чтобы дебажить c/c++ код.
- Для пользователей Windows необходимо установить cygwin[5] (Добавляем Cygwin/bin в PATH. В эклипсе - cinfigure build path, и устанавливаем build command в что-то типа "bash C:\AndroidNDK\ndk-build").
- Android SDK [6].
- Android NDK. Используем CrystaX NDK[7]. (В текущей версии NDK (6) от Google Android NDK libsvg собрать не удалось)
- В настройках эклипса указываем пути к NDK и SDKIn
bzr branch lp:libsvg-android
”. Bazaar загрузит исходники.Ok. Теперь создаем новый проект “
ImageViewSvg
”. В контекстном меню проекта выбираем AndroidTools/Add Native support. К проекту добавится папка “jni”. Удаляем из нее все и копируем содержимое папки “jni” из android-libsvg
. Обновляем папку jni нашего проекта(F5). Посмотрим на файл Android.mk в папке “jni”. Что означают некоторые переменные:- LOCAL_PATH := $(call my-dir) – my-dyr макрос устанавливает LOCAL_PATH переменную, которая указывает где находятся файлы исходников(в текущем каталоге)
- include $(CLEAR_VARS) – Очищает вс локальные переменные
- LOCAL_MODULE – Имя библиотеки
- LOCAL_CFLAGS – Устанавливает флаги компилятора пути к "include" файлам
- LIBJPEG_SOURCES, … - Список всех файлов исходников, для каждой библиотеки, которая будет использоваться
- LOCAL_LDLIBS – Линки к дополнительным библиотекам
- LOCAL_SRC_FILES – Список всех исходников для всех библиотек
- BUILD_SHARED_LIBRARY – Линк к mk файлу, который построит "shared" библиотку
Более подробно смотрите файлANDROID-MK.TXT в NDK.
Иногда в WIndows, после рестарта эклипса, он не может запустить ndk-build. Необходимо переустановить "build command" в крнфигурации.
Далее создаем
com.toolkits.libsvgandroid
и копируем туда SvgRaster.java из проекта libsvg-android
. Все практически готово.Для поддержки классом
ImageView
SVG формата, необходимо перегрузить в нем некоторые методы. Но еще хотелось бы использовать стандартный атрибут android:src
как SVG файл и устанавливать его из стандартной папки“drawable” вместо папки “raw”. Вначале изменим конструктор. Для доступа к android:src
атрибуту, добавляем attrs.xml в res/values:<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="ImageViewSvg"> <attr name="android:src"/> </declare-styleable> </resources>Посмотрим на исходники конструктора класса
ImageView
. Имеем следующий код:Drawable d = a.getDrawable(com.android.internal.R.styleable.ImageView_src); if (d != null) { setImageDrawable(d); }
Далее посмотрим на метод
setImageBitmap
. Он просто вызывает setImageDrawable
. Т.о. мы можем использовать его, если у нас будет подходящий битмэп. Правда еще необходимо достать файл из “drawable”. Ну, здесь - как два пальца об асфальт - – достаем ID ресурса из android:src
атрибута и читаем его в поток. Далее, при помощи, libandroidsvg
парсим SVG: В итоге конструктор выглядит так:
public ImageViewSvg(Context context, AttributeSet attrs, int defStyle) { // Let's try load supported by ImageView formats super(context, attrs, defStyle); if(this.getDrawable() == null) { // Get defined attributes TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ImageViewSvg, defStyle, 0); // Getting a file name CharSequence cs = a.getText(R.styleable.ImageViewSvg_android_src); String file = cs.toString(); // Is it SVG file? if (file.endsWith(".svg")) { // Retrieve ID of the resource int id = a.getResourceId( R.styleable.ImageViewSvg_android_src, -1); if(id != -1){ try { // Get the input stream for the raw resource InputStream inStream = getResources().openRawResource(id); int size = inStream.available(); // Read into the buffer byte[] buffer = new byte[size]; inStream.read(buffer); inStream.close(); // And make a string mSvgContent = EncodingUtils.getString (buffer, "UTF-8"); // Parse it mSvgId = SvgRaster.svgAndroidCreate(); SvgRaster.svgAndroidParseBuffer (mSvgId, mSvgContent.toString()); SvgRaster.svgAndroidSetAntialiasing(mSvgId, true); mIsSvg = true; } catch (IOException e) { mIsSvg = false; e.printStackTrace(); } } } } }Другая проблема в том, что в SVG не указан его размер(Не всегда, в некоторых указан желаемый. См. Пы.Сы.). Более того
ImageView
параметры лэйаута могут быть установлены в wrap_content
, fill_parent
, или конкретные размеры. Используем метод onSizeChanged
. Единственная проблема - атрибут wrap_content
. В этом случае размер будет 0
. Нуобходимо будет заменить wrap_content
наfill_parent
на лету. К сожалению, это ничего не даст. Если взглянуть на исходники, то будет видно, что родительски лэйаут вытаскивает параметры прямо из атрибутов и вызывает метод setLayoutParams
. Перегрузим его:@Override public void setLayoutParams(ViewGroup.LayoutParams params){ if(mIsSvg) { // replace WRAP_CONTENT if needed if(params.width == ViewGroup.LayoutParams.WRAP_CONTENT && getSuggestedMinimumWidth() == 0) params.width = ViewGroup.LayoutParams.FILL_PARENT; if(params.height == ViewGroup.LayoutParams.WRAP_CONTENT && getSuggestedMinimumHeight() == 0) params.height = ViewGroup.LayoutParams.FILL_PARENT; } super.setLayoutParams(params); }Также
onSizeChanged
:@Override public void onSizeChanged(int w, int h, int ow, int oh){ if(mIsSvg){ //Create the bitmap to raster svg to Canvas canvas = new Canvas(); mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); canvas.setBitmap(mBitmap); // Render SVG with use of libandroidsvg SvgRaster.svgAndroidRenderToArea( mSvgId, canvas, 0, 0, canvas.getWidth(), canvas.getHeight()); this.setImageBitmap(mBitmap); } else super.onSizeChanged(w, h, ow, oh); }
И, в конце-концов, настала пора опробовать то, что получилось. Создадим такой лэйаут:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:background="#AA0000" android:layout_height="fill_parent" android:layout_width="fill_parent" android:layout_weight="1.0" android:gravity="center" > <com.imageviewsvg.controls.ImageViewSvg android:src="@drawable/lion" android:layout_width="100dip" android:layout_height="100dip" android:id="@+id/svgview" android:layout_gravity="center" /> </LinearLayout>
Запускаемся:
Отладка
Для отладки с/с++ кода следуйте инструкциям Кароса Соунто: [9]. Сначала там не все ясно. Несколько советов:- При настройке C++ конфигурации, приложение действительно должно быть
app_process
, не обращаем внимания на жалобы эклипса, что такого файла нет, Он будет создан позже. - Необходимо каждый раз запускать ndk-gdb при запуске приложения. Иногда команда должна быть ndk-gdb –adb=
/tools/adb –force. - Не забываем ставить "debuggable" в манифесте.
Другой подход. Anti Grain Geometry.
Это еще одна библиотека для растеризации SVG :[10]. Единственнаая дополнительная к ней библиотека -libexpat
. Она уже есть в проекте. В папке jni создаем еще одну:Копируем соответствующие файлы из исходников agg в gpc/include/src. Там, в папке examples есть папкаsvg_viewer. Копируем из нее все, за исключением svg_test в папкуaggsvg jni . Единственно, эта библиотека поддерживает только простые SVG. Необходимо будет дописывать парсер. В папке aggsvg-android , создаем файл aggsvgandroid.cpp. Пример парсит SVG из файловой системы. Для строки, добавляем следующий метод в класс
parser
:void parser::parse(const char *chars, int length){ char msg[1024]; XML_Parser p = XML_ParserCreate(NULL); if(p == 0) { throw exception("Couldn't allocate memory for parser"); } XML_SetParamEntityParsing(p, XML_PARAM_ENTITY_PARSING_ALWAYS); XML_UseForeignDTD(p, true); XML_SetUserData(p, this); XML_SetElementHandler(p, start_element, end_element); XML_SetCharacterDataHandler(p, content); int done = 0; std::string str = std::string(chars); std::istringstream inputString(str); while(true){ if(done) break; size_t len = inputString.readsome(m_buf, buf_size); done = len < buf_size; if(!XML_Parse(p, m_buf, len, done)) { sprintf(msg, "%s at line %d\n", XML_ErrorString(XML_GetErrorCode(p)), (int)XML_GetCurrentLineNumber(p)); throw exception(msg); } } XML_ParserFree(p); char* ts = m_title; while(*ts) { if(*ts < ' ') *ts = ' '; ++ts; } }
В конце файла Android.mk , добавляем секцию для построения
еще одной библиотеки. Все довольно просто. ПРосто очищаем переменные после построения первой
библиотеки и настраиваем их для построения второй.
Класс для растеризации с использованием AGG:
class SvgRasterizer{ agg::svg::path_renderer m_path; double m_min_x; double m_min_y; double m_max_x; double m_max_y; double m_x; double m_y; pix_format_e pixformat; agg::rendering_buffer m_rbuf_window; public: SvgRasterizer(pix_format_e format, uint32_t width, uint32_t height, void *pixels) : \ m_path(), \ m_min_x(0.0), \ m_min_y(0.0), \ m_max_x(0.0), \ m_max_y(0.0), \ pixformat(format) { m_rbuf_window.attach((unsigned char*)pixels, width, height, 4*width); } void parse_svg(const char* svg, int length){ // Create parser agg::svg::parser p(m_path); // Parse SVG p.parse(svg, length); // Make all polygons CCW-oriented m_path.arrange_orientations(); // Get bounds of the image defined in SVG m_path.bounding_rect(&m_min_x, &m_min_y, &m_max_x, &m_max_y); } void rasterize_svg() { typedef agg::pixfmt_rgba32 pixfmt; typedef agg::renderer_base<pixfmt> renderer_base; typedef agg::renderer_scanline_aa_solid<renderer_base> renderer_solid; pixfmt pixf(m_rbuf_window); renderer_base rb(pixf); renderer_solid ren(rb); agg::rasterizer_scanline_aa<> ras; agg::scanline_p8 sl; agg::trans_affine mtx; double scl; // Calculate the scale the image to fit given bitmap if(m_max_y > m_max_x) scl = pixf.height()/m_max_y; else scl = pixf.width()/m_max_x; // Default gamma as is ras.gamma(agg::gamma_power(1.0)); mtx *= agg::trans_affine_scaling(scl); m_path.expand(0.0); // Render image m_path.render(ras, sl, ren, mtx, rb.clip_box(), 1.0); ras.gamma(agg::gamma_none()); } };
В исходниках я добавил возможность протестировать обе библиотеки:
Заключение
Итак, имеется по меньшей мере два способа использования SVG в Андроиде. Основное преимуществоlibsvg-android
в том, что она готова к использования, но более чем в три раза медленнее AGG, в которой сы вынуждены дорабатывать парсер. Помимо того, в AGG имеется масса возможностей для работы с изображениями. Я показал лишь как можно использовать ImageView
в лэйауте, для программного использования необходимо будет еще перегрузить методы такие как setImageResource
, к примеру. На данный момент все, спасибо за внимание!
Ресурсы
- http://www.eclipse.org
- http://developer.motorola.com/docstools/motodevstudio
- http://developer.android.com/guide/developing/tools/adt.html
- http://www.eclipse.org/sequoyah/
- http://www.cygwin.com/
- http://developer.android.com/sdk/index.html
- http://www.crystax.net/en/android/ndk/6
- https://launchpad.net/libsvg-android
- http://www.eclipse.org/sequoyah/documentation/native_debug.php
- http://www.antigrain.com/
P.S. На Code Project вы также можете найти статью об использовании SVG как Drawable:
Drawable with SVG Support
Известные проблемы:
ibsvg-android не поддерживает представление цвета в виде rgb(255, 255, 255) (Inkscape). (По крайней мере на момент написания статьи.)
Проверьте файл и замените на fill: #ffffff
Возможно что-то еще. Проверяйте структуру SVG перед использованием.
Неподдреживаемые фичи будут выявляться только при отладке.