Bug 26143 - Performing Android.Graphics operation is about 10x slower in Xamarin.Android
Summary: Performing Android.Graphics operation is about 10x slower in Xamarin.Android
Status: RESOLVED ANSWERED
Alias: None
Product: Android
Classification: Xamarin
Component: BCL Class Libraries ()
Version: 4.1.x
Hardware: Macintosh Mac OS
: --- normal
Target Milestone: ---
Assignee: Jonathan Pryor
URL:
Depends on:
Blocks:
 
Reported: 2015-01-18 07:44 UTC by timm
Modified: 2015-03-02 13:52 UTC (History)
1 user (show)

Tags:
Is this bug a regression?: ---
Last known good build:

Notice (2018-05-24): bugzilla.xamarin.com is now in read-only mode.

Please join us on Visual Studio Developer Community and in the Xamarin and Mono organizations on GitHub to continue tracking issues. Bugzilla will remain available for reference in read-only mode. We will continue to work on open Bugzilla bugs, copy them to the new locations as needed for follow-up, and add the new items under Related Links.

Our sincere thanks to everyone who has contributed on this bug tracker over the years. Thanks also for your understanding as we make these adjustments and improvements for the future.


Please create a new report on Developer Community or GitHub with your current version information, steps to reproduce, and relevant error messages or log files if you are hitting an issue that looks similar to this resolved bug and you do not yet see a matching new report.

Related Links:
Status:
RESOLVED ANSWERED

Description timm 2015-01-18 07:44:32 UTC
Giving an image a sepia effect with Android.Graphics is about 10x slower in Xamarin.Android than native Android.

Environment:
Xamarin Studio 5.7, lates Xamarin.Android
Android Studio 1.0.2
Device: Huawai Ascend Y300 with Android 4.1.1

Image is 1200x900

Code: 
// ---------- Xamarin code
// about 45000 ms with 1200x900 px bitmap
private Bitmap processSepiaImage (Bitmap src, int depth, double red, double green, double blue) {
	// image size
	int width = src.Width;
	int height = src.Height;
	Console.WriteLine("sepia: w:" + width + " h:" + height);
	// create output bitmap
	Bitmap bmOut = Bitmap.CreateBitmap(width, height, src.GetConfig());
	// constant grayscale
	const double GS_RED = 0.3;
	const double GS_GREEN = 0.59;
	const double GS_BLUE = 0.11;
	// color information
	int A, R, G, B;
	int pixel;

	// scan through all pixels
	for (int x = 0; x < width; ++x) {
		for (int y = 0; y < height; ++y) {
			// get pixel color 
			pixel = src.GetPixel (x, y);
			// get color components			
			A = global::Android.Graphics.Color.GetAlphaComponent(pixel);
			R = global::Android.Graphics.Color.GetRedComponent(pixel);
			G = global::Android.Graphics.Color.GetGreenComponent(pixel);
			B = global::Android.Graphics.Color.GetBlueComponent (pixel);
			// apply grayscale sample
			B = G = R = (int)(GS_RED * R + GS_GREEN * G + GS_BLUE * B);
			// apply intensity level for sepid-toning on each channel
			R += (int)(depth * red);
			if(R > 255) { R = 255; }
			G += (int)(depth * green);
			if(G > 255) { G = 255; }
			B += (int)(depth * blue);
			if(B > 255) { B = 255; }
			// set new pixel color to output image
			bmOut.SetPixel (x, y, global::Android.Graphics.Color.Argb(A, R, G, B));
		}
	}
	// return final image
	return bmOut;
}

// ---------- Android code
// about 5300 ms with 1200x900 px bitmap
    private static Bitmap createSepiaToningEffect(Bitmap src, int depth, double red, double green, double blue) {
        // image size
        int width = src.getWidth();
        int height = src.getHeight();
        Log.i("imgsize", "sepia w:" + width + " h:" + height);
        // create output bitmap
        Bitmap bmOut = Bitmap.createBitmap(width, height, src.getConfig());
        // constant grayscale
        final double GS_RED = 0.3;
        final double GS_GREEN = 0.59;
        final double GS_BLUE = 0.11;
        // color information
        int A, R, G, B;
        int pixel;

        // scan through all pixels
        for(int x = 0; x < width; ++x) {
            for(int y = 0; y < height; ++y) {
                // get pixel color
                pixel = src.getPixel(x, y);
                // get color on each channel
                A = Color.alpha(pixel);
                R = Color.red(pixel);
                G = Color.green(pixel);
                B = Color.blue(pixel);
                // apply grayscale sample
                B = G = R = (int)(GS_RED * R + GS_GREEN * G + GS_BLUE * B);
                // apply intensity level for sepid-toning on each channel
                R += (depth * red);
                if(R > 255) { R = 255; }
                G += (depth * green);
                if(G > 255) { G = 255; }
                B += (depth * blue);
                if(B > 255) { B = 255; }
                // set new pixel color to output image
                bmOut.setPixel(x, y, Color.argb(A, R, G, B));
            }
        }
        // return final image
        return bmOut;
    }
Comment 1 Jonathan Pryor 2015-03-02 13:52:00 UTC
This unfortunately isn't surprising; there is a fair bit of overhead in the JNI boundary:

http://lists.ximian.com/pipermail/monodroid/2012-July/011358.html

> Mono's P/Invoke mechanism isn't that bad; if you have a C function which does nothing,
> and a C# method which does nothing, and call each in a loop, P/Invoke method invoke
> takes ~10x the time of a non-inlined C# method invoke. Probably not noticeable, since
> normally your native method would actually be doing something, not nothing...
> 
> Dalvik's JNI overhead, on the other hand... Worst case, using JNI to invoke an empty
> Java method, is 160x-280x the time of an equivalent non-inlined C# method.

To speed things up, you need to reduce JNI transitions, either by doing more in C# or by doing more in Java (and using JNIEnv or a Binding library to invoke the Java helper).