효율적인 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!

 

 




“효율적인 Bitmap 이미지 라운딩 처리방법”에 대한 5개의 생각

  1. 정말 도움이 되는 글이었습니다. 감사합니다.
    draw()에서 canvas.draw* method들로 여러가지 응용할 수 있을 듯 한대요.
    반쪽짜리 원이나 특수하게 정의된 Polygon형태로 가능할까요?

  2. 안녕하세요. 라운드처리에 관해 구글링 하다가 들어와서 사용하고 싶은데 클래스 구현을 하고 어떻게 사용을 해야 할지 잘 모르겠어서 질문 남깁니다.

    현재 소스에서 imageLoader.displayImage() 를 통해 이미지를 그리는 부분을 좀 수정하면 될 것 같은데 ImageView 로 개발되어 있는 영역을 불러와서 위 함수를 통해 그린 후에 비트맵으로 변환을 해서 클래스를 호출하면 되는것인지요?

댓글 남기기