태그:                     

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

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

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

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

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

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

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

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

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

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

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

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

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

<?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>
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();
	}
}
/**
 * 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;
	}
}

관련글

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

이미지뷰에 맞게 사진 로딩하기(회전 보정 추가)”에 대한 4개의 생각

  • 2015년 7월 5일 7:42 오전
    고유주소

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

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

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

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

    응답
    • 2015년 7월 6일 6:32 오후
      고유주소

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

      응답
  • 2015년 8월 18일 9:25 오후
    고유주소

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

    응답
    • 2015년 8월 26일 4:01 오전
      고유주소

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

      응답

답글 남기기

이메일 주소는 공개되지 않습니다.