효율적인 Bitmap 이미지 라운딩 처리방법



 

요즘 소셜 앱/웹을 보면 프로필 이미지를 둥근 라운딩 처리를 하는게 디자인적으로 트렌드인것 같다.

대표적으로 페이스북 메신저 앱과 Path앱 이다. 또한 구글+ 앱/웹 모두 프로필 이미지를 라운딩 처리 한 것을 볼 수있다.

 

 

 

그렇다면 안드로이드에서 사각형 이미지를 효율적으로 둥글게 라운딩 처리를 할 수 있을지 알아보자.

 

방법은 2가지로 방법으로

  1. 이미지 위에 마스크 이미지를 얹는 방법

  2. 이미지를 둥글게 잘라 내는 방법

 

 

첫번째 이미지 위에 마스크 이미지를 얹는 방법을 구현 해보자.

 

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
             android:layout_width="match_parent"
             android:layout_height="match_parent">
  <FrameLayout
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_gravity="center"
      android:foreground="@drawable/overlay"
      android:foregroundGravity="center"
      >
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:src="@drawable/avatar"
        />
  </FrameLayout>
 
</FrameLayout>

이와 같은 레이아웃 구성을 통해서 이미지위에 마스크를 얹는 방법이다.

 

 

비 효율적

 

이 레이아웃 구성은 Background에 따라 마스크이미지가 바껴야 한다는 문제점이 있다. 백그라운드 이미지가 모두 같은 경우에는 이 방법이 효율적이긴 하나, 위의 이미지에서 보듯 배경 백그라운드가 화면별로 다양해질때는 배경과 맞는 Foreground 이미지가 필요하다.

 

두 번째 방법으로 Bitmap이미지를 라운드를 투명으로 라운딩 하는 방법이다.

public static Bitmap getRoundedBitmap(Bitmap bitmap) {
    final Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
    final Canvas canvas = new Canvas(output);
 
    final int color = Color.GRAY;
    final Paint paint = new Paint();
    final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
    final RectF rectF = new RectF(rect);
 
    paint.setAntiAlias(true);
    canvas.drawARGB(0, 0, 0, 0);
    paint.setColor(color);
    canvas.drawOval(rectF, paint);
 
    paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
    canvas.drawBitmap(bitmap, rect, rect, paint);
 
    bitmap.recycle();
 
    return output;
  }

 

인터넷을 보면 위와 같은 라운드 해주는 코드들이 많이돌아 다닌다. 새로운 비트맵을 만들어 라운딩 한다음 기존 비트맵을 recycle을 하고 새로 만든 비트맵을 반환 한다.

라운딩 된 부분이 투명으로 처리 되기 때문에 첫번째 방법과 같은 문제점은 없다.

 

 

 

성능/안정 문제

 

 

현재 처럼 메인쓰레드(UI쓰레드)에서 작업을 하게 되면 여러 이미지를 한번에 생성 하게 되면 작업하는 동안 화면이 멈추게 되므로 별도의 쓰레드에서 작업을 해야 한다. 이 보다 더 문제점은 OutOfMemoryException이 일어날 가능성이 높다는 것이다. 기존 이미지를 recycle를 해준다고 해서 GC가 바로 메모리 해제를 해주지 않는다. 이러한 문제점으로 인해 OutOfMemoryException이 날 확률이 대단히 크다는 문제점이 있다.

 

위의 문제점은 자세히 설명한 아래 블로그를 통해 알아보면 되겠다.

http://blog.naver.com/PostView.nhn?blogId=nimbusob&logNo=147298809

 

 

 

이런 문제점을 모두 감안한 방법은 없을까?

안드로이드의 Drawable을 이용해서 이미지가  draw될때 이미지를 라운딩 처리해서 그리는 방법이다.

public class RoundedAvatarDrawable extends Drawable {
  private final Bitmap mBitmap;
  private final Paint mPaint;
  private final RectF mRectF;
  private final int mBitmapWidth;
  private final int mBitmapHeight;
 
  public RoundedAvatarDrawable(Bitmap bitmap) {
    mBitmap = bitmap;
    mRectF = new RectF();
    mPaint = new Paint();
    mPaint.setAntiAlias(true);
    mPaint.setDither(true);
    final BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
    mPaint.setShader(shader);
 
    mBitmapWidth = mBitmap.getWidth();
    mBitmapHeight = mBitmap.getHeight();
  }
 
  @Override
  public void draw(Canvas canvas) {
    canvas.drawOval(mRectF, mPaint);
  }
 
  @Override
  protected void onBoundsChange(Rect bounds) {
    super.onBoundsChange(bounds);
 
    mRectF.set(bounds);
  }
 
  @Override
  public void setAlpha(int alpha) {
    if (mPaint.getAlpha() != alpha) {
      mPaint.setAlpha(alpha);
      invalidateSelf();
    }
  }
 
  @Override
  public void setColorFilter(ColorFilter cf) {
    mPaint.setColorFilter(cf);
  }
 
  @Override
  public int getOpacity() {
    return PixelFormat.TRANSLUCENT;
  }
 
  @Override
  public int getIntrinsicWidth() {
    return mBitmapWidth;
  }
 
  @Override
  public int getIntrinsicHeight() {
    return mBitmapHeight;
  }
 
  public void setAntiAlias(boolean aa) {
    mPaint.setAntiAlias(aa);
    invalidateSelf();
  }
 
  @Override
  public void setFilterBitmap(boolean filter) {
    mPaint.setFilterBitmap(filter);
    invalidateSelf();
  }
 
  @Override
  public void setDither(boolean dither) {
    mPaint.setDither(dither);
    invalidateSelf();
  }
 
  public Bitmap getBitmap() {
    return mBitmap;
  }
 
}

 

두번째 방법에서 새로운 Bitmap이미지를 만드는게 아닌 이미지를 화면에 그리는 시점에 라운딩 처리를 한다는 것이다. 위의 코드는 Bitmap을 Drawable의 Canvas에 그릴때 라운딩 처리한다. 성능과 효율을 한번에 해결 해준다.

 

 

 

The Good!