이 튜토리얼에서는 안드로이드(Android)에서 비트맵(Bitmap)으로부터 원형 영역을 잘라내어 원형 이미지를 만드는 방법을 단계별로 소개합니다.
프로필 사진이나 아바타 UI를 구현할 때 자주 쓰이는 기법으로, 단순히 원형으로 자르는 것뿐 아니라 흰색 테두리와 그림자 효과까지 추가하는 과정도 함께 다룹니다. 핵심은 PorterDuff.Mode.SRC_IN 전송 모드를 이용해 비트맵을 원형으로 마스킹하는 것입니다.
1단계 — 새 프로젝트 생성
Android Studio에서 File ⇒ New Project 메뉴로 이동한 뒤, 새 프로젝트 생성에 필요한 정보를 모두 입력하여 프로젝트를 만듭니다.
2단계 — 레이아웃 작성 (activity_main.xml)
res/layout/activity_main.xml 파일에 아래 코드를 추가합니다. 화면 중앙에는 이미지를 표시할 ImageView를, 하단 오른쪽에는 변환을 실행할 버튼을 배치했습니다.
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android" xmlns:app="https://schemas.android.com/apk/res-auto" xmlns:tools="https://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" android:id="@+id/rl" android:padding="16dp" android:background="#edf2ea"> <ImageView android:id="@+id/iv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true"/> <Button android:id="@+id/btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Circular It" android:layout_alignParentBottom="true" android:layout_alignParentRight="true"/> </RelativeLayout>
3단계 — MainActivity.java 작성
src/MainActivity.java에 다음 코드를 추가합니다. 이 클래스에는 세 가지 핵심 커스텀 메서드가 포함되어 있습니다.
- getCircularBitmap() — 비트맵의 가로·세로 중 짧은 쪽을 기준으로 정사각형 크기를 계산한 뒤, 캔버스에 타원을 그리고
SRC_IN모드로 원본 비트맵을 합성해 원형 비트맵을 생성합니다. - addBorderToCircularBitmap() — 원형 비트맵 주변에 지정한 색상(예제에서는 흰색)과 두께(15px)의 테두리를 그립니다.
- addShadowToCircularBitmap() — 원형 비트맵 바깥쪽에 옅은 회색(LTGRAY) 그림자 효과를 더해 입체감을 줍니다.
package com.medkart.sample;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.RelativeLayout;
public class MainActivity extends AppCompatActivity {
private Context mContext;
private Resources mResources;
private RelativeLayout mRelativeLayout;
private Button mBTN;
private ImageView mImageView;
private Bitmap mBitmap;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Get the application context
mContext = getApplicationContext();
// Get the Resources
mResources = getResources();
// Get the widgets reference from XML layout
mRelativeLayout = (RelativeLayout) findViewById(R.id.rl);
mImageView = (ImageView) findViewById(R.id.iv);
mBTN = (Button) findViewById(R.id.btn);
// Get the bitmap resource id
final int bitmapResourceID =R.drawable.flower;
// Set an image to ImageView
mImageView.setImageBitmap(BitmapFactory.decodeResource(mResources, bitmapResourceID));
// Set a click listener for Button widget
mBTN.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// Get the bitmap from drawable resources
mBitmap = BitmapFactory.decodeResource(mResources, bitmapResourceID);
// Create a circular bitmap
mBitmap = getCircularBitmap(mBitmap);
// Add a border around circular bitmap
mBitmap = addBorderToCircularBitmap(mBitmap, 15, Color.WHITE);
// Add a shadow around circular bitmap
mBitmap = addShadowToCircularBitmap(mBitmap, 4, Color.LTGRAY);
// Set the ImageView image as drawable object
mImageView.setImageBitmap(mBitmap);
}
});
}
protected Bitmap getCircularBitmap(Bitmap srcBitmap) {
// Calculate the circular bitmap width with border
int squareBitmapWidth = Math.min(srcBitmap.getWidth(), srcBitmap.getHeight());
// Initialize a new instance of Bitmap
Bitmap dstBitmap = Bitmap.createBitmap (
squareBitmapWidth, // Width
squareBitmapWidth, // Height
Bitmap.Config.ARGB_8888 // Config
);
Canvas canvas = new Canvas(dstBitmap);
// Initialize a new Paint instance
Paint paint = new Paint();
paint.setAntiAlias(true);
Rect rect = new Rect(0, 0, squareBitmapWidth, squareBitmapWidth);
RectF rectF = new RectF(rect);
canvas.drawOval(rectF, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
// Calculate the left and top of copied bitmap
float left = (squareBitmapWidth-srcBitmap.getWidth())/2;
float top = (squareBitmapWidth-srcBitmap.getHeight())/2;
canvas.drawBitmap(srcBitmap, left, top, paint);
// Free the native object associated with this bitmap.
srcBitmap.recycle();
// Return the circular bitmap
return dstBitmap;
}
// Custom method to add a border around circular bitmap
protected Bitmap addBorderToCircularBitmap(Bitmap srcBitmap, int borderWidth, int borderColor) {
// Calculate the circular bitmap width with border
int dstBitmapWidth = srcBitmap.getWidth()+borderWidth*2;
// Initialize a new Bitmap to make it bordered circular bitmap
Bitmap dstBitmap = Bitmap.createBitmap(dstBitmapWidth,dstBitmapWidth, Bitmap.Config.ARGB_8888);
// Initialize a new Canvas instance
Canvas canvas = new Canvas(dstBitmap);
// Draw source bitmap to canvas
canvas.drawBitmap(srcBitmap, borderWidth, borderWidth, null);
// Initialize a new Paint instance to draw border
Paint paint = new Paint();
paint.setColor(borderColor);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(borderWidth);
paint.setAntiAlias(true);
canvas.drawCircle(
canvas.getWidth() / 2, // cx
canvas.getWidth() / 2, // cy
canvas.getWidth()/2 - borderWidth / 2, // Radius
paint // Paint
);
// Free the native object associated with this bitmap.
srcBitmap.recycle();
// Return the bordered circular bitmap
return dstBitmap;
}
// Custom method to add a shadow around circular bitmap
protected Bitmap addShadowToCircularBitmap(Bitmap srcBitmap, int shadowWidth, int shadowColor){
// Calculate the circular bitmap width with shadow
int dstBitmapWidth = srcBitmap.getWidth()+shadowWidth*2;
Bitmap dstBitmap = Bitmap.createBitmap(dstBitmapWidth,dstBitmapWidth, Bitmap.Config.ARGB_8888);
// Initialize a new Canvas instance
Canvas canvas = new Canvas(dstBitmap);
canvas.drawBitmap(srcBitmap, shadowWidth, shadowWidth, null);
// Paint to draw circular bitmap shadow
Paint paint = new Paint();
paint.setColor(shadowColor);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(shadowWidth);
paint.setAntiAlias(true);
// Draw the shadow around circular bitmap
canvas.drawCircle (
dstBitmapWidth / 2, // cx
dstBitmapWidth / 2, // cy
dstBitmapWidth / 2 - shadowWidth / 2, // Radius
paint // Paint
);
srcBitmap.recycle();
return dstBitmap;
}
}
4단계 — AndroidManifest.xml 설정
Manifests/AndroidManifest.xml에 아래 코드를 추가합니다.
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="https://schemas.android.com/apk/res/android" package="com.medkart.sample"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
앱 실행 및 결과 확인
이제 애플리케이션을 실행해 보겠습니다. 실제 안드로이드 기기가 컴퓨터에 연결되어 있다고 가정합니다. Android Studio에서 프로젝트의 액티비티 파일 중 하나를 연 뒤, 툴바의 Run
아이콘을 클릭하세요. 기기 목록에서 본인의 모바일 기기를 선택하면 앱이 설치되고 실행됩니다.
앱이 시작되면 먼저 사각형 형태의 원본 이미지가 표시됩니다. “Circular It” 버튼을 누르면 흰색 테두리와 회색 그림자가 적용된 원형 이미지로 전환되는 것을 확인할 수 있습니다.

