Android – Correcting jpeg image orientation in View Canvas with ExifInterface orientation tag

June 26, 2013
By

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”

Create new Android application project

Figure 1 : Create new Android application project


2. Configure the project

Configure the project

Figure 2 : Configure the project


3. Design application launcher icon

Design application launcher icon

Figure 3 : Design application launcher icon


4. Create a blank activity

Create a blank activity

Figure 4 : Create a blank activity


5. Enter MainActivity details

Enter MainActivity details

Figure 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

Pick up an image via an external application

Figure 6 : Pick up an image via an external application

Showing an image with Exif orientation applied

Figure 7 : Showing an image with Exif orientation applied


11. Download Source Code


How to hire me?

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


Android Knowledge Quiz

Ready to test your knowledge in Android? Take this quiz :



Tags: , , , , , , ,

3 Responses to Android – Correcting jpeg image orientation in View Canvas with ExifInterface orientation tag

  1. tawisak on June 29, 2013 at 6:05 pm

    may i get author e-mail

    I have some problem about display kml on android for ask you

    • george on June 30, 2013 at 11:22 am

      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.

      • tawisak on June 30, 2013 at 4:18 pm

        Thank you in advance…

Leave a Reply

Your email address will not be published. Required fields are marked *

Be friend at g+

Subscribe for Lastest Updates

FBFPowered by ®Google Feedburner