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
Сначала используем библиотеку android-libsvg [
8]. Кроме того, она зависит от libsvg, libpng, libjpeg, libexpat и zlib. Ее преимущество в том, что она поддерживает почти все фичи SVG. Создаем папку
android-libsvg где-нибудь в файловой системе и в консоли (или cygwin) выполняем “
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 перед использованием.
Неподдреживаемые фичи будут выявляться только при отладке.