We may have noticed that images captured in portrait mode, looks rotated 90 degrees on drawing the image in a view canvas. Also images taken from front camera looks upside down. This issue can be resolved with exif meta data embedded with jpeg images.
In this article, we will develop an Android application which reads the exif data associated with the jpeg file and and uses that information to display the image correctly.
This application is developed in Eclipse 4.2.0 with ADT Plugin (22.0.1) and Android SDK ( 22.0.1 ) .
1. Create new Android application project namely “GraphicsExifOrientation”
2. Configure the project
3. Design application launcher icon
4. Create a blank activity
5. Enter MainActivity details
6. Update the file res/values/strings.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">Exif Orientation</string> <string name="action_settings">Settings</string> <string name="hello_world">Hello world!</string> <string name="pick">Pick an Image</string> <string name="raw_image">Raw Image</string> <string name="exif_image">Image with Exif Orientation tag applied</string> </resources>
7. Create a java class file src/in/wptrafficanalyzer/graphicsexiforientation/PaintView.java
package in.wptrafficanalyzer.graphicsexiforientation; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Matrix.ScaleToFit; import android.graphics.Paint; import android.graphics.RectF; import android.util.AttributeSet; import android.view.View; public class PaintView extends View{ Paint mPaint; Bitmap mBitmap; Matrix mMatrix; RectF mSrcRectF; RectF mDestRectF; boolean mPause; public PaintView(Context context,AttributeSet attributeSet){ super(context,attributeSet); mPaint = new Paint(); mMatrix = new Matrix(); mSrcRectF = new RectF(); mDestRectF = new RectF(); mPause = false; } public void addBitmap(Bitmap bitmap){ mBitmap = bitmap; } public Bitmap getBitmap(){ return mBitmap; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if(!mPause){ if(mBitmap!=null){ // Setting size of Source Rect mSrcRectF.set(0, 0,mBitmap.getWidth(),mBitmap.getHeight()); // Setting size of Destination Rect mDestRectF.set(0, 0, getWidth(), getHeight()); // Scaling the bitmap to fit the PaintView mMatrix.setRectToRect( mSrcRectF , mDestRectF, ScaleToFit.CENTER); // Drawing the bitmap in the canvas canvas.drawBitmap(mBitmap, mMatrix, mPaint); } // Redraw the canvas invalidate(); } } // Pause or resume onDraw method public void pause(boolean pause){ mPause = pause; } }
8. Update the layout file res/layout/activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" > <TextView android:id="@+id/tv_raw" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/raw_image" android:layout_alignParentTop="true" /> <Button android:id="@+id/btn_pick" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="@string/pick" /> <TextView android:id="@+id/tv_exif" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/exif_image" android:layout_below="@id/btn_pick" /> <in.wptrafficanalyzer.graphicsexiforientation.PaintView android:id="@+id/paint_view" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_above="@id/btn_pick" android:layout_below="@id/tv_raw" /> <in.wptrafficanalyzer.graphicsexiforientation.PaintView android:id="@+id/paint_view_rotated" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_below="@id/tv_exif" /> </RelativeLayout>
9. Update the class src/in/wptrafficanalyzer/graphicspickimageviewcanvas/MainActivity.java
package in.wptrafficanalyzer.graphicsexiforientation; import java.io.IOException; import java.io.InputStream; import android.app.Activity; import android.content.Intent; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.media.ExifInterface; import android.net.Uri; import android.opengl.Visibility; import android.os.Bundle; import android.provider.MediaStore; import android.view.Menu; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends Activity { PaintView mPaintView; Button mBtnPick; TextView mTvRaw; TextView mTvExif; int mWidth; int mHeight; PaintView mRotatedPaintView; int mRotatedWidth; int mRotatedHeight; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mWidth = mHeight = 0; mRotatedWidth = mRotatedHeight = 0; // Getting reference to PaintView mPaintView = (PaintView) findViewById(R.id.paint_view); // Getting reference to PaintView mRotatedPaintView = (PaintView) findViewById(R.id.paint_view_rotated); // Getting reference to Button "Pick an Image" mBtnPick = (Button) findViewById(R.id.btn_pick); // Getting reference to TextView tv_raw mTvRaw = (TextView) findViewById(R.id.tv_raw); // Getting reference to TextView tv_exif mTvExif = (TextView) findViewById(R.id.tv_exif); // Hiding TextView tv_raw mTvRaw.setVisibility(View.INVISIBLE); // Hiding TextView tv_exif mTvExif.setVisibility(View.INVISIBLE); // Setting OnClickListener for the button mBtnPick.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent i = new Intent(); i.setType("image/*"); i.setAction(Intent.ACTION_GET_CONTENT); Intent customChooserIntent = Intent.createChooser(i, "Pick an image via"); startActivityForResult(customChooserIntent, 10); } }); if(savedInstanceState!=null){ mWidth = savedInstanceState.getInt("width"); mHeight = savedInstanceState.getInt("height"); mRotatedWidth = savedInstanceState.getInt("rotated_width"); mRotatedHeight = savedInstanceState.getInt("rotated_height"); Bitmap bitmap = savedInstanceState.getParcelable("bitmap"); if(bitmap!=null){ mPaintView.addBitmap(bitmap); mTvRaw.setVisibility(View.VISIBLE); } Bitmap rotatedBitmap = savedInstanceState.getParcelable("rotated_bitmap"); if(rotatedBitmap!=null){ mRotatedPaintView.addBitmap(rotatedBitmap); mTvExif.setVisibility(View.VISIBLE); } } } // Courtesy : developer.android.com/training/displaying-bitmaps/load-bitmap.html public static int calculateInSampleSize( BitmapFactory.Options options, int reqWidth, int reqHeight) { // Raw height and width of image final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { // Calculate ratios of height and width to requested height and width final int heightRatio = Math.round((float) height / (float) reqHeight); final int widthRatio = Math.round((float) width / (float) reqWidth); // Choose the smallest ratio as inSampleSize value, this will guarantee // a final image with both dimensions larger than or equal to the // requested height and width. inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio; } return inSampleSize; } private String getFilePath(Uri data){ String path = ""; // For non-gallery application path = data.getPath(); // For gallery application String[] filePathColumn = { MediaStore.Images.Media.DATA }; Cursor cursor = getContentResolver().query(data,filePathColumn, null, null, null); if(cursor!=null){ cursor.moveToFirst(); int columnIndex = cursor.getColumnIndex(filePathColumn[0]); path = cursor.getString(columnIndex); cursor.close(); } return path; } private Bitmap getRotatedBitmap(String path, Bitmap bitmap){ Bitmap rotatedBitmap = null; Matrix m = new Matrix(); ExifInterface exif = null; int orientation = 1; try { if(path!=null){ // Getting Exif information of the file exif = new ExifInterface(path); } if(exif!=null){ orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0); switch(orientation){ case ExifInterface.ORIENTATION_ROTATE_270: m.preRotate(270); break; case ExifInterface.ORIENTATION_ROTATE_90: m.preRotate(90); break; case ExifInterface.ORIENTATION_ROTATE_180: m.preRotate(180); break; } // Rotates the image according to the orientation rotatedBitmap = Bitmap.createBitmap(bitmap,0,0,bitmap.getWidth(),bitmap.getHeight(),m,true); } } catch (IOException e) { e.printStackTrace(); } return rotatedBitmap; } private Bitmap getBitmapFromUri(Uri data){ Bitmap bitmap = null; // Starting fetch image from file InputStream is=null; try { is = getContentResolver().openInputStream(data); // First decode with inJustDecodeBounds=true to check dimensions final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; // BitmapFactory.decodeFile(path, options); BitmapFactory.decodeStream(is, null, options); // Calculate inSampleSize options.inSampleSize = calculateInSampleSize(options, mWidth, mHeight); // Decode bitmap with inSampleSize set options.inJustDecodeBounds = false; is = getContentResolver().openInputStream(data); bitmap = BitmapFactory.decodeStream(is,null,options); if(bitmap==null){ Toast.makeText(getBaseContext(), "Image is not Loaded",Toast.LENGTH_SHORT).show(); return null; } is.close(); }catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }catch(NullPointerException e){ e.printStackTrace(); } return bitmap; } @Override protected void onActivityResult(int requestCode, int resultCode, Intent intent) { // TODO Auto-generated method stub super.onActivityResult(requestCode, resultCode, intent); if (requestCode == 10 && resultCode == RESULT_OK && null != intent) { Uri data = intent.getData(); String path = getFilePath(data); Bitmap bitmap = getBitmapFromUri(data); Bitmap rotatedBitmap = getRotatedBitmap(path,bitmap); // Setting Raw Image if(bitmap!=null){ mPaintView.addBitmap(bitmap); mTvRaw.setVisibility(View.VISIBLE); } // Setting Exif Image if(rotatedBitmap!=null){ mRotatedPaintView.addBitmap(rotatedBitmap); mTvExif.setVisibility(View.VISIBLE); } } } @Override public void onWindowFocusChanged(boolean hasFocus) { // TODO Auto-generated method stub super.onWindowFocusChanged(hasFocus); // Getting width of the RawPaintView mWidth = mPaintView.getWidth(); // Getting height of the RawPaintView mHeight = mPaintView.getHeight(); // Getting width of the RotatedPaintView mRotatedWidth = mRotatedPaintView.getWidth(); // Getting height of the RotatedPaintView mRotatedHeight = mRotatedPaintView.getHeight(); } @Override protected void onSaveInstanceState(Bundle outState) { outState.putInt("width", mWidth); outState.putInt("height", mHeight); outState.putInt("rotated_width", mRotatedWidth); outState.putInt("rotated_height", mRotatedHeight); if(mPaintView.getBitmap()!=null){ outState.putParcelable("bitmap", mPaintView.getBitmap()); } if(mRotatedPaintView.getBitmap()!=null){ outState.putParcelable("rotated_bitmap", mRotatedPaintView.getBitmap()); } super.onSaveInstanceState(outState); } @Override protected void onResume() { mPaintView.pause(false); mRotatedPaintView.pause(false); // Resume repainting mPaintView.invalidate(); mRotatedPaintView.invalidate(); super.onResume(); } @Override protected void onPause() { // Pause repainting mPaintView.pause(true); mRotatedPaintView.pause(true); super.onPause(); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } }
10. Screenshots of the application
11. Download Source Code


I am George Mathew, working as software architect and Android app developer at wptrafficanalyzer.in
You can hire me on hourly basis or on project basis for Android applications development.
For hiring me, please mail your requirements to info@wptrafficanalyzer.in.
My other blogs
store4js.blogspot.com
may i get author e-mail
I have some problem about display kml on android for ask you
Hi,
I suggest you to put the issues related with the application in the comments itself. So that every body can read and respond to the queries.
If necessary, you can send mails to georgemathewk@yahoo.com.
Thank you in advance…