Digital Developer Conference: Cloud Security 2021 – Build the skills to secure your cloud and data Register free

Reuse existing C code with the Android NDK

The Android Software Developer Kit (SDK) used by many Android application authors requires the use of the Java (or now Kotlin) programming language. However, there is a large body of C language code available on the Internet that Android developers can use. The Android Native Developer Kit (NDK) permits an Android developer to reuse existing C source code within an Android application. This tutorial demonstrates using existing C code within an Android Java application. In the tutorial, you create a bar code generation application, similar to the bar codes generated in many popular loyalty apps. The application generates a bar code in the symbology known as 3 of 9, or commonly, Code39. Basics of bar coding technology are also introduced in this tutorial.

Before you start

To get the most out of this tutorial, you should be comfortable constructing Android applications with the Android SDK as well as have a basic familiarity with the C programming language. After completion, you will have learned how to create a Java Native Interface (JNI) library, which is written in C and compiled with the Native Development Kit, and incorporate the library into an Android application that is written in the Java language. The application demonstrates how to perform basic image-processing operations against raw image data. You will also have learned how the NDK incorporates into the Android Studio environment.

This tutorial introduces the Android Native Developer Kit within the Android Studio environment. The NDK is used to add functions to an Android application by using the C programming language. I begin the tutorial with a high-level look at the NDK and its common usage scenarios. From there, the topic of bar code symbology and uses is introduced, followed by an introduction and demonstration of this tutorial’s application, named “Bar code via JNI.” This application is a mix of SDK-based Java code and NDK-compiled C code. I move on to introduce the Java Native Interface, which is the technology of interest when working with the NDK. A look ahead to the completed project’s source files provides a roadmap for the application that is constructed in the application. Then, you construct this application yourself. The Java class and C source files are explained.

Prerequisites

You need the following tools to complete this tutorial:

I created the code samples for this tutorial on a MacBook Pro with Android Studio 3.5.1. The Android SDK can target most releases of the Android operating system because the NDK has been in existence for quite some time. The Android NDK supports two build types: CMake and “Side by Side”. This tutorial uses CMake. Figure 1 shows the SDK tools that are enabled in my build environment.

Figure 1. SDK tools SDK Tools

The Android Native Development Kit

Let’s begin with a look at the Android NDK and how it can be used.

The NDK is a software release that is available as a freed download from within the Android Studio environment. The NDK includes all of the components necessary to incorporate functions written in the C programming language into an Android application. The Android NDK documentation goes into depth around specific “helper” classes that enable the easy transition of data structures from the Java environment and the C environment. This tutorial relies on the image-handling capabilities of the NDK.

Two common uses of the NDK are to increase application performance and to use existing C code by porting it to the Android environment. Let’s look first at performance improvement. Writing code in C does not guarantee a significant increase in performance. In fact, poorly written native code can actually slow down an application when compared to a well-written Java application. Application performance improvements are available when carefully crafted functions that are written in C are used to perform memory-based or computationally intensive operations such as those demonstrated in this tutorial. In particular, algorithms that use pointer arithmetic are particularly ripe for use with the NDK. The second common use case for the NDK is to port an existing body of C code that is written for another platform such as Linux. This tutorial demonstrates the NDK in a way that highlights both the performance and the reuse cases.

The NDK contains a compiler and build scripts, allowing you to focus directly on the C source files and leave the build magic to the NDK installation.

One quick word around the idea of mixing languages, or more generally, the architecture of common languages. With each successive year, there is seemingly the introduction of a new programming language. Ruby. Python. PHP. Node. Which is the best? It depends, naturally. Most programming languages are syntactically unique and semantically novel approaches to making things “easier, better, or faster.” Invariably, these languages are implemented in the C programing language. When the new language falls short in some way, it is augmented by using a “native” extension. Native extensions are written in C, almost exclusively. Or assembler language, but that is the very deep end of the pool and not of interest to us now.

JNI, the Java Native Interface, is the term that is given for the extension mechanism of the Java programming environment, and the one demonstrated in this tutorial.

Before jumping into the application itself, let’s take a brief detour to discuss some fundamentals of bar code symbologies.

Bar codes, data collection, automated

It is hard to imagine today, but there was a time when products in the grocery store had no bar code labels. Pricing and purchasing involved the painstaking process of labeling each individual item, and then at checkout the clerk was required to key in each item. The process was comparatively slow, expensive, and error-prone. While shrinkage, or loss or theft of product, will be around forever, the lack of adequate tracking technology created an environment all too conducive to product “falling off the truck.” Enter the bar code, a revolution in material handling and to the benefit of the supply chain industry.

Bar codes come in a number of different formats, or “symbologies.” The most common symbology for consumers to have exposure to is the UPC, or Universal Product Code. Each product contains a label that represents a 12-digit code. The first part of the label identifies the manufacturer of the product, and the latter portion represents the specific product code. Various symbologies include error-checking approaches such as checksums and CRCs (cyclical redundancy check).

Today, bar code labels are printed as part of the packaging by the manufacturer. Each individual SKU (Stock Keeping Unit) has a numeric value that is represented by the bar code. Each retailer’s inventory and point-of-sale software track not only the current location of the product in the store, but also the current sales price that we pay at checkout. Bar codes, along with the scanning technology that brings their application to life, have revolutionized not only the retail experience but also the supply chain activities, of which the consumer partakes in only the last segment.

Much of the advancement in the use of barcoding has been driven by large retailers who have mandated that products include the bar codes and that the shipping boxes contain appropriately labeled information as well. Advances in barcoding are enabling the automated pick, pack, and ship activities that power modern commerce as we know it today. There would arguably be no next-day shipping if not for advances in material handling technologies, the most obvious of them being the bar code. The bar code is the grandfather of other technologies such as RFID and Near Field Communications (NFC).

Early implementations of bar code symbologies are known as one dimensional, or 1D, symbologies. These include Code39, Code 128, and POSTNET. Building on the success of 1D barcoding technology, the introduction of 2D bar codes, including PDF417 and QR Codes, has enabled the storage of more information in the same physical space. The information density is higher with a 2D bar code versus a 1D bar code. A common use for a QR code is to encode a URL to a product information website.

Bar code technology is inherently in the domain of computer vision. Many applications use a smartphone’s camera. However, industrial scanning was built upon laser technology, working with photo receptors that detect the reflection of alternating strips of black bars and blank spaces. Barcoding applications are essentially dynamic image-processing applications, with pixel manipulation and interaction with higher-level logic. This makes barcoding a good candidate for learning how to create a native Android application.

The application architecture

This tutorial demonstrates the construction of a simple bar code creation application. Figure 2 shows a screen capture from the Android Studio IDE with the project expanded to show the source and output files.

Figure 2. Android Studio project view
Android Studio project view

The application’s user interface is constructed with traditional Android development techniques of a single layout file (activity_main.xml) and a single Activity, implemented in the MainActivity.java file. A single CPP source file that is located in a folder named cpp beneath the project’s main folder contains the bar code creation routines. The NDK toolchain compiles the C source file into a shared library file named libnative-lib.so, which is then placed into the main output APK file. A library file is created for each target hardware platform or processor architecture, as shown in Figure 3. Table 1 enumerates the application’s source files.

Table 1. The required application source files

File Comment
MainActivity.java Extends the Android Activity class for user interface and application logic
native-lib.cpp Implements bar code creation routines
activity_main.xml Home page of the application user interface
AndroidManifest.xml Deployment descriptor for the Android application
CMakeLists.txt Makefile snippet that is used by the NDK to construct the JNI library with the CMake toolchain

If you do not have a working Android development environment, now is a great time to install Android Studio and SDKs. For more information on how to set up an Android development environment, see “Resources” for links to the required tools, plus some introductory articles and tutorials on developing applications for Android. Having a familiarity with Android is helpful in understanding this tutorial.

Figure 3. Multiple architectures supported by the NDK Supported architectures

Now that you have an overview of the architecture and the application, you can see how it looks when running on an Android device.

Demonstrating the application

Sometimes it is helpful to begin with the end in mind, so before you dive into the step-by-step process of creating this application, have a quick look at the application in action. The following screen images were captured from an emulator running on a MacBook. Figure 4 shows the home screen of the application at startup.

Figure 4. Home screen of the sample application Application home screen

The application is almost as simple as you can get, an EditText to permit the user to enter the wanted text for the bar code and a single Button widget to initiate the process. Below the Button is an ImageView, which is presently devoid of an image, but not for long. Tapping the Go! Button generates a bar code on the screen that encodes the supplied text, as shown in Figure 5.

Figure 5. Code 39 bar code containing the value 12345
Code 39 bar code

The generated bar code is in symbology 3 of 9, or Code 39. Here are the key characteristics of Code 39:

  • Each character includes exactly five bars and four spaces. (5 + 4 = 9), with the pattern BsBsBsBsB.
  • There are both narrow and wide bars or spaces.
  • In any given digit, there are exactly three bars or spaces that are wide and the balances are narrow.
  • The ratio of wide to narrow is typically 3 to 1 or 2 to 1.
  • In practice, a space of approximately twice the wide dimension is required between characters for proper decoding.
  • Code 39 bar codes should start and end with an asterisk (*) character.

Table 2 lists the valid characters in Code 39.

Table 2. Code 39 characters

Characters How many Cumulative count of characters
A-Z 26 26
0-9 10 36
1 37
Space 1 38
$ 1 39
/ 1 40
+ 1 41
% 1 42
. 1 43

Table 2 describes the “Standard Code 39,” which permits the encoding of 43 characters, plus the asterisk character. There is an extended Code 39 version that encodes the full lower 128 characters of the ASCII table. Our application implements the Standard Code 39 symbology.

The asterisk character is the start/stop character. Therefore, it should not be used within the bar code itself. Code 39 does not contain a check digit and is relatively simple to render.

A weakness of Code 39 is its relative low data density as compared to other symbologies that can encode more information within the same physical area. That’s OK. Code 39 is to bar coding what lay-ups are to basketball. Start here, and then advance to the 3-point line.

This is a good time to speak briefly around bar code scanners. There are a few aspects to a scanner, including:

  • Optics: How does it read the bar code? Typical options include laser, CCD (Charge Coupled Device), Image (camera), and pen-based (where you drag the pen along the bar code).

  • Supported symbologies: Which types of bar codes can it decode? Code 39 is just one of many symbologies. Sometimes, you must license different symbologies. It is common to configure a scanner for a particular application for only the bar code symbologies of interest for your application. This is a way that you can have your scanner “ignore” other bar codes. Additionally, representing different values in different symbologies can be an application strategy for reading multiple values more or less simultaneously.

  • Connectivity: Short-range wireless, similar to a wireless keyboard or mouse, a USB or serial cable, and Bluetooth are the most common varieties.

  • Interface: Keyboard wedge or direct data. A scanner set up as a keyboard wedge presents scanned data as though it were keyed in by a human. A direct data interface brings the data into your application as if it were a peripheral device, not keyed-in data.

Regardless of the type of scanner, a successfully decoded bar code presents the decoded data to the host device without start and stop characters and without any error-checking devices such as a checksum or CRC (cyclical redundancy check).

The bar code in Figure 4 actually contains the characters 12345, but when read by a bar code scanner, only 12345 is decoded and brought back to the application.

Before diving into the application, let’s look at one more Code 39 bar code that is generated with the application, which is shown in Figure 6. This time, I encode “IBM DEVELOPERWORKS”. Note that the bar code is much longer than the previously encoded “12345”. Code 39 is also known as a variable length bar code symbology, unlike something like UPC, which has a fixed length.

Figure 6. Longer bar code
Longer bar code

Now, it is your turn. Let’s build this application together.

Creating the application

Creating the application within the Android Studio is very straightforward. I would suggest starting with the project template for a Native application, though you can do this step later to enable an existing project for compiling with the NDK. Figure 7 shows the type of project to create.

Figure 7. Creating a new Android project with NDK capability Creating new Android project

Figure 8. Complete the new application wizard Completing the wizard

When completing the new project wizard, provide the following information:

  1. A valid project and package name.
  2. A build target.
  3. A save location, that is, the location on your hard disk where you want to store this application’s project and source files.
  4. Language. Your options are the Java language and Kotlin. This application uses the Java language.
  5. Minimum API level. This should match whatever platform you are targeting.

The final step for a new NDK project is to specify which version of C++ you are relying on. Don’t worry if you don’t know, or care, about this. You can just select Toolchain default. If this does matter to your application, feel free to make the appropriate selection. After you have populated the wizard screen, select Finish.

When the project is populated in Android Studio, you are ready to implement the source files necessary for this application. You begin with the user interface elements of the application.

Implementing the user interface

The user interface for this application is extremely straightforward. It contains a single Activity with a single EditText, a Button, and an ImageView widget to display the rendered bar code. Like many simple Android applications, the user interface is defined in the activity_main.xml file, which is shown in Listing 1.

Listing 1. UI layout file, activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">



    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center|top"
        android:orientation="vertical"
        tools:layout_editor_absoluteX="1dp"
        tools:layout_editor_absoluteY="138dp">


        <EditText
            android:id="@+id/etBarcode"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:ems="10"
            android:inputType="textPersonName"
            android:maxLength="20"
            android:text="12345" />


        <Button
            android:id="@+id/bBarcode"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Go!" />

        <ImageView
            android:id="@+id/ivBarcode"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:scaleType="fitCenter"
            tools:visibility="visible" />

        <TextView
            android:id="@+id/sample_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />


    </LinearLayout>

</android.support.constraint.ConstraintLayout>

After the UI has been defined in the layout file, the Activity code must be written to work with the UI. This is implemented in the MainActivity.java file, where the Activity class is extended. Listing 2 shows the code.

Listing 2. Application imports and class declaration

package com.navitend.ten31c;

import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;


public class MainActivity extends AppCompatActivity {


    private final String tag = MainActivity.class.getSimpleName();
    private EditText etBarcode;
    private ImageView ivBarcode;
    private Button bBarcode;


// BEGIN NATIVE CODE RELATED
    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }
    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();           // created by new application wizard
    public native void code39(String text,Bitmap bitmap);       // our function to generate code 3 of 9 bar code symbol

// END NATIVE CODE RELATED

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        bBarcode = findViewById(R.id.bBarcode);

        bBarcode.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                String textOfInterest = "*" +etBarcode.getText().toString().toUpperCase()+"*";
                Log.i(tag,"clicked! [" + textOfInterest + "]");
                DisplayMetrics displayMetrics = new DisplayMetrics();
                getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
                int height = displayMetrics.heightPixels;
                int width = displayMetrics.widthPixels;
                Bitmap bitmapWip = Bitmap.createBitmap(width-20,100,Config.ALPHA_8);
                code39(textOfInterest,bitmapWip);
                ivBarcode.setImageBitmap(bitmapWip);
            }
        });
        etBarcode = findViewById(R.id.etBarcode);
        ivBarcode = findViewById(R.id.ivBarcode);

        // Example of a call to a native method
        //TextView tv = findViewById(R.id.sample_text);
        //tv.setText(stringFromJNI());
    }


}

Let’s break this down into a few notable comments:

  1. There are a handful of member variables:

    • tag: This is used in all logging statements to help filter the LogCat during debugging.
    • etBarCode: This EditText widget permits the user to enter a value to be encoded.
    • ivBarCode: This ImageView renders the encoded data as a bar code image.
    • bBarcode: This Button widget initiates the encoding of the contents of etBarCode into an image through the native function that you will write.
  2. The “NDK Stuff” section included four lines:

    • The library containing the native code is loaded with a call to System.loadLibrary. Note that this code is contained in a block that is marked as “static.” This causes the library to be loaded when the application is started.
    • Prototype declaration for stringFromJNI: This function is created by the Android Studio new NDK application wizard. This is helpful to see the basic structure of a JNI function. You copy this and modify it for your purposes.
    • Prototype declaration for code39. This is the native function, which creates the Code 39 bar code dynamically.
  3. The onCreate method inflates the layout that is identified by R.layout.activity_main, obtains a reference to the ImageView widget (ivBarCode), the EditText widget (etBarcode), and the Button widget (bBarcode).

  4. There is a single additional method, which is the click handler for the Button widget. Here is a quick rundown of what the click handler is doing:

    • Getting a copy of the keyed-in text, making it uppercase (Code 39 only supports uppercase characters), and adding the start/stop asterisk characters that are required by bar code scanners to decode Code39.
    • The screen dimensions are obtained to create a bitmap, which uses the available space on the screen.
    • A new, empty Bitmap object is created by using the dynamically obtained dimensions.
    • You call your code39 native function, passing in two values: the string value to encode and a reference to your Bitmap.

Lastly, you display the bitmap in the ImageView. If all goes well, this image contains a properly encoded Code 39 representation of the text entered in etBarCode. This wraps up the UI code, and it’s time for you to implement the bar code rendering routines. First, you must create the library itself. In a real implementation, you would probably add the start and stop characters in the native function itself, but for this simple application, you just pre- and post-pend them to the data before passing into the code39 routine itself.

Creating the NDK files

The NDK creates shared libraries and relies on a makefile system. If you created your application using the wizard as described in this tutorial, you should have a folder labeled cpp in your project. Under that folder, there is a folder that is named includes that contains two files:

  • CMakeLists.txt: This is the makefile for the CMake toolchain. CMake is one of two NDK options. CMake is open source software that (somewhat magically) manages the build process for native applications. CMake integrates within Android Studio through the CMakeLists.txt file, which is explained below.

  • native-lib.cpp: This is the C++ file that implements the native bar code rendering function.

Listing 3 contains the CMakeLists.txt file contents.

Listing 3. CMakeLists.txt file

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.
        native-lib

        # Sets the library as a shared library.
        SHARED

        # Provides a relative path to your source file(s).
        native-lib.cpp)

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
        log-lib

        # Specifies the name of the NDK library that
        # you want CMake to locate.
        log)

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
        native-lib
        -ljnigraphics

        # Links the target library to the log library
        # included in the NDK.
        ${log-lib})

Much of this file is boilerplate from the new application wizard. Only a few lines in Listing 3 are actually noncomments. Among other things, this makefile (snippet) instructs the NDK to:

  1. Compile the native-lib.cpp source file into a shared library.
  2. Name the shared library. By default, the shared library naming convention is lib.so. The resulting file here is named libnative-lib.so. Recall in Figure 2 that this library is generated for each Android architecture that is supported by your project.
  3. Specify the required “input” libraries. The shared library relies on two built-in library files for logging (liblog.so) and JNI graphics (libjnigraphics.so). The logging library permits you to add entries to the LogCat, which is helpful during the development phase of your project. The graphics library provides routines for working with Android Bitmaps and their image data. The only modification required to this file from the wizard-generated version is the line to include the graphics library: -l jnigraphics.

The native-lib.cpp source file contains a few C include statements. Listing 4 shows the first few lines of the native-lib.cpp file. Most of the file is discussed later.

Listing 4. native-lib includes and macros

#include <jni.h>
#include <string>
#include <android/log.h>
#include <android/bitmap.h>

#define  LOG_TAG    "barcodeme"
#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)

The LOGI and LOGE macros make calls to the Logging facility and are equivalent in function to Log.i() and Log.e() in the Android SDK. The include statements provide the necessary declarations for the C compiler for JNI glue, logging, and bitmap handling.

Now, it’s time for you to implement the bar code rendering routine. However, before you examine the code itself, you must understand the naming convention of JNI functions.

When Java code calls a native function, it maps the function name to an expanded, or decorated, function that is exported by the JNI shared library. Here is the convention.

Java_fully_qualified_classname_functionname

For example, the code39 function is implemented in the C code as:

Java_com_navitend_ten31c_MainActivity_code39

The first two arguments to the JNI functions include a pointer to the JNI Environment and to the calling class object instance. For more information about the NDK and JNI, see “Resources.”

Building the library is very simple – Android Studio takes care of it for you! Candidly, while this is really awesome, it can also be a bit frustrating when everything is done for you. Perhaps that is just me showing my age, but I remember when we got to open a terminal (or DOS) window to have some of this fun. Today’s applications rely increasingly on build tools to hide the complexities from the programmer. This is great when everything works, but a bit maddening when all of the bloated build environment fails to deliver. Yes, I long for the command line, I confess.

One library file is generated for each supported hardware platform. The correct library is loaded at run time.

Let’s look at how the bar code rendering is implemented.

Creating bar codes

There are two functions that are shown in Listings 5 and 6. Listing 5 contains the JNI function that is called directly by the main Java code. Listing 6 shows a subset of a helper routine that manages the pattern of wide and narrow bars and spaces.

Listing 5. code39 function

extern "C" JNIEXPORT void JNICALL
Java_com_navitend_ten31c_MainActivity_code39 (
        JNIEnv * env,
        jobject  obj /* this */,
        jstring dataToEncode,
        jobject bitmapbarcode) {

    AndroidBitmapInfo infobarcode;
    void *pixelsbarcode;
    uint8_t    *imagedata;
    int ret;
    int y;
    int x;
    const int NARROW = 1;
    const int WIDE = 3;

    LOGI("code39");
    const char * szData = env->GetStringUTFChars(dataToEncode,NULL) ;

   // LOGI("data to print [%s]",szData);

    if ((ret = AndroidBitmap_getInfo(env, bitmapbarcode, &infobarcode)) < 0) {
        LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
        return;
    }


    if ((ret = AndroidBitmap_lockPixels(env, bitmapbarcode, &pixelsbarcode)) < 0) {
        LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
    }


    imagedata = (uint8_t *) pixelsbarcode;
    int left = 20; // start here
    const char *p = szData; // walk the string via a pointer
    int k=0;
    while (*p != 0)
    {
        const char * pattern = getSequence(*p);
        if (pattern != NULL) {
            int thiswidth =  0;

            for (int k=0;k<4;k+=1) {
                LOGI("k is [%d]",k);
                // bar
                if (pattern[k*2] == 0) thiswidth = NARROW;
                if (pattern[k*2] == '1') thiswidth = WIDE;
                LOGI("thiswidth is [%d]\t[%d]",thiswidth,left);
                for (y=10;y<=infobarcode.height - 10 - 1;y++) {
                    for (x=left;x<(left+thiswidth);x++) {
                        *(imagedata + x + (y * infobarcode.width)) = 255;
                    }
                }
                left+=thiswidth;
                // space
                if (pattern[k*2+1] == '0') thiswidth = NARROW;
                if (pattern[k*2+1] == '1') thiswidth = WIDE;
                left += thiswidth;
                LOGI("thiswidth is [%d]\t[%d]",thiswidth,left);
            }
            // final bar
            if (pattern[8] == '0') thiswidth = NARROW;
            if (pattern[8] == '1') thiswidth = WIDE;
            LOGI("thiswidth is [%d]\t[%d]",thiswidth,left);
            for (y=10;y<=infobarcode.height - 10 - 1;y++) {
                for (x=left;x<(left+thiswidth);x++) {
                    *(imagedata + x + (y * infobarcode.width)) = 255;
                }
            }
            // inter character gap
            left += WIDE * 2;
        }
        p++;
    }
    AndroidBitmap_unlockPixels(env, bitmapbarcode);

}

This function takes two arguments from the calling Java code: a string representing the data to encode and a bitmap for you to render your bar code image. Here is a walk-through of the code:

  1. The AndroidBitmapInfo structure, which is defined in bitmap.h, is helpful for learning about a Bitmap object.
  2. The AndroidBitmap_getInfo function, found in the jnigraphics library, obtains information about a specific Bitmap object. You need this to get the height of your bar code. This helps when you do pointer arithmetic later. Yes, you get to use pointers in this application!
  3. The AndroidBitmap_lockPixels function locks down the image data so that you can perform operations directly on the data.
  4. The AndroidBitmap_unlockPixels function unlocks previously locked pixel data. These functions should be called as a “lock/unlock pair.”
  5. Sandwiched between the lock and unlock functions you see the pixel operations.

Pointer fun

Image-processing applications in C typically involve the use of pointers. Pointers are variables that “point” to a memory address. The data type of a variable specifies the type and size of memory that you are working with. For example, a char represents a signed 8-bit value, so a char pointer (char *) lets you reference an 8-bit value and perform operations through that pointer. The image data is represented as uint8_t, which means an unsigned 8-bit value, where each byte holds a value ranging from 0 to 255. A collection of three 8-bit unsigned values represents a pixel of image data for a 24-bit image.

Working through an image involves working on the individual rows of data and moving across the columns. The Bitmap structure contains a member known as the “stride.” The stride represents the width, in bytes, of a row of image data. For example, a 24-bit color plus alpha channel image has 32 bits, or 4 bytes, per pixel. So an image with a width of 320 pixels has a stride of 320 x 4, or 1280 bytes. An 8-bit grayscale image has 8 bits, or 1 byte, per pixel. A grayscale bitmap with a width of 320 pixels has a stride of 320 x 1, or 320 bytes. With this information in mind, let’s look at the image-processing algorithm for generating a code39 bar code image.

  1. When the image data is “locked,” the base address of the image data is referenced by a pointer named imagedata.
  2. You define the starting point for your bar code image (left = 20) and the width of NARROW and WIDE bars/spaces.
  3. You set up a pointer to the incoming text and then iterate over it, character by character.
  4. For each character, you obtain the “sequence” or “pattern” of bars and spaces. Recall that each character has five bars with four spaces in between. The pattern tells you whether the individual bar or space is NARROW or WIDE. If the character is unrecognized, that is, not supported by Code 39, you ignore it.
  5. You use some simple pointer navigation and for loops to generate the bars. For spaces in the bar code, you simply advance the appropriate number of pixels by adding the value of NARROW or WIDE to the current value of “left.”
  6. You loop through the pattern by drawing four sets of bar+plus+space. Then, you draw the final bar. Feel free to improve upon this implementation. Your function relies on a helper function named getSequence, which takes a single character as an argument. Note that this is the old-school 8-bit char data type.

Listing 6. getSequence function

const char * getSequence(char charwewant) {
    switch (charwewant) {
        case '*':
            return "010010100";
        case '-':
            return "010000101";
        case '.':
            return "110000100";
        case ' ':
            return "011000100";
        case '$':
            return "010101000";
        case '/':
            return "010100010";
        case '+':
            return "010001010";
        case '%':
            return "000101010";
        case 'A':
            return "100001001";
        case 'B':
            return "001001001";
        case 'C':
            return "101001000";
        case 'D':
            return "000011001";
        case 'E':
            return "100011000";
        case 'F':
            return "001011000";
        case 'G':
            return "000001101";
        case 'H':
            return "100001100";
        case 'I':
            return "001001100";
        case 'J':
            return "000011100";
        case 'K':
            return "100000011";
        case 'L':
            return "001000011";
        case 'M':
            return "101000010";
        case 'N':
            return "000010011";
        case 'O':
            return "100010010";
        case 'P':
            return "001010010";
        case 'Q':
            return "000000111";
        case 'R':
            return "100000110";
        case 'S':
            return "001000110";
        case 'T':
            return "000010110";
        case 'U':
            return "110000001";
        case 'V':
            return "011000001";
        case 'W':
            return "111000000";
        case 'X':
            return "010010001";
        case 'Y':
            return "110010000";
        case 'Z':
            return "011010000";
        case '0':
            return "000110100";
        case '1':
            return "100100001";
        case '2':
            return "001100001";
        case '3':
            return "101100000";
        case '4':
            return "000110001";
        case '5':
            return "100110000";
        case '6':
            return "001110000";
        case '7':
            return "000100101";
        case '8':
            return "100100100";
        case '9':
            return "001100100";
        default:
            return NULL;
    }
}

This function takes a bit of a brute-force approach to comparing the charwewant in a switch statement. If the character is found, you return a pointer to a 9-digit string containing 1s and 0s. The 1s represent WIDE and the 0s represent NARROW. The nine bars/spaces are always the same. This pattern simply represents the width of the respective element. All Code 39 look like this.

Bar<space>Bar<space>Bar<space>Bar<space>Bar

Let’s look at one example, the number 3:

case '3':
    return "101100000";

The first bar is WIDE.
Then a NARROW space.
Then a WIDE bar.
Then a WIDE space.
Then a NARROW bar.
Then a NARROW space.
Then a NARROW bar.
Then a NARROW space.
Finally, a NARROW bar.

The combination of the code39 and getSequence functions generate a Code 39 representation of the data that is passed in from the user-facing application. You can implement other bar code symbologies in a similar manner.

Conclusion

This tutorial presented an example of using the Android Native Development Kit to incorporate functions in the C programming language. The function used here represents the creation of a Code 39 bar code. In a similar manner, any valid C code that is compatible for the Android platform can be used with the help of the Android NDK. In addition to the mechanics of using the NDK within Android Studio, you also learned some fundamental concepts around bar code data collection.

Downloads

The source code for this tutorial.