Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Android

วิธีครอป Bitmap ให้เป็นวงกลม พร้อมขอบและเงาใน Android

ตัวอย่างนี้สาธิตวิธีการตัด Bitmap ให้เป็นรูปวงกลม (Circular Crop) พร้อมเพิ่มขอบ (Border) และเงา (Shadow) ด้วย Canvas และ Paint

ขั้นตอนที่ 1: สร้างโปรเจกต์ใหม่

Android Studio → File → New Project

ขั้นตอนที่ 2: Layout (res/layout/activity_main.xml)

<?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 (Logic หลัก)

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);

        mContext = getApplicationContext();
        mResources = getResources();
        mRelativeLayout = findViewById(R.id.rl);
        mImageView = findViewById(R.id.iv);
        mBTN = findViewById(R.id.btn);

        final int bitmapResourceID = R.drawable.flower;
        
        // แสดงรูปต้นฉบับ
        mImageView.setImageBitmap(
            BitmapFactory.decodeResource(mResources, bitmapResourceID)
        );

        mBTN.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 1. โหลด Bitmap ต้นฉบับ
                mBitmap = BitmapFactory.decodeResource(mResources, bitmapResourceID);
                
                // 2. ตัดเป็นวงกลม
                mBitmap = getCircularBitmap(mBitmap);
                
                // 3. เพิ่มขอบสีขาว ความหนา 15px
                mBitmap = addBorderToCircularBitmap(mBitmap, 15, Color.WHITE);
                
                // 4. เพิ่มเงาสีเทาอ่อน ความหนา 4px
                mBitmap = addShadowToCircularBitmap(mBitmap, 4, Color.LTGRAY);
                
                // 5. แสดงผล
                mImageView.setImageBitmap(mBitmap);
            }
        });
    }

    // ===== Method 1: ตัด Bitmap ให้เป็นวงกลม =====
    protected Bitmap getCircularBitmap(Bitmap srcBitmap) {
        // คำนวณขนาดสี่เหลี่ยมจัตุรัส (ใช้ด้านที่สั้นกว่า)
        int squareBitmapWidth = Math.min(srcBitmap.getWidth(), srcBitmap.getHeight());

        // สร้าง Bitmap ใหม่สำหรับผลลัพธ์
        Bitmap dstBitmap = Bitmap.createBitmap(
            squareBitmapWidth, squareBitmapWidth, Bitmap.Config.ARGB_8888
        );

        Canvas canvas = new Canvas(dstBitmap);
        Paint paint = new Paint();
        paint.setAntiAlias(true); // ขอบนุ่ม

        // วาดวงกลมบน Canvas
        Rect rect = new Rect(0, 0, squareBitmapWidth, squareBitmapWidth);
        RectF rectF = new RectF(rect);
        canvas.drawOval(rectF, paint);

        // ใช้ PorterDuffXfermode เพื่อเก็บเฉพาะส่วนที่ทับกับวงกลม
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));

        // คำนวณตำแหน่งวาด Bitmap ต้นฉบับให้อยู่กลาง
        float left = (squareBitmapWidth - srcBitmap.getWidth()) / 2f;
        float top = (squareBitmapWidth - srcBitmap.getHeight()) / 2f;
        canvas.drawBitmap(srcBitmap, left, top, paint);

        // ปล่อยหน่วยความจำ Bitmap ต้นฉบับ
        srcBitmap.recycle();

        return dstBitmap;
    }

    // ===== Method 2: เพิ่มขอบ (Border) =====
    protected Bitmap addBorderToCircularBitmap(Bitmap srcBitmap, int borderWidth, int borderColor) {
        int dstBitmapWidth = srcBitmap.getWidth() + borderWidth * 2;
        
        Bitmap dstBitmap = Bitmap.createBitmap(
            dstBitmapWidth, dstBitmapWidth, Bitmap.Config.ARGB_8888
        );

        Canvas canvas = new Canvas(dstBitmap);
        
        // วาด Bitmap ต้นฉบับ (วงกลม) ลงไปก่อน
        canvas.drawBitmap(srcBitmap, borderWidth, borderWidth, null);

        // วาดขอบ
        Paint paint = new Paint();
        paint.setColor(borderColor);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(borderWidth);
        paint.setAntiAlias(true);

        canvas.drawCircle(
            canvas.getWidth() / 2f,
            canvas.getWidth() / 2f,
            canvas.getWidth() / 2f - borderWidth / 2f,
            paint
        );

        srcBitmap.recycle();
        return dstBitmap;
    }

    // ===== Method 3: เพิ่มเงา (Shadow) =====
    protected Bitmap addShadowToCircularBitmap(Bitmap srcBitmap, int shadowWidth, int shadowColor) {
        int dstBitmapWidth = srcBitmap.getWidth() + shadowWidth * 2;
        
        Bitmap dstBitmap = Bitmap.createBitmap(
            dstBitmapWidth, dstBitmapWidth, Bitmap.Config.ARGB_8888
        );

        Canvas canvas = new Canvas(dstBitmap);
        canvas.drawBitmap(srcBitmap, shadowWidth, shadowWidth, null);

        Paint paint = new Paint();
        paint.setColor(shadowColor);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(shadowWidth);
        paint.setAntiAlias(true);

        canvas.drawCircle(
            dstBitmapWidth / 2f,
            dstBitmapWidth / 2f,
            dstBitmapWidth / 2f - shadowWidth / 2f,
            paint
        );

        srcBitmap.recycle();
        return dstBitmap;
    }
}

ขั้นตอนที่ 4: 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>

คำอธิบายเทคนิคสำคัญ

เทคนิค คำอธิบาย
PorterDuff.Mode.SRC_IN เก็บเฉพาะพิกเซลที่ทับกันระหว่าง Bitmap ต้นฉบับกับ Shape วงกลม
paint.setAntiAlias(true) ทำให้ขอบวงกลมนุ่ม ไม่มีเส้นหยัก
bitmap.recycle() ปล่อยหน่วยความจำ Native ของ Bitmap เก่า (สำคัญสำหรับหน่วยความจำ)
Bitmap.Config.ARGB_8888 รองรับ Alpha (โปร่งใส) 8-bit ต่อ Channel

การทดสอบ

รันแอป → กดปุ่ม "Circular It" → รูปภาพจะกลายเป็นวงกลมพร้อมขอบสีขาวและเงาสีเทา