ตัวอย่างนี้สาธิตวิธีให้ผู้ใช้วาดพื้นที่บนรูปภาพด้วยนิ้ว แล้วครอป (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>
การทำงานของโค้ด
- โหลดรูปภาพ
R.drawable.demoมาเป็น Bitmap - ผู้ใช้ลากนิ้วบนหน้าจอ → สร้าง
Path - ทุกครั้งที่ลาก (
ACTION_MOVE) หรือปล่อย (ACTION_UP) จะเรียกcropBitmap() cropBitmap()สร้าง Canvas ใหม่ → วาด Path → ตั้งXfermode = SRC_IN→ วาด Bitmap ต้นฉบับ- ผลลัพธ์: Bitmap ใหม่ที่เหลือเฉพาะพื้นที่ใน Path
ข้อจำกัดและการปรับปรุง
- Path ต้องปิดล้อม (Closed Path) จะได้ผลลัพธ์สมบูรณ์ - ปัจจุบันโค้ดไม่ปิด Path อัตโนมัติ
- ควรเพิ่ม
path.close()เมื่อACTION_UP - ต้องการ Crop เป็นวงกลมจริง ให้ใช้วิธีในบทความก่อนหน้า (ID 1004061883)
- ประสิทธิภาพ: การสร้าง Bitmap ใหม่ทุกครั้งที่ Move อาจช้า - ควร Optimize
การทดสอบ
รันแอป → ลากนิ้ววาดรูปบนภาพ → จะเห็นพื้นที่นอก Path หายไป ทีละส่วนตามการลาก