Android. ImageView with SVG Support
2015-11-09 18:32
471 查看
Android. ImageViewwith SVG Support:
http://www.codeproject.com/Articles/130791/Android-ImageView-with-SVG-Support
Android. ImageViewwith SVG Support
![](https://www.gravatar.com/avatar/51aaef60a3b47717deed08d5aa36c50d.jpg?d=identicon&s=150&r=pg)
Igor Kushnarev,
25 Nov 2010
CPOL
![]() ![]() | 4.82 (19 votes) |
|
Download source - 1.3 MB
Introduction
As you know, Android doesn’t support SVG format. But benefits of SVG are obvious. First, it’s scalable. You don’t need to have pictures in different resolutions, no need to scale, for example, bitmap image with a quality loss. SVG image can be scaled toany resolution and the quality will be the same. Second, SVG is an ordinary XML file, so its size can be much lesser than the raster format file of the same picture. Moreover, you can change a picture on the fly due to this feature. You can open an SVG file
in an ordinary text editor and look how it's composed. And so on… But as Android doesn’t deal with SVG, it will imply some native coding. The good news is that there won’t be a lot of native coding. There are some open source libraries for parsing and rasterizing
SVG format.
Prerequisites
There are a lot of tutorials how to begin the native development for the Android platform, so I won’t repeat all of them here. I will just give some useful tips.First, you need the Eclipse IDE. You can download it at [1].
Or as an alternative, you can use Motodev Studio [2]. I prefer the second one as it has some delicious features. BTW, you can install Motodev
Studio as a plugin for the Eclipse IDE. I couldn’t setup it on OpenSUSE, but as the plugin it works fine.
Once Eclipse is installed, add CDT plugin to it.
Add Android plugin [3].
After that, add Eclipse Sequoyah[4] plugin needed for the native debugging. But make sure you installed CDT before Sequoyah. As the Sequoyah
project claims, it installs all dependencies it didn’t really install CDT in my case. While installing Sequoyah, make sure you unchecked “Group Items by Category” and checked “Sequoyah Android Native Support”.
Also Windows users will need cygwin[5] (Add the
Cygwin/bin path to your system.). While installing it, setup development tools.
Download Android SDK [6].
And at last, you will need Android NDK. Download CrystaX NDK[7]. It has support for C++ exceptions, RTTI and Standard C++ Library.
In Eclipse preferences, set Android SDK & NDK locations. That’s all for now.
First Approach
For the first approach, I will use android-libsvg[8] library. Really it depends on libsvg, libpng, libjpeg, libexpat and zlib. Butat this moment, it has support for almost all features of SVG format. To get its sources, create
android-libsvgfolder somewhere in a file system and go to this folder in console (in cygwin for windows users) and run “
bzr branch lp:libsvg-android” command. Bazaar will download sources to this folder.
The Beginning
Ok. Create a new Android project “ImageViewSvg”. Now right click on the project and go to AndroidTools/Add Native support. It will create “jni” folder in the project. Delete all stuff from it and copy contents of “jni” folder
of
android-libsvgproject. Refresh jni folder in the project. Let’s look at
Android.mk file in “jni” folder. I'll explain some variables:
LOCAL_PATH := $(call my-dir) – my-dyr macro sets LOCAL_PATH variable used to locate source files into current directory
include $(CLEAR_VARS) – clears all local variables
LOCAL_MODULE – the name of the library
LOCAL_CFLAGS – sets compiler flags and some include directories
LIBJPEG_SOURCES, … - the list of sources files for each library that will be used
LOCAL_LDLIBS – links to additional libraries
LOCAL_SRC_FILES – the list of all source files to be compiled, here it contains all sources for all libraries
BUILD_SHARED_LIBRARY – link to mk file to build shared library
For more information, please see ANDROID-MK.TXT in NDK.
Sometimes on Windows, after restarting IDE, it can’t run ndk-build. Then right click the project and go to “Build Path/Configure Build Path” and change “Build command” to something like “bash /cygdrive/c/ndk/ndk-build”.
Next create
com.toolkits.libsvgandroidpackage and copy there SvgRaster.java from the
libsvg-androidproject. Preparations are over.
The ImageViewSvg Class
To makeImageViewclass to support SVG format, it’s enough to inherit from it and override some methods. But I want it to set standard
android:srcattribute as SVG file and to be able to pick the file from standard “drawable” folder vs. “raw” folder. At the beginning, let’s make all logic to be done in the constructor. To get access to the
android:srcattribute, add attrs.xml file to the res/values folder:
Hide Copy Code
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="ImageViewSvg"> <attr name="android:src"/> </declare-styleable> </resources>
Let’s look at
ImageViewclass constructor sources. It contains the following code:
Hide Copy Code
Drawable d = a.getDrawable(com.android.internal.R.styleable.ImageView_src); if (d != null) { setImageDrawable(d); }
And look at
setImageBitmapmethod. It just calls
setImageDrawable. So we can use it in our constructor if we have the corresponding bitmap. But what about getting file from “drawable” folder. Nothing supernatural – get
resource ID from the
android:srcattribute and read raw file into an input stream. Next,
libandroidsvggives us a way to parse SVG file:
So, the constructor will look like this:
Hide Shrink
![](http://www.codeproject.com/images/arrow-up-16.png)
Copy Code
public ImageViewSvg(Context context, AttributeSet attrs, int defStyle) {
// Let's try load supported by ImageViewformats
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();
}
}
}
}
}
Another problem is that SVG doesn’t have a size. It’s scalable and it’s all. Moreover
ImageViewlayout parameters can be set to
wrap_content,
fill_parentor we can set predefined size of the image. But when a layout is required, Android sets the size and we can override
onSizeChangedmethod. The only problem is
wrap_content
attribute. In this case, the size will be
0. The idea is to replace
with
wrap_content
fill_parenton the fly. But doing it in the constructor will give nothing. If you debug through source codes, you will see that parent layout drags layout parameters from attributes directly and calls
setLayoutParamsmethod. Let’s override it:
Hide Copy Code
@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); }
And
onSizeChanged:
Hide Copy Code
@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); }
And at last, it’s the time to try it. Create the following layout:
Hide Copy Code
<?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>
And run:
Debugging Native Code
To debug native code, you can follow the instructions given by Carlos Souto at Sequoyah Project [9]. But during the first time, some thingsare unclear. So there are several tips:
While configuring C++ debug configuration, the application really must be
app_process, don’t care Eclipse says the program doesn’t exist, it will be created later.
You should run ndk-gdb each time you run debugging. Sometimes the command should be ndk-gdb –adb=/tools/adb –force.
Don’t forget to set "debuggable" in the manifest.
Second Approach. Anti Grain Geometry.
There is another library where you can use to rasterize SVG files. It’s Anti Grain Geometry[10]. The only extra library that will be neededis
libexpat. We already have it in our project. In the jni folder, create folders like this:
Copy the corresponding files from agg sources folder into gpc/include/src folders. There in an
examples folder where you can find svg_viewer folder. Copy all files except
svg_test into aggsvg jni folder. It will parse and rasterize SVG with use of AGG. But it has only basic support for SVG and can’t parse complicated stuff. You should extend the parserby yourself. In aggsvg-android folder, create
aggsvgandroid.cpp file. The example parses SVG from a file system. To parse a
string, add the following method to a
parserclass:
Hide Shrink
![](http://www.codeproject.com/images/arrow-up-16.png)
Copy Code
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; } }
At the end of Android.mk file, add section to build another library. It’s pretty simple. Just clear variables after the first library build and set them to build another library. And here is the class to rasterize with use of AGG:
Hide Shrink
![](http://www.codeproject.com/images/arrow-up-16.png)
Copy Code
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::parserp(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());
}
};
And in sources, I’ve added an ability to test both approaches:
Conclusion
So, there are at last two ways to show SVG file in Android. The main benefit oflibsvg-androidis that it is ready to use, but it is more than three times slower than AGG, with which you should extend SVG parserby your own. Also with AGG, you get extra features for the image processing. I’ve just used
ImageViewin the layout, but to use it programmatically you, of cause, should override more methods, such as
setImageResourcefor example.
That’s all. Thanks!
Resources
http://www.eclipse.orghttp://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/android/ndk-r4.php?lang=en
https://launchpad.net/libsvg-android
http://www.eclipse.org/sequoyah/documentation/native_debug.php
http://www.antigrain.com/
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories