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

วิธีครอปพื้นที่วงกลมจาก Bitmap ด้วยการสัมผัส (Touch) ใน Android

ตัวอย่างนี้สาธิตวิธีให้ผู้ใช้วาดพื้นที่บนรูปภาพด้วยนิ้ว แล้วครอป (Crop) เฉพาะพื้นที่ที่วาดได้ โดยใช้ Path และ PorterDuffXfermode

ขั้นตอนที่ 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:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
</RelativeLayout>

ขั้นตอนที่ 3: MainActivity.java (Custom View)

import androidx.appcompat.app.AppCompatActivity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // ใช้ Custom View แทน Layout XML
        setContentView(new CutImage(this));
    }

    class CutImage extends View {
        Bitmap bitmap;
        Path path = new Path();
        Paint paint;
        Paint cropPaint;

        public CutImage(Context context) {
            super(context);
            // โหลดรูปภาพจาก drawable (ต้องมีไฟล์ demo.png ใน res/drawable)
            bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.demo);
            
            // Paint สำหรับวาด Path (สีแดง ขีดเส้น)
            paint = new Paint();
            paint.setColor(Color.RED);
            paint.setStyle(Paint.Style.STROKE);
            paint.setStrokeWidth(5f);

            // Paint สำหรับ Crop (ใช้ PorterDuff)
            cropPaint = new Paint();
        }

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            // วาด Bitmap ต้นฉบับ
            canvas.drawBitmap(bitmap, 0, 0, null);
            // วาด Path ที่ผู้ใช้ลาก
            canvas.drawPath(path, paint);
        }

        @Override
        public boolean onTouchEvent(MotionEvent event) {
            float x = event.getX();
            float y = event.getY();

            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    // เริ่มวาด Path
                    path.moveTo(x, y);
                    break;
                case MotionEvent.ACTION_MOVE:
                case MotionEvent.ACTION_UP:
                    // ต่อ Path และ Crop ทันที
                    path.lineTo(x, y);
                    cropBitmap();
                    break;
            }
            invalidate(); // เรียก onDraw ใหม่
            return true;
        }

        private void cropBitmap() {
            // สร้าง Bitmap ใหม่สำหรับผลลัพธ์
            Bitmap croppedBitmap = Bitmap.createBitmap(
                bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig()
            );
            
            Canvas cropCanvas = new Canvas(croppedBitmap);
            Paint paint1 = new Paint();
            
            // วาด Path ลง Canvas ใหม่ (สร้าง Mask)
            cropCanvas.drawPath(path, paint1);
            
            // ใช้ SRC_IN: เก็บเฉพาะส่วนที่ Path และ Bitmap ทับกัน
            paint1.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
            
            // วาด Bitmap ต้นฉบับลงไป (จะเหลือเฉพาะใน Path)
            cropCanvas.drawBitmap(bitmap, 0, 0, paint1);
            
            // อัปเดต Bitmap
            bitmap = croppedBitmap;
        }
    }
}

ขั้นตอนที่ 4: AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
    package="app.com.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>

การทำงานของโค้ด

  1. โหลดรูปภาพ R.drawable.demo มาเป็น Bitmap
  2. ผู้ใช้ลากนิ้วบนหน้าจอ → สร้าง Path
  3. ทุกครั้งที่ลาก (ACTION_MOVE) หรือปล่อย (ACTION_UP) จะเรียก cropBitmap()
  4. cropBitmap() สร้าง Canvas ใหม่ → วาด Path → ตั้ง Xfermode = SRC_IN → วาด Bitmap ต้นฉบับ
  5. ผลลัพธ์: Bitmap ใหม่ที่เหลือเฉพาะพื้นที่ใน Path

ข้อจำกัดและการปรับปรุง

  • Path ต้องปิดล้อม (Closed Path) จะได้ผลลัพธ์สมบูรณ์ - ปัจจุบันโค้ดไม่ปิด Path อัตโนมัติ
  • ควรเพิ่ม path.close() เมื่อ ACTION_UP
  • ต้องการ Crop เป็นวงกลมจริง ให้ใช้วิธีในบทความก่อนหน้า (ID 1004061883)
  • ประสิทธิภาพ: การสร้าง Bitmap ใหม่ทุกครั้งที่ Move อาจช้า - ควร Optimize

การทดสอบ

รันแอป → ลากนิ้ววาดรูปบนภาพ → จะเห็นพื้นที่นอก Path หายไป ทีละส่วนตามการลาก