A Tiny Image Converter Application for Android

Emre Kut
The Startup
Published in
5 min readJul 24, 2020

In this article, I will show a more concrete example of an Android application that makes use of native C and C++ libraries. For this purpose, we will make a tiny image converter application that converts JPEG to TIFF using native C libraries through a thin C++ wrapper. The sample application basically lets you pick a jpeg image from your phone and convert that image to tiff format. The converted image is saved into Pictures folder under external storage. The following screenshot of the application is from an Android 9(API-28) device:

The application code is set up as an Android Studio Project and consists of three major components:

  • Android Application Code
  • JNI Wrapper Library(CMake External Native Build)
  • C++ ImageOps Library (CMake External Native Build)

Android application code does nothing about image conversion. It just provides UI functionality to choose a jpeg image from phone and pass this information to native code through Jni.

The Jni wrapper library consists of only one bridge function which is compiled into a shared library called “imageOps-jni.so”. Since native code talks to Android runtime with C linkage, there is (extern “C”) language linkage specifier in jni wrapper header.

C++ ImageOps library contains code that deals with image encoding and links dynamically to libjpeg and libtiff C libraries.

Native dependency relationship is as follows:

Decoding JPEG and Encoding TIFF

We will use libjpeg v9d to decode JPEG images and libtiff v4.1.0 to encode TIFF images. Jpeg images are decoded by the following native code which makes libjpeg calls:

And the following is the code that encodes TIFF with LZW compression in RGB format using libtiff:

Validation and Error Handling

Validating image extensions, image conversion and saving it to external storage are all handled in native C/C++ code.

All validation and encoding/decoding related errors are propagated as C++ exceptions and re-thrown as Java exceptions back to Java code :

Deploying the Project

Before we start, please download the following and install them to directory of your choice:

  • Android Studio (I used version 4.0)
  • Android NDK r21d
  • Android SDK

In earlier versions of Android NDK, you would have to create a standalone toolchain. Starting from NDK version r19, there is no need to do that anymore.

We have to know the location of Android sdk and ndk from the project. For that purpose, we need to create local.properties at the root directory of the project and provide ndk.dir, sdk.dir properties that reference to installations directories like this one:

local.properties

In order to use libtiff and libjpeg libraries in our Android application, we have to cross compile them using Android NDK for our target platforms (arm-v7a, arm64-v8a, x86, x86_64).

libtiff-4.1.0 and libjpeg-9d libraries are already built and included in the project. The intention of including “Building and Deploying the Dependencies” section is to show how you would build and deploy the dependencies if you created the project from scratch.

Building and Deploying the Dependencies

Firstly, let’s cross build the libjpeg and libtiff libraries (NDK uses Clang compiler) for Android. In my project, I have used Ubuntu 18.04 as development platform.

Note: Autoconf projects are generally not buildable on Windows. Windows users can build the libraries using WSL or MSYS.

Cross Building LibTIFF with Clang for Android

Extract the libtiff source to the directory of your choice and copy the following script (build-lib.sh) to the libtiff source root directory.

build-lib.sh script does the necessary job of setting up environment variables so that the build scripts call the correct cross compilation tools in NDK. What you have to do in the build-lib.sh script is to change <NDK-INSTALL-DIR> to the directory where you have installed Android NDK to (e.g. /home/testUser/android-ndk-r21d). Uncomment the corresponding TARGET line depending on your target platform and execute the script at the root directory of libtiff source. Libtiff has zlib dependency but there is no need to cross build zlib since prebuilt zlib library is already shipped with ndk-r21d.

Running build-lib.sh script should successfully build the libtiff and install the headers and compiled binaries to “install” folder under libtiff source root directory.

Cross Building LibJPEG with Clang for Android

Extract the libjpeg source to the directory of your choice and copy the same build-lib.sh script we used in previous section to the libjpeg source root directory. Follow the script modification steps as mentioned in previous section and execute the script from the root directory of libjpeg source. Running build-lib.sh script should successfully build the libjpeg and install the headers and compiled binaries to “install” folder under libjpeg source root directory.

Deploying The Libraries in Android Studio Project

We need to deploy libjpeg and libtiff shared libraries into Android Studio Project. We want Gradle to package our prebuilt native libraries that are not used in any external native build. Since we are using Gradle plugin 4.0, we need to keep prebuilt libraries in a directory other than jniLibs. (Important: Please don’t forget to copy libz.so from ndk package. Libtiff has libz dependency.) So, we add them to the src/main/libs/ABI directory of our module. Deployment should look like follows:

Native libraries in the final .apk and their dependencies in call sequence are as follows:

  • libimageOps-jni.so (JNI Wrapper for C++ ImageOps Library (External Native Build))
  • libimageOps.so (C++ ImageOps Library (External Native Build))
  • libtiff.so & libjpeg.so (Prebuilt 3rd party C Libraries)
  • libz.so (Prebuilt dependency of libtiff.so)
Native libraries in the final .apk
Native Libs Call Dependency

For more detail about how to bridge Android code with C/C++, you can check another article here.

You can clone the project from https://github.com/ekutt/AndroidImageConverter and open it in Android Studio.

Some Notes

  • You can also link Jni wrapper code, libimageOps, libjpeg, libtiff and libz libraries statically into a single shared library. Just change the project configuration accordingly for that.
  • If you have a build error similar to “Source foo.so and target foo.so must be different..” in Android Studio, deleting temporary folders “../app/build” and “../app/.cxx” worked for me.

References

--

--