Creating Sajuno Filter in Editor of Phimpme Android

What is Sajuno filter?

Sajuno filter is an image filter which we used in the editor of Phimpme Android application for brightening the skin region of a portrait photo.

How to perform?

Generally in a portrait photo, the dark regions formed due to shadows or low lightning conditions or even due to tanning of skin contains more darker reds than the greens and blues. So, for fixing this darkness of picture, we need to find out the area where reds are more dominant than the greens and blues. After finding the region of interest, we need to brighten that area corresponding to the difference of the reds and other colors.

How we implemented in Phimpme Android?

In Phimpme Android application, first we created a mask satisfying the above conditions. It can be created by subtracting blues and greens from red. The intensity can be varied by adjusting the intensity of reds. The condition mentioned here in programmatical way is shown below.


bright = saturate_cast<uchar>((intensity * r - g * 0.4 - b * 0.4));

In the above statement, r,g,b are the reds, greens and blues of the pixels respectively. The coefficients can be tweaked a little. But these are the values which we used in Phimpme Android application. After the above operation, a mask is generated as below.

 

This mask has the values that correspond to the difference of reds and greens and difference of reds and blues. So, we used this mask directly to increase the brightness of the dark toned skin of the original image. We simply need to add the mask and the original image. This results the required output image shown below.

 

As you can see the resultant image has less darker shades on the face than the original image. The way of implementation which we used in Phimpme Android editor is shown below.


double intensity = 0.5 + 0.35 * val;     // 0 < val < 1
dst = Mat::zeros(src.size(), src.type());
uchar r, g, b;
int bright;

for (y = 0; y < src.rows; y++) {
   for (x = 0; x < src.cols; x++) {
       r = src.at<Vec3b>(y, x)[0];
       g = src.at<Vec3b>(y, x)[1];
       b = src.at<Vec3b>(y, x)[2];

       bright = saturate_cast<uchar>((intensity * r - g * 0.4 - b * 0.4));
       dst.at<Vec3b>(y, x)[0] =
               saturate_cast<uchar>(r + bright);
       dst.at<Vec3b>(y, x)[1] =
               saturate_cast<uchar>(g + bright);
       dst.at<Vec3b>(y, x)[2] =
               saturate_cast<uchar>(b + bright);
   }
}

Resources:

Displaying an Animated Image in Splash Screen of Phimpme Android

A splash screen is the welcome page of the application. It gives the first impression of the application to the user. So, it is very important to make this page a better-looking one. In Phimpme Android, we had a normal page with a static image which is very common in all applications. So, in order to make it different from the most applications, we created an animation of the logo and added it to the splash screen.

As the splash screen is the first page/activity of the Phimpme Android application, most of the initialization functions are called in this activity. These initializations might take a little time giving us the time to display the logo animation.

Creating the animation of the Phimpme logo

For creating the animation of the Phimpme Android application’s logo, we used Adobe After Effects software. There are many free tools available on the web for creating the animation but due to the sophistic features present in After Effects, we used that software. We created the Phimpme Android application’s logo animation like any other normal video but with a lower frame rate. We used 12 FPS for the animation and it was fine as it was for a logo. Finally, we exported the animation as a transparent PNG formatted image sequence.

How to display the animation?

In Phimpme Android, we could’ve directly used the sequence of resultant images for displaying the animation. We could’ve done that by using a handler to change the image resource of an imageview. But this approach is very crude. So, we planned to create a GIF with the image sequence first and then display the GIF in the image view.

Creating a GIF from the image sequence

There are many tools on the web which create a GIF image from the given image sequence but most of the tools don’t support transparent images. This tool which we used to create the transparent GIF image supports both transparent and normal images. The frame rate and loop count can also be adjusted using this free tool. Below is the GIF image created using that tool.

Displaying the GIF in Phimpme

GIF image can be displayed in Phimpme Android application very easily using the famous Glide image caching and displaying library. But glide library doesn’t fulfill the need of the current scenario. Here, in Phimpme Android, we are displaying the GIF in the splash screen i.e. the next page should get displayed automatically. As we are showing an intro animation, the next activity/page should get opened only after the animation is completed. For achieving this we need a listener which triggers on the loop completion of the GIF image. Glide doesn’t provide any listener of this kind so we cannot Glide here.

There is a library named android-gif-drawable, which has the support for a GIF completion listener and many other methods. So, we used this for displaying the Phimpme Android application’s logo animation GIF image in the splash screen. When the GIF completed function gets triggered, we started the next activity if all the tasks which had to get completed in this activity itself are finished. Otherwise, we added a flag that the animation is done so that when the task gets completed, it just navigates to next page.

The way of the implementation described above is performed in Phimpme Android in the following manner.

First of all, we imported the library by adding it to the dependencies of build.gradle file.

compile 'pl.droidsonroids.gif:android-gif-drawable:1.2.7'

Then we added a normal imageview widget in the layout of the SplashScreen activity.

<ImageView
   android:id="@+id/imgLogo"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:scaleType="fitCenter"
   android:layout_centerInParent="true"
   />

Finally, in the SplashScreen.java, we created a GifDrawable object using the GIF image of Phimpme Android logo animation, which we copied in the assets folder of the Phimpme application. We added a listener to the GifDrawble and added function calls inside that function. It is shown below.

GifDrawable gifDrawable = null;
try {
   gifDrawable = new GifDrawable( getAssets(), "splash_logo_anim.gif" );
} catch (IOException e) {
   e.printStackTrace();
}
if (gifDrawable != null) {
   gifDrawable.addAnimationListener(new AnimationListener() {
       @Override
       public void onAnimationCompleted(int loopNumber) {
           Log.d("splashscreen","Gif animation completed");
           if (can_be_finished && nextIntent != null){
               startActivity(nextIntent);
               finish();
           }else {
               can_be_finished = true;
           }
       }
   });
}
logoView.setImageDrawable(gifDrawable);

Resources:

Creating Different Shades of a Color in Phimpme Android

Getting different shades of a particular color is very much useful when setting color to layouts which are just adjacent to each other. It maintains the uniformity of the theme and also creates a distinction between those layouts. In Phimpme Android application we used this method for setting colors for the status bar, navigation bar and few other foreground elements.

Use of different shades of a color

As per the normal theme of the application, we assigned the primary color of the theme to the  background color of the action bar in Phimpme Android application. The status bar will be present just above the action bar. So if we assign the same primary color to the status bar also, then there will be no distinction between both the views and it is not visibly attractive. For getting the distinction between both the views, we created a dark shade of the primary color of the current theme and set it as the background color of the status bar.

  

In Phimpme Android application we used a bottomnavigationview widget for navigating between activities in the application. It is placed at the bottom of the application window i.e. just above the system navigation bar. So, even here, for creating the distinction between both the views, we used the similar approach of getting the darker shade of the color, assigning it to the system navigation bar and normal shade to the bottomnavigationview of Phimpme Android application.

  

In the splash screen of the Phimpme Android application, the primary color of the theme is set as the main background. A progress bar also gets displayed on the screen. Its color should match the theme and should also get distinguished from the background. We could’ve used darker shade for this too, but a lighter foreground object would be visibly much better. So, we created a lighter shade of the primary color and used it as the color filter for the progress bar.

Implementation

In Phimpme Android, this is implemented by getting the color coordinates in HSV space corresponding to the given coordinates (here – primary color of theme) in RGB space and changing the luminance in that HSV space and remapping it back to RGB space. By this we get almost any shade of the color.

The implementation for getting darker shade of a color which we used for status bar in Phimpme application is shown below.

public int getDarkerShadeColor(int c){
   float[] hsv = new float[3];
   int color = c;
   Color.colorToHSV(color, hsv);
   hsv[2] *= 0.80f; 
   color = Color.HSVToColor(hsv);
   return color;
}

Generally 20% darker color would be enough for distinguishing from original color. So, we reduced the luminosity to 80% of its previous value and created darker color in the above function.

The implementation for getting lighter color is also almost same. It is given below

public int getLighterShadeColor(int c){
   float[] hsv = new float[3];
   int color = c;
   Color.colorToHSV(color, hsv);
   hsv[2] *= 1.35f; 
   color = Color.HSVToColor(hsv);
   return color;
}

As you can see that both functions are almost same except the multiplier. If the multiplier is less than 1 then the function returns darker shade of the original color and if the multiplier is greater than 1 then the function returns lighter shade of the color. So by using this function, one can produce any shade of a color dynamically without the need hard code in xml files.

Resources:

Integration of Flickr in Phimpme Android

Flickr is very widely used photo sharing website. So, we added a functionality in Phimpme Android to share the image to Flickr. Using this feature, the user can upload a picture to Flickr, without leaving the application with a single click. The way how we implemented this Flickr in our Phimpme Android application is shown below.

Setting up Flickr API

Flickr provides only an official web API. So we used the flickrj-android library built using the official Flickr API. First of all, we created an application in Flickr App Garden and requested API tokens from here. We noted down the resultant API key and secret key. These keys are necessary for authenticating Flickr in the application.

Steps for Authentication

Establishing the authentication with Flickr requires the following steps as per the official documentation.

  • Getting a request token
  • Getting User Authorization
  • Exchanging the Request Token for an Access Token
  • Use the Access Token with up.flickr.com/services/upload/ to upload pictures.

Authorizing the user of Flickr in Phimpme

As shown in the above picture, a request token has to be generated. Flickrj SDK provides it for us using Flickr object which further is created using the API key and Secret key. This request token is used for directing the user to a page for manually authorizing the Phimpme application to share the picture to his/her Flickr account. In Phimpme Android application, we created the Flickr object as shown below.

Flickr f = new Flickr(API_KEY, API_SEC, new REST());
OAuthToken oauthToken = f.getOAuthInterface().getRequestToken(
     OAUTH_CALLBACK_URI.toString());
saveTokenSecrent(oauthToken.getOauthTokenSecret());
URL oauthUrl = f.getOAuthInterface().buildAuthenticationUrl(
     Permission.WRITE, oauthToken);

Getting Access Token

After the manual authorization of the application, Flickr returns the OAuth token and OAuth verifier. These have to used for making another request to Flickr for getting the “Access Token” which gives us the access to upload the picture to Flickr account. For implementing this, intent data is collected when the application reopens after manual authorization. This data contains the OAuth token and OAuth verifier. These are passed to get Access Token from functions present Flickrj library. In Phimpme Android, we implemented this in following way.

Uri uri = intent.getData();
String query = uri.getQuery();
String[] data = query.split("&");
if (data != null && data.length == 2) {
  String oauthToken = data[0].substring(data[0].indexOf("=") + 1);
  String oauthVerifier = data[1].substring(data[1].indexOf("=") + 1);
  OAuth oauth = getOAuthToken();
  if (oauth != null && oauth.getToken() != null && oauth.getToken().getOauthTokenSecret() != null) {
  Flickr f = new Flickr(API_KEY, API_SEC, new REST());
  OAuthInterface oauthApi = null;
  if (f != null) {
     oauthApi = f.getOAuthInterface();
     return oauthApi.getAccessToken(oauthToken, oauthTokenSecret,verifier);}
  }
}

Uploading Image to Flickr

As we got the Access Token, we used that for creating new Flickr Object. The upload function present in the API requires three arguments – a file name, input stream and its metadata. The input stream is generated using the ContentResolver present in the Android. The code showing how we implemented uploading to Flickr is shown below.

Flickr f = new Flickr(API_KEY, API_SEC, new REST());
RequestContext requestContext = RequestContext.getRequestContext();
OAuth auth = new OAuth();
auth.setToken(new OAuthToken(token, secret));
requestContext.setOAuth(auth);
UploadMetaData uploadMetaData = new UploadMetaData();
uploadMetaData.setTitle(your file name);
InputStream is = getContentResolver().openInputStream(Uri.fromFile(your file object));
f.getUploader().upload(your file name, is, uploadMetaData);

The full implementation, using AsyncTasks and other well-optimized functions, is available in our Phimpme Android project repo on GitHub.

Resources

Using OpenCV for Native Image Processing in Phimpme Android

OpenCV is very widely used open-source image processing library. After the integration of OpenCV Android SDK in the Phimpme Android application, the image processing functions can be written in Java part or native part. Taking runtime of the functions into consideration we used native functions for image processing in the Phimpme application.

We didn’t have the whole application written in native code, we just called the native functions on the Java OpenCV Mat object. Mat is short for the matrix in OpenCV. The image on which we perform image processing operations in the Phimpme Android application is stored as Mat object in OpenCV.

Creating a Java OpenCV Mat object

Mat object of OpenCV is same whether we use it in Java or C++. We have common OpenCV object in Phimpme for accessing from both Java part and native part of the application. We have a Java bitmap object on which we have to perform image processing operations using OpenCV. For doing that we need to create a Java Mat object and pass its address to native. Mat object of OpenCV can be created using the bitmap2Mat() function present in the OpenCV library. The implementation is shown below.

Mat inputMat = new Mat(bitmap.getWidth(), bitmap.getHeight(), CvType.CV_8UC3);
Utils.bitmapToMat(bitmap, inputMat);

“bitmap” is the Java bitmap object which has the image to be processed. The third argument in the Mat function indicates that the Mat should be of type 8UC3 i.e. three color channels with 8-bit depth. With the second line above, the bitmap gets saved as the OpenCV Mat object.

Passing Mat Object to Native

We have the OpenCV Mat object in the memory. If we pass the whole object again to native, the same object gets copied from one memory location to another. In Phimpme application, instead of doing all that we can just get the memory location of the current OpenCV Mat object and pass it to native. As we have the address of the Mat, we can access it directly from native functions. Implementation of this is shown below.

Native Function Definition:

private static native void nativeApplyFilter(long inpAddr);

Native Function call:

nativeApplyFilter(inputMat.getNativeObjAddr());

Getting Native Mat Object to Java

We can follow the similar steps for getting the Mat from the native part after processing. In the Java part of Phimpme, we created an OpenCV Mat object before we pass the inputMat OpenCV Mat object to native for processing. So we have inputMat and outputMat in the memory before we send them to native. We get the memory locations of both the Mat objects and pass those addresses to native part. After the processing is done, the data gets written to the same memory location and can be accessed in Java. The above functions can be modified and rewritten for this purpose as shown below

Native Function Definition:

private static native void nativeApplyFilter(long inpAddr, long outAddr );

Native Function call:

nativeApplyFilter(inputMat.getNativeObjAddr(),outputMat.getNativeObjAddr());
inputMat.release();

if (outputMat !=null){
   Bitmap outbit = Bitmap.createBitmap(bitmap.getWidth(),bitmap.getHeight(),bitmap.getConfig());
   Utils.matToBitmap(outputMat,outbit);
   outputMat.release();
   return outbit;
}

Native operations on Mat using OpenCV

The JNI function in the native part of Phimpme application receives the memory locations of both the OpenCV Mat objects. As we have the addresses, we can create Mat object pointing that memory location and can be passed to processing functions for performing native operations just like all OpenCV functions. This implementation is shown below.

#include <jni.h>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include "enhance.h"
using namespace std;
using namespace cv;

JNIEXPORT void JNICALL
Java_org_fossasia_phimpme_editor_editimage_filter_PhotoProcessing_nativeApplyFilter(JNIEnv *env, jclass type, jlong inpAddr,jlong outAddr) {
       Mat &src = *(Mat*)inpAddr;
       Mat &dst = *(Mat*)outAddr;
       applyFilter(src, dst);
}

applyFilter() function can have any image processing operation. The implementation of edge detection function using OpenCV in the Phimpme Android is shown below. We were able to do this in very few lines which otherwise would have needed an extremely large code.  

Mat grey,detected_edges;
cvtColor( src, grey, CV_BGR2GRAY );
blur( grey, detected_edges, Size(3,3) );
dst.create( grey.size(), grey.type() );
Canny( detected_edges, detected_edges, 70, 200, 3 );
dst = Scalar::all(0);
detected_edges.copyTo( dst, detected_edges);
 

  

The general structure of an OpenCV function which is necessary for implementing custom image processing operations can be understood by referring this below-mentioned brightness adjustment function.  

int x,y,bright;
cvtColor(src,src,CV_BGRA2BGR);
dst = Mat::zeros( src.size(), src.type() );
for (y = 0; y < src.rows; y++) {
   for (x = 0; x < src.cols; x++) {
       dst.at<Vec3b>(y, x)[0] =
                  saturate_cast<uchar>((src.at<Vec3b>(y, x)[0]) + bright);
       dst.at<Vec3b>(y, x)[1] =
               saturate_cast<uchar>((src.at<Vec3b>(y, x)[1]) + bright);
       dst.at<Vec3b>(y, x)[2] =
               saturate_cast<uchar>((src.at<Vec3b>(y, x)[2]) + bright);
   }
}

    

Resources:

Integrating OpenCV for Native Image Processing in Phimpme Android

OpenCV is an open source computer vision library written in C and C++. It has many pre-built image processing functions for all kinds of purposes. The filters and image enhancing functions which we implemented in Phimpme are not fully optimized. So we decided to integrate the Android SDK of OpenCV in the Phimpme Android application.

For integrating OpenCV in the application, the files inside the OpenCV Android SDK have to be properly placed inside the project. The build and make files of the project have to be configured correspondingly.

Here, in this post, I will mention how we fully integrated OpenCV Android SDK and configured the Phimpme project.

Setting Up

First, we downloaded the OpenCV-Android-SDK zip from the official release page here.

Now we extracted the OpenCV-Android-SDK.zip file to navigated to SDK folder to see that there are three folders inside it. etc, Java and native.

All the build files are present inside the native directory and are necessarily copied to our project. The java directory inside the SDK folder is an external gradle module which has to imported and added as a dependency to our project.

So we copied all the files from jni directory of SDK folder inside OpenCV-Android-SDK to jni directory of the Phimpme project inside main. Similarly copied the 3rdparty directory into the main folder of the Phimpme project. Libs folder should also be copied into the main folder of the Phimpme but it has to be renamed to jniLibs. The project structure can be viewed in the below image.

Adding Module to Project

Now we headed to Android Studio and right clicked on the app in the project view, to add a new module. In that window select import Gradle project and browse to the java folder inside the SDK folder of OpenCV-Android-SDK as shown in figures.

Now we opened the build.gradle file of app level (not project level) and added the following line as a dependency.

  compile project(':openCVLibrary320')

Now open the build.gradle inside the openCVLibrary320 external module and change the platform tools version and  SDK versions.

Configuring Native Build Files

We removed all files related to cmake be we use ndk-build to compile native code. After removing the cmake files, added following lines in the application.mk file

APP_OPTIM := release
APP_ABI := all
APP_STL := gnustl_static
APP_CPPFLAGS := -frtti -fexceptions
APP_PLATFORM := android-25

Now we further added the following lines to the top of Android.mk file

OPENCV_INSTALL_MODULES:=on
OPENCV_CAMERA_MODULES:=on
OPENCV_LIB_TYPE := STATIC
include $(LOCAL_PATH)/OpenCV.mk

The third line here is a flag indicating that the library has to be static and there is no need for external OpenCVLoader application.

Finally, find this line

OPENCV_LIBS_DIR:=$(OPENCV_THIS_DIR)/../libs/$(OPENCV_TARGET_ARCH_ABI)

in OpenCV.mk file and replace it with

OPENCV_LIBS_DIR:=$(OPENCV_THIS_DIR)/../jniLibs/$(OPENCV_TARGET_ARCH_ABI)

And finally, add the following lines to java part. So that the OpenCV is initialized and linked to application

static {
   if (!OpenCVLoader.initDebug()) {
       Log.e( TAG + " - Error", "Unable to load OpenCV");
   } else {
       System.loadLibrary("modulename_present_in_Android.mk");
   }
}

With this, the integration of OpenCV and the configuration of the project is completed. Remove the build directories i.e. .build and .externalNativeBuild directories inside app folder for a fresh build so that you don’t face any wrong build error.

Resources

Passing Java Bitmap Object to Native for Image Processing and Getting it back in Phimpme Android

To perform any image processing operations on an image, we must have an image object in native part like we have a Bitmap object in Java. We cannot just pass the image bitmap object directly as an argument to native function because ‘Bitmap’ is a java object and C/C++ cannot interpret it directly as an image. So, here I’ll discuss a method to send java Bitmap object to Native part for performing image processing operations on it, which we implemented in the image editor of Phimpme Android Image Application.

C/C++ cannot interpret java bitmap object. So, we have to find the pixels of the java bitmap object and send them to native part to create a native bitmap over there.

In Phimpme, we used a “struct” data structure in C to represent native bitmap. An image has three color channels red, green, blue. We consider alpha channel(transparency) for an argb8888 type image. But in Phimpme, we considered only three channels as it is enough to manipulate these channels to implement the edit functions, which we used in the Editor of Phimpme.

We defined a struct, type-defined as NativeBitmap with attributes corresponding to all the three channels. We defined this in the nativebitmap.h header file in the jni/ folder of Phimpme so that we can include this header file in c files in which we needed to use a NativeBitmap struct.

#ifndef NATIVEBITMAP
#define NATIVEBITMAP
#endif
typedef struct {
  unsigned int width;
  unsigned int height;
  unsigned int redWidth;
  unsigned int redHeight;
  unsigned int greenWidth;
  unsigned int greenHeight;
  unsigned int blueWidth;
  unsigned int blueHeight;
  unsigned char* red;
  unsigned char* green;
  unsigned char* blue;
} NativeBitmap;

void deleteBitmap(NativeBitmap* bitmap);
int initBitmapMemory(NativeBitmap* bitmap, int width, int height);

As I explained in my previous post here on introduction to flow of native functions in Phimpme-Android, we defined the native functions with necessary arguments in Java part of Phimpme. We needed the image bitmap to be sent to the native part of the Phimpme application. So the argument in the function should have been java Bitmap object. But as mentioned earlier, the C code cannot interpret this java bitmap object directly as an image. So we created a function for getting pixels from a bitmap object in the Java class of Phimpme application. This function returns an array of unsigned integers which correspond to the pixels of a particular row of the image bitmap. The array of integers can be interpreted by C, so we can send this array of integers to native part and create a receiving native function to create NativeBitmap.

We performed image processing operations like enhancing and applying filters on this NativeBitmap and after the processing is done, we sent the array of integers corresponding to a row of the image bitmap and constructed the Java Bitmap Object using those received arrays in java.

The integers present in the array correspond to the color value of a particular pixel of the image bitmap. We used this color value to get red, green and blue values in native part of the Phimpme application.

Java Implementation for getting pixels from the bitmap, sending to and receiving from native is shown below.

private static void sendBitmapToNative(NativeBitmap bitmap) {
   int width = bitmap.getWidth();
   int height = bitmap.getHeight();
   nativeInitBitmap(width, height);
   int[] pixels = new int[width];
   for (int y = 0; y < height; y++) {
       bitmap.getPixels(pixels, 0, width, 0, y, width, 1); 
                    //gets pixels of the y’th row
       nativeSetBitmapRow(y, pixels);
   }
}
private static Bitmap getBitmapFromNative(NativeBitmap bitmap) {
   bitmap = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), srcBitmap.getConfig());
   int[] pixels = new int[width];
   for (int y = 0; y < height; y++) {
       nativeGetBitmapRow(y, pixels);
       bitmap.setPixels(pixels, 0, width, 0, y, width, 1);
   }
   return bitmap;
}

The native functions which are defined in Java part have to be linked to native functions. So they have to be created with proper function name in main.c (JNI) file of the Phimpme application. We included nativebitmap.h in main.c so that we can use NativeBitmap struct which is defined in nativebitmap.h.
The main functions which do the actual work related to converting integer array received from java part to NativeBitmap and converting back to an integer array of color values of pixels in rows of image bitmap are present in nativebitamp.c. The main.c file acts as Java Native Interface which helps in linking native functions and java functions in Phimpme Android.

After adding all the JNI functions, the main.c function looks as below.

main.c

static NativeBitmap bitmap;
int Java_org_fossasia_phimpme_PhotoProcessing_nativeInitBitmap(JNIEnv* env, jobject thiz, jint width, jint height) {
  return initBitmapMemory(&bitmap, width, height);  //function present
                                                    // in nativebitmap.c
}

void Java_org_fossasia_phimpme_PhotoProcessing_nativeSetBitmapRow(JNIEnv* env, jobject thiz, jint y, jintArray pixels) {
  int cpixels[bitmap.width];
  (*env)->GetIntArrayRegion(env, pixels, 0, bitmap.width, cpixels);
  setBitmapRowFromIntegers(&bitmap, (int)y, &cpixels);
}

void Java_org_fossasia_phimpme_PhotoProcessing_nativeGetBitmapRow(JNIEnv* env, jobject thiz, jint y, jintArray pixels) {
  int cpixels[bitmap.width];
  getBitmapRowAsIntegers(&bitmap, (int)y, &cpixels);
  (*env)->SetIntArrayRegion(env, pixels, 0, bitmap.width, cpixels);
                    //sending bitmap row as output
}

We now reached the main part of the implementation where we created the functions for storing integer array of color values of pixels in the NativeBitmap struct, which we created in nativebitmap.h of Phimpme Application. The definition of all the functions is present in nativebitmap.h, so we included that header file in the nativebitmap.c for using NativeBitmap struct in the functions. We implemented the functions defined in main.c i.e. initializing bitmap memory, setting bitmap row using integer array, getting bitmap row and a method for deleting native bitmap from memory after completion of processing the image in nativebitmap.c.

The implementation of nativebitmap.c after adding all functions is shown below. A proper explanation is added as comments wherever necessary.

void setBitmapRowFromIntegers(NativeBitmap* bitmap, int y, int* pixels) {
  //y is the number of the row (yth row)
  //pixels is the pointer to integer array which contains color value of a pixel
  unsigned int width = (*bitmap).width;
  register unsigned int i = (width*y) + width - 1;
                                //this represent the absolute                                 //index of the pixel in the image bitmap  
  register unsigned int x;      //this represent the index of the pixel 
                                //in the particular row of image bitmap
  for (x = width; x--; i--) {
          //functions defined above
     (*bitmap).red[i] = red(pixels[x]);
     (*bitmap).green[i] = green(pixels[x]);
     (*bitmap).blue[i] = blue(pixels[x]);
  }
}

void getBitmapRowAsIntegers(NativeBitmap* bitmap, int y, int* pixels) {
  unsigned int width = (*bitmap).width;
  register unsigned int i = (width*y) + width - 1; 
  register unsigned int x;              
  for (x = width; x--; i--) {
          //function defined above
     pixels[x] = rgb((int)(*bitmap).red[i], (int)(*bitmap).green[i], (int)(*bitmap).blue[i]);
  }
}

The native bitmap has to be initialized first before we set pixel values to it and has to be deleted from memory after completion of the processing. The functions for doing these tasks are given below. These functions should be added to nativebitmap.c file.

void deleteBitmap(NativeBitmap* bitmap) {
//free up memory
  freeUnsignedCharArray(&(*bitmap).red);//do same for green and blue
}

int initBitmapMemory(NativeBitmap* bitmap, int width, int height) {
  deleteBitmap(bitmap); 
             //if nativebitmap already has some value it gets removed
  (*bitmap).width = width;
  (*bitmap).height = height;
  int size = width*height;
  (*bitmap).redWidth = width;
  (*bitmap).redHeight = height;
            //assigning memory to the red,green and blue arrays and
            //checking if it succeeded for each step.
  int resultCode = newUnsignedCharArray(size, &(*bitmap).red);
  if (resultCode != MEMORY_OK) return resultCode;
            //repeat the code given above for green and blue colors
}

You can find the values of different color components of the RGB color value and RGB color values from the values of color components using the below functions. Add these functions to the top of the nativebitmap.c file.

int rgb(int red, int green, int blue) {
  return (0xFF << 24) | (red << 16) | (green << 8) | blue;
//Find the color value(int) from the red, green, blue values of a particular //pixel.
}

unsigned char red(int color) {
  return (unsigned char)((color >> 16) & 0xFF);
//Getting the red value form the color value of a particular pixel
}

//for green return this ((color >> 8) & 0xFF)
//for blue return this (color & 0xFF);

Do not forget to define native functions in Java part. As now everything got set up, we can use this in PhotoProcessing.java in the following manner to send Bitmap object to native.

Bitmap input = somebitmap;
if  (bitmap != null) {
    sendBitmapToNative(bitmap);
}

////Do some native processing on native bitmap struct
////Discussed in the next post

Bitmap output = getBitmapFromNative(input);
nativeDeleteBitmap();

Performing image processing operations on the NativeBitmap in the image editor of Phimpme like enhancing the image, applying filters are discussed in next posts.

Resources

Applying Filters on Images using Native Functions in Phimpme Android

In the Phimpme application, the user can apply multiple colorful filters on images captured from application’s camera or already available images on the device. This application of filters on images is performed using native image processing functions. We implemented many filters for enhancing the image. Implementation of few of the filter functions is shown below.

Filters are applied to an image by modifying the color values of pixels in the Phimpme application. This is similar to the implementation of image enhancing functions in the editor of Phimpme. My post on that is available here.

Black and White filter:

Black and white filter can be called as gray scaling the image. In a gray scale image, there will only be a single color channel. If multiple channels are present, the corresponding pixel values in all channels will be same. Here in Phimpme, we have an RGB image. It has 3 color channels. Every pixel has three values. Black and white filter can be implemented by replacing those three different values with the average of those values. The implementation of the function and the resultant image with the comparison is shown below.

void applyBlackAndWhiteFilter(Bitmap* bitmap) {
  register unsigned int i;
  unsigned int length = (*bitmap).width * (*bitmap).height;
  register unsigned char grey;
  unsigned char* red = (*bitmap).red;
  unsigned char* green = (*bitmap).green;
  unsigned char* blue = (*bitmap).blue;
  for (i = length; i--;) {
     grey = (red[i] + green[i] + blue[i]) / 3;
     red[i] = truncate((int) grey);
     green[i] = truncate((int) grey);
     blue[i] = truncate((int) grey);
  }
}

    

Ansel Filter

This Ansel Filter is a monotone filter present in Phimpme which is similar to black and white. Here in this filter, the contrast will be little high and gives the image artistic look. This is achieved in Phimpme by hard overlaying the gray pixel components of the image. The rest is same as the black and white filter. The implementation of hard overlay blending and the Ansel function is shown below with the resultant images.

static unsigned char hardLightLayerPixelComponents(unsigned char maskComponent, unsigned char imageComponent) {

  return (maskComponent > 128) ? 255 - (( (255 - (2 * (maskComponent-128)) ) * (255-imageComponent) )/256) : (2*maskComponent*imageComponent)/256;
}

void applyAnselFilter(Bitmap* bitmap) {
/*initializations*/
  unsigned char br,bg,bb;
  for (i = length; i--; ) {
       grey = (red[i] + green[i] + blue[i]) / 3;
       int eff = hardLightLayerPixelComponents(grey, grey);
       red[i] = truncate(eff);
       green[i] = truncate(eff);
       blue[i] = truncate(eff);
  }
}

    

Sepia Filter

The Sepia Filter in Phimpme results in a monotone image with orangish yellow tone. Its implementation uses pre-defined look up tables(LUTs) for all the three channels. The luminosity of a particular pixel is found out and then the red, green, blue values are found out from the look up tables(LUTs) corresponding to that luminosity. The look up table arrays we used for the sepia effect in Phimpme are given below and the implementation is also shown below.

const unsigned char sepiaRedLut[256] = {24, 24, 25, 26, 27, 28, 29, 30, 30, 30, 31, 32, 33, 34, 35, 36, 37, 37, 38, 38, 39, 40, 41, 42, 43, 43, 44, 45, 46, 47, 47, 48, 49, 50, 50, 51, 52, 53, 54, 55, 56, 57, 57, 58, 58, 59, 60, 61, 62, 63, 64, 64, 65, 66, 67, 68, 69, 70, 71, 71, 72, 72, 73, 74, 75, 76, 77, 78, 78, 79, 80, 81, 82, 83, 84, 85, 85, 86, 87, 88, 89, 89, 90, 91, 92, 93, 93, 94, 95, 96, 97, 97, 98, 99, 100, 101, 102, 102, 103, 104, 105, 106, 107, 108, 109, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 146, 147, 148, 149, 150, 151, 152, 153, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 178, 180, 181, 182, 183, 184, 185, 186, 186, 187, 188, 189, 190, 191, 193, 194, 195, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 255};

const unsigned char sepiaGreenLut[256] = {16, 16, 16, 17, 18, 18, 19, 20, 20, 20, 21, 22, 22, 23, 24, 24, 25, 25, 26, 26, 27, 28, 28, 29, 30, 30, 31, 31, 32, 33, 33, 34, 35, 36, 36, 36, 37, 38, 39, 39, 40, 41, 42, 43, 43, 44, 45, 46, 47, 47, 48, 48, 49, 50, 51, 51, 52, 53, 54, 54, 55, 55, 56, 57, 58, 59, 60, 61, 61, 61, 62, 63, 64, 65, 66, 67, 67, 68, 68, 69, 70, 72, 73, 74, 75, 75, 76, 77, 78, 78, 79, 80, 81, 81, 82, 83, 84, 85, 86, 87, 88, 90, 90, 91, 92, 93, 94, 95, 96, 97, 97, 98, 99, 100, 101, 103, 104, 105, 106, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 122, 123, 123, 124, 125, 127, 128, 129, 130, 131, 132, 132, 134, 135, 136, 137, 138, 139, 141, 141, 142, 144, 145, 146, 147, 148, 149, 150, 151, 152, 154, 155, 156, 157, 158, 160, 160, 161, 162, 163, 165, 166, 167, 168, 169, 170, 171, 173, 174, 175, 176, 177, 178, 179, 180, 182, 183, 184, 185, 187, 188, 189, 189, 191, 192, 193, 194, 196, 197, 198, 198, 200, 201, 202, 203, 205, 206, 207, 208, 209, 210, 211, 212, 213, 215, 216, 217, 218, 219, 220, 221, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 255};

const unsigned char sepiaBlueLut[256] = {5, 5, 5, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 9, 10, 10, 11, 11, 11, 11, 12, 12, 13, 13, 14, 14, 14, 14, 15, 15, 16, 16, 17, 17, 17, 18, 18, 19, 20, 20, 21, 21, 21, 22, 22, 23, 23, 24, 25, 25, 26, 27, 28, 28, 29, 29, 30, 31, 31, 31, 32, 33, 33, 34, 35, 36, 37, 38, 38, 39, 39, 40, 41, 42, 43, 43, 44, 45, 46, 47, 47, 48, 49, 50, 51, 52, 53, 53, 54, 55, 56, 57, 58, 59, 60, 60, 61, 62, 63, 65, 66, 67, 67, 68, 69, 70, 72, 73, 74, 75, 75, 76, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 90, 91, 92, 93, 93, 95, 97, 98, 99, 100, 101, 102, 104, 104, 106, 107, 108, 109, 111, 112, 114, 115, 115, 117, 118, 120, 121, 122, 123, 124, 125, 127, 128, 129, 131, 132, 133, 135, 136, 137, 138, 139, 141, 142, 144, 145, 147, 147, 149, 150, 151, 153, 154, 156, 157, 159, 159, 161, 162, 164, 165, 167, 168, 169, 170, 172, 173, 174, 176, 177, 178, 180, 181, 182, 184, 185, 186, 188, 189, 191, 192, 193, 194, 196, 197, 198, 200, 201, 203, 204, 205, 206, 207, 209, 210, 211, 213, 214, 215, 216, 218, 219, 220, 221, 223, 224, 225, 226, 227, 229, 230, 231, 232, 234, 235, 236, 237, 238, 239, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 255};
void applySepia(Bitmap* bitmap){
/*bitmap initializations*/
for (i = length; i--; ) {
  register float r = (float) red[i] / 255;
  register float g = (float) green[i] / 255;
  register float b = (float) blue[i] / 255;
  register float luminosity =  (0.21f * r + 0.72f * g + 0.07 * b) * 255;
      red[i] = truncate((int)( sepiaRedLut[(int)luminosity]));
      green[i] = truncate((int)(sepiaGreenLut[(int)luminosity]));
      blue[i] = truncate((int)(sepiaBlueLut[(int)luminosity]));
  }
}

     

Cyano Filter

As the name suggests this filter adds a cyan tone to the image. For implementing this cyano filter, we first found the black and white value of the pixel and then the ceilingComponent value of the pixels of three color channels. Then the ceilingComponent Values and the gray values are overlayed to give the resultant image. Finding the ceilComponent Values and the filter implementation is shown below.

#define componentCeiling(x) ((x > 255) ? 255 : x)

static unsigned char overlayPixelComponents(unsigned int overlayComponent, unsigned char underlayComponent, float alpha) {
  float underlay = underlayComponent * alpha;
  return (unsigned char)((underlay / 255) * (underlay + ((2.0f * overlayComponent) / 255) * (255 - underlay)));

}

void applyCyano(Bitmap* bitmapl) {
  //Cache to local variables
//Bitamp initialization
  register unsigned int i;
  register unsigned char grey, r, g, b;
  for (i = length; i--;) {
     grey = ((red[i] * 0.222f) + (green[i] * 0.222f) + (blue[i] * 0.222f));
     r = componentCeiling(61.0f + grey);
     g = componentCeiling(87.0f + grey);
     b = componentCeiling(136.0f + grey);
     grey = (red[i] + green[i] + blue[i]) / 3;
     red[i] = truncate((int)(overlayPixelComponents(grey, r, 0.9f)));
     green[i] = truncate((int)(overlayPixelComponents(grey, g, 0.9f)));
     blue[i] = truncate((int)(overlayPixelComponents(grey, b, 0.9f)));
  }
}

      

Grain Filter

It is clear from the name that this filter adds grain to the image giving an artistic effect. It can be implemented in a very simple manner by assigning gray values to random pixels of an image. The condition inside the main for loop of the below implementation controls the proportion of added grain with respect to the whole image. For generating a random value, the timer has to be initialized first. The whole implementation of the function is shown below.

void applyGrain(Bitmap* bitmap) {
/*initializations*/
   time_t t;
   srand((unsigned) time(&t));
  for (i = length; i--;) {
       int rval = rand()%255;
       if (rand()%100 < 15)){
           int grey = (red[i] + green[i] + blue[i]) / 3;
           red[i] = truncate(rval);
           green[i] = truncate(rval);
           blue[i] = truncate(rval);
       }
  }
}

      

Threshold Filter

Thresholding an image gives a binary image i.e the pixels of the image will have only two values. One for a value less than the threshold and other for values greater than the threshold. The threshold value is adjusted by seek bar in Phimpme. An image looks very artistic for a particular value on the seek bar. Its implementation is shown below.

void applyThreshold(Bitmap* bitmap, int val) {
/*bitmap initializations*/
  unsigned char grey, color;
  int thres = 220 - (int)((val/100.0) * 190);
  for (i = length; i--;) {
       grey = (red[i] + green[i] + blue[i]) / 3;
       if (grey < thres) color = 0;
       else color = 255;
       red[i] = truncate((int)(color));
       green[i] = truncate((int)(color));
       blue[i] = truncate((int)(color));
  }
}

     

Resources:

Enhancing Images using Native functions in Phimpme Android

Enhancing the image can be performed by adjusting the brightness, contrast, saturation etc. of that image. In the Phimpme Android Image Application, we implemented many enhancement operations. All these image enhancement operations are performed by the native image processing functions.

An image is made up of color channels. A gray-scale image has a single channel, colored opaque image has three channels and colored image with transparency has four channels. Each color channel of an image represents a two dimensional matrix of integer values. An image of resolution 1920×1080 has 1920 elements in its row and 1080 such rows. The integer values present in the matrices will be ranging from 0 to 255. For a grayscale image there will be a single channel. So, for that image, 0 corresponds to black color and 255 corresponds to white color. By changing the value present in the matrices, the image can be modified.

The implementation of the enhancement functions in Phimpme Application are given below.

Brightness

Brightness adjustment is the easiest of the image processing functions in Phimpme. Brightness can be adjusted by increasing or decreasing the values of all elements in all color channel matrices. Its implementation is given below.

void tuneBrightness(Bitmap* bitmap, int val) {
  register unsigned int i;
  unsigned int length = (*bitmap).width * (*bitmap).height;
  unsigned char* red = (*bitmap).red;
  unsigned char* green = (*bitmap).green;
  unsigned char* blue = (*bitmap).blue;
  signed char bright = (signed char)(((float)(val-50)/100)*127);
  for (i = length; i--; ) {
       red[i] =  truncate(red[i]+bright);
       green[i] = truncate(green[i]+bright);
       blue[i] = truncate(blue[i]+bright);
  }
}

  

low brightness, normal, high brightness(in the order) images are shown above

For the above function, the argument val is given by the seekbar implemented in java activity. Its value ranges from 0 – 100, so a new variable is introduced to change the range of the input argument in the function. You can see that in the for loop there is function named truncate. As the name suggests it truncates the input argument’s value to accepted range. It is added to the top of the c file as below

#define truncate(x) ((x > 255) ? 255 : (x < 0) ? 0 : x)

Contrast

Contrast of an image is adjusted in Phimpme application by increasing the brightness of the brighter pixel and decreasing value of the darker pixel. This is achieved by using the following formula for the adjustment contrast in editor of phimpme application.

pixel[i] = {(259 x (C + 255))/(255 x (259 - C))} x (pixel[i] - 128)

In the above formula, C is the contrast value and pixel[i] is the value of the element in the image matrix that we are modifying for changing the contrast.

 

low contrast, normal, high contrast(in the order) images are shown above

So, after this formula for modifying every pixel value, the function looks like below

void tuneContrast(Bitmap* bitmap, int val) {
  register unsigned int i;
  unsigned int length = (*bitmap).width * (*bitmap).height;
  unsigned char* red = (*bitmap).red;
  unsigned char* green = (*bitmap).green;
  unsigned char* blue = (*bitmap).blue;
  int contrast = (int)(((float)(val-50)/100)*255);
  float factor = (float)(259*(contrast + 255))/(255*(259-contrast));

  for (i = length; i--; ) {
       red[i] = truncate((int)(factor*(red[i]-128))+128);
       green[i] = truncate((int)(factor*(green[i]-128))+128);
       blue[i] = truncate((int)(factor*(blue[i]-128))+128);
  }
}

Hue

The below image explains hue shift by showing what happens when shift in hue takes place over time. The image with hue 0 looks identical with image with hue 360. Hue shift is cyclic. The definition and formulae corresponding hue is found in wikipedia page here. Using that formulae and converting them back, i.e we got rgb values from hue in Phimpme application. Its implementation is shown below.

[img source:wikipedia]

void tuneHue(Bitmap* bitmap, int val) {
  register unsigned int i;
  unsigned int length = (*bitmap).width * (*bitmap).height;
  unsigned char* red = (*bitmap).red;
  unsigned char* green = (*bitmap).green;
  unsigned char* blue = (*bitmap).blue;
  double H = 3.6*val;
  double h_cos = cos(H*PI/180);
  double h_sin = sin(H*PI/180);
  double r,g,b;

  for (i = length; i--; ) {
       r = (double)red[i]/255;
       g = (double)green[i]/255;
       b = (double)blue[i]/255;
       red[i] = truncate((int)(255*((.299+.701*h_cos+.168*h_sin)*r +  (.587-.587*h_cos+.330*h_sin)*g + (.114-.114*h_cos-.497*h_sin)*b)));

       green[i] = truncate((int)(255*((.299-.299*h_cos-.328*h_sin)*r + (.587+.413*h_cos+.035*h_sin)*g + (.114-.114*h_cos+.292*h_sin)*b)));

       blue[i] = truncate((int)(255*((.299-.3*h_cos+1.25*h_sin)*r +  (.587-.588*h_cos-1.05*h_sin)*g + (.114+.886*h_cos-.203*h_sin)*b)));
  }
}

Saturation

Saturation is the colorfulness of the image. You can see the below null saturation, unmodified and high saturated images in the respective order. The technical definition and formulae for getting the saturation value from the rgb value is given in the wikipedia page here. In Phimpme application we used those formulae to get the rgb values from the saturation value.

Its implementation is given below.

  

low saturation, normal, high saturation(in the order) images are shown above

void tuneSaturation(Bitmap* bitmap, int val) {
  register unsigned int i;
  unsigned int length = (*bitmap).width * (*bitmap).height;
  unsigned char* red = (*bitmap).red;
  unsigned char* green = (*bitmap).green
  unsigned char* blue = (*bitmap).blue;
  double sat = 2*((double)val/100);
  double temp;
  double r_val = 0.299, g_val = 0.587, b_val = 0.114;
  double r,g,b;
  for (i = length; i--; ) {
      r = (double)red[i]/255;
      g = (double)green[i]/255;
      b = (double)blue[i]/255;
      temp = sqrt( r * r * r_val +
                     g * g * g_val +
                       b * b * b_val );
      red[i] = truncate((int)(255*(temp + (r - temp) * sat)));
      green[i] = truncate((int)(255*(temp + (g - temp) * sat)));
      blue[i] = truncate((int)(255*(temp + (b - temp) * sat)));
  }
}

Temperature

If the color temperature of the image is high, i.e the image with the warm temperature will be having more reds and less blues. For a cool temperature image reds are less and blues are more. So In Phimpme Application, we implemented this simply by adjusting the brightness of the red channel matrix and blue channel matrix as we did in brightness adjustment. We didn’t modify the green channel here.

  

low temperature, normal, high temperature(in the order) images are shown above

void tuneTemperature(Bitmap* bitmap, int val) {
  register unsigned int i;
  unsigned int length = (*bitmap).width * (*bitmap).height;
  unsigned char* red = (*bitmap).red;
  unsigned char* green = (*bitmap).green;
  unsigned char* blue = (*bitmap).blue;
  int temperature = (int)1.5*(val-50);
  for (i = length; i--; ) {
       red[i] = truncate(red[i] + temperature);
       blue[i] = truncate(blue[i] - temperature);
  }
}

Tint

In Phimpme application, we adjusted the tint of an image in the same way of adjusting the temperature. But in this instead of modifying the red and blue channels, we modified the green channel of the image. An image with more tint will have a tone of magenta color and if it is decreased the image will have a greenish tone. The below shown code shows how we implemented this function in image editor of Phimpme application.

  

low tint, normal, high tint(in the order) images are shown above

void tuneTint(Bitmap* bitmap, int val) {
  register unsigned int i;
  unsigned int length = (*bitmap).width * (*bitmap).height;
  unsigned char* red = (*bitmap).red;
  unsigned char* green = (*bitmap).green;
  unsigned char* blue = (*bitmap).blue;
  int tint = (int)(1.5*(val-50));

  for (i = length; i--; ) {
       green[i] = truncate(green[i] - tint);
  }
}

Vignette

Vignetting is the reduciton in the brightness of the image towards the edges than the center. It is applied to draw the attention of the viewer to the center of the image.

 

normal and vignetted images are shown above

For implementing vignette in Phimpme application, we reduced the brightness of the pixel corresponding to a radial gradient value which is generated based on the pixel’s distance from the corner and center. It’s function in Phimpme as is shown below.

double dist(int ax, int ay,int bx, int by){
   return sqrt(pow((double) (ax - bx), 2) + pow((double) (ay - by), 2));
}

void tuneVignette(Bitmap* bitmap, int val) {
  register unsigned int i,x,y;
  unsigned int width = (*bitmap).width, height = (*bitmap).height;
  unsigned int length = width * height;
  unsigned char* red = (*bitmap).red;
  unsigned char* green = (*bitmap).green;
  unsigned char* blue = (*bitmap).blue;
  double radius = 1.5-((double)val/100), power = 0.8;
  double cx = (double)width/2, cy = (double)height/2;
  double maxDis = radius * dist(0,0,cx,cy);
  double temp,temp_s;
   for (y = 0; y < height; y++){
       for (x = 0; x < width; x++ ) {
           temp = dist(cx, cy, x, y) / maxDis;
           temp = temp * power;
           temp_s = pow(cos(temp), 4);
           red[x+y*width] = truncate((int)(red[x+y*width]*temp_s));
           green[x+y*width] = truncate((int)(green[x+y*width]*temp_s));
           blue[x+y*width] = truncate((int)(blue[x+y*width]*temp_s));
       }
   }
}

All these above mentioned functions are called from main.c file by creating JNI functions corresponding to each. These JNI functions are further defined with proper name in Java and arguments are passed to it. If you are not clear with JNI, refer my previous posts.

Resources