이미지뷰에 맞게 사진 로딩하기(회전 보정 추가)

View Comments

안드로이드 프로그래밍 중 사진을 출력할 필요가 있었다. 처음에는 단순 무식하게 이미지뷰에 그냥 사진을 로딩해서 붙여넣었다.
처음에 디자이어 HD로 테스트할 때는 이상없이 잘 출력되었다.
그래서 이상없이 프로그래밍이 된 걸으로 알고 구글 플레이에 퍼블리싱을 했다.

그런데 막내의 넥서스S에서 시험하려고 이미지를 등록하고 화면을 몇 번 전환했더니 바로 프로그램이 죽어버렸다.
왜 그런지 이유를 알 수 없었다.

다음으로 갤럭시 S2에서 시험해봤다. 사진을 등록하고 화면에 표시된 것을 보는 순간...사진이 180도 회전되서 출력이 되는 것이다.

이런...이런...퍼블리싱하기 전에 더 테스트를 해봤어야 되는데...했지만 이미 늦은...

구글링으로 이유를 찾아봤다.

이유는 사진이 너무 커서 메모리를 다 먹어버리니까 강제 종료가 된 것이었다.

사진 크기를 줄이는 방법은 쉽게 찾을 수 있었다.

바로 처리하고, 더불어서 모서리까지 라운드로 처리해서 출력하도록 수정했다(그 소스는 위의 참조 링크 두 번째에 있다).

일단 급한 버그를 수정해서 업그레이드하고나니 사진이 90도, 180도, 270도 회전되서 출력하는 문제가 남았다.

그 것도 이리저리 찾아보니 사진의 EXIF 정보를 읽어서 사진 방향을 보정해야 된단다.

다행히도 위의 참조 링크 첫 번째에 있는 방법으로 처리하니 잘 출력이 된다.

이미지 처리와 관련된 부분만 뽑아서 ImageUtil이란 클래스로 만들고, 메소드는 다 static으로 해서 바로 뽑아쓸 수 있게 만들었다.

아래에 예제 프로젝트 전문을 게재한다. 예제 프로젝트는 화면 가운데에 ImageView를 하나 놓고, ImageView 부분을 누르면 갤러리(또는 사진 보는 프로그램)에서 사진을 불러와서 적절하게 가공해서 ImageView에 표시한다.

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/txtSizeInfo"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello"
        />

    <ImageView
        android:id="@+id/imgView"
        android:layout_width="240dip"
        android:layout_height="240dip"
        android:layout_gravity="center_horizontal"
        android:padding="2dip"
        android:background="#fccc"
        android:contentDescription="@string/hello"
        />
</LinearLayout>

MyImageViewActivity.java

public class MyImageViewActivity extends Activity {
    public static final int REQUEST_CODE_PICKALBUM = 101; 

    private String mImgPath;

    ImageView iv;
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        iv = (ImageView)findViewById(R.id.imgView);
        iv.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                Intent intent = new Intent(Intent.ACTION_PICK,
                        MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
                intent.setType(MediaStore.Images.Media.CONTENT_TYPE);
                startActivityForResult(Intent.createChooser(intent, "앨범에서 불러오기"),
                        REQUEST_CODE_PICKALBUM);
            }
        });
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data){
        if (requestCode == REQUEST_CODE_PICKALBUM) {
            if (resultCode == RESULT_OK) {
                // 앨범인 경우
                Uri mImageUri = data.getData();

                // 이미지 Path 취득
                mImgPath = getPath(mImageUri);
                updateImageView();
            }
        }
    }

    private String getPath(Uri uri) {
        String[] projection = { MediaStore.Images.Media.DATA };

        Cursor cursor = managedQuery(uri, projection, null, null, null);
        int column_index = cursor
        .getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
        cursor.moveToFirst();

        return cursor.getString(column_index);
    }

    private void updateImageView() {
        int degree = ImageUtil.GetExifOrientation(mImgPath);
        Bitmap resizeBitmap = ImageUtil.loadBackgroundBitmap(
                MyImageViewActivity.this, mImgPath);
        Bitmap rotateBitmap = ImageUtil.GetRotatedBitmap(resizeBitmap, degree);
        Bitmap roundBitmap = ImageUtil.getRoundedCornerBitmap(rotateBitmap);
        iv.setImageBitmap(roundBitmap);
        resizeBitmap.recycle();
    }
}

ImageUtil.java

/**
  * Image 처리에 관련된 기능들을 모아놓은 유틸리티 클래스.
  * 
  * @author : nexturbo
  * @create : 2012.4.24
  */
public class ImageUtil {
    /**
      * 비트맵의 모서리를 라운드 처리 한 후 Bitmap을 리턴
      *
      * @param bitmap
      *       bitmap handle
      * @return Bitmap
      */
    public static Bitmap getRoundedCornerBitmap(Bitmap bitmap) {
        Bitmap output = Bitmap.createBitmap(bitmap.getWidth(),
            bitmap.getHeight(), Config.ARGB_8888);
        Canvas canvas = new Canvas(output);
        final int color = 0xff424242;
        final Paint paint = new Paint();
        final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
        final RectF rectF = new RectF(rect);
        final float roundPx = 10;

        paint.setAntiAlias(true);
        canvas.drawARGB(0, 0, 0, 0);
        paint.setColor(color);
        canvas.drawRoundRect(rectF, roundPx, roundPx, paint);

        paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
        canvas.drawBitmap(bitmap, rect, rect, paint);

        bitmap.recycle();
        bitmap = output;

        return bitmap;
    }

    /**
      * 지정한 패스의 파일을 화면 크기에 맞게 읽어서 Bitmap을 리턴
      *
      * @param context
      *       application context
      * @param imgFilePath
      *       bitmap file path
      * @return Bitmap
      * @throws IOException
      */
    public static Bitmap loadBackgroundBitmap(Context context, String imgFilePath) {
        File file = new File(imgFilePath);
        if (file.exists() == false) {
            return null;
        }

        // 폰의 화면 사이즈를 구한다.
        Display display = ((WindowManager)context.getSystemService(
                Context.WINDOW_SERVICE)).getDefaultDisplay();
        int displayWidth = display.getWidth();
        int displayHeight = display.getHeight();

        // 읽어들일 이미지의 사이즈를 구한다.
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inPreferredConfig = Config.RGB_565;
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(imgFilePath, options);

        // 화면 사이즈에 가장 근접하는 이미지의 스케일 팩터를 구한다.
        // 스케일 팩터는 이미지 손실을 최소화하기 위해 짝수로 한다.
        float widthScale = options.outWidth / displayWidth;
        float heightScale = options.outHeight / displayHeight;
        float scale = widthScale > heightScale ? widthScale : heightScale;
                
        if (scale >= 8)
            options.inSampleSize = 8;
        else if (scale >= 6)
            options.inSampleSize = 6;
        else if (scale >= 4)
            options.inSampleSize = 4;
        else if (scale >= 2)
            options.inSampleSize = 2;
        else
            options.inSampleSize = 1;
        options.inJustDecodeBounds = false;

        return BitmapFactory.decodeFile(imgFilePath, options);
    }       

    /**
      * 지정한 패스의 파일의 EXIF 정보를 읽어서 회전시킬 각도 구하기
      *
      * @param imgFilePath
      *       bitmap file path
      * @return degree
      */
    public synchronized static int GetExifOrientation(String filepath) {
        int degree = 0;
        ExifInterface exif = null;
        
        try {
            exif = new ExifInterface(filepath);
        } 
        catch (IOException e) {
            Log.e("TAG", "cannot read exif");
            e.printStackTrace();
        }
        
        if (exif != null) {
            int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1);
            
            if (orientation != -1) {
                // We only recognize a subset of orientation tag values.
                switch(orientation) {
                    case ExifInterface.ORIENTATION_ROTATE_90:
                        degree = 90;
                        break;
                        
                    case ExifInterface.ORIENTATION_ROTATE_180:
                        degree = 180;
                        break;
                        
                    case ExifInterface.ORIENTATION_ROTATE_270:
                        degree = 270;
                        break;
                }
            }
        }
        
        return degree;
    }

    /**
      * 지정한 패스의 파일을 EXIF 정보에 맞춰 회전시키기
      *
      * @param bitmap
      *       bitmap handle
      * @return Bitmap
      */
    public synchronized static Bitmap GetRotatedBitmap(Bitmap bitmap, int degrees) {
        if (degrees != 0 && bitmap != null) {
            Matrix m = new Matrix();
            m.setRotate(degrees, (float) bitmap.getWidth() / 2,
                    (float) bitmap.getHeight() / 2 );
            try {
                Bitmap b2 = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
                        bitmap.getHeight(), m, true);
                if (bitmap != b2) {
                    bitmap.recycle();
                    bitmap = b2;
                }
            } 
            catch (OutOfMemoryError ex) {
                // We have no memory to rotate. Return the original bitmap.
            }
        }
        
        return bitmap;
    }
}

"프로그래밍" 카테고리의 다른 글

2012/05/01 18:17 2012/05/01 18:17

4 Comments (+add yours?)

  1. 손진한 2015/07/05 16:42

    앱 개발 도중에 이미지가 회전하고

    크기 때문에 오류가 나서 이것저것 찾아보고 있었는데

    덕분에 큰 문제 2개나 해결되었습니다 ㅠㅠ

    정말 유용한 정보 감사드립니다!!!

     Reply  Address

    • 새우 2015/07/07 03:32

      도움이 되었다니 저도 기분이 좋네요. ^^

       Address

  2. 형태희 2015/08/19 06:25

    상세한 코드 감사합니다.
    급했는데 복붙해서 살짝만 손봐주니까 바로 되네요 !!

     Reply  Address

    • 새우 2015/08/26 13:01

      네...유용하게 쓰세요. ^^

       Address

Newer Entries Older Entries

Leave a Reply

트랙백0 Tracbacks (+view to the desc.)

Trackback Address :: 이 글에는 트랙백을 보낼 수 없습니다