วิธีครอบตัดพื้นที่วงกลมจากบิตแมปใน Android


108

ฉันมีบิตแมปและต้องการครอบตัดพื้นที่วงกลมจากบิตแมปนี้ พิกเซลทั้งหมดนอกวงกลมควรโปร่งใส ฉันจะทำเช่นนี้ได้อย่างไร?

ใส่คำอธิบายภาพที่นี่

คำตอบ:


216

หลังจากการระดมความคิดเป็นเวลานานฉันพบวิธีแก้ปัญหา

public Bitmap getCroppedBitmap(Bitmap bitmap) {
    Bitmap output = Bitmap.createBitmap(bitmap.getWidth(),
            bitmap.getHeight(), Config.ARGB_8888);
    Canvas canvas = new Canvas(output);

    final int color = 0xff424242;
    final Paint paint = new Paint();
    final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());

    paint.setAntiAlias(true);
    canvas.drawARGB(0, 0, 0, 0);
    paint.setColor(color);
    // canvas.drawRoundRect(rectF, roundPx, roundPx, paint);
    canvas.drawCircle(bitmap.getWidth() / 2, bitmap.getHeight() / 2,
            bitmap.getWidth() / 2, paint);
    paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
    canvas.drawBitmap(bitmap, rect, rect, paint);
    //Bitmap _bmp = Bitmap.createScaledBitmap(output, 60, 60, false);
    //return _bmp;
    return output;
}

1
คุณสามารถทำได้โดยการตัดบิตแมปกับเส้นทางการตัดแบบวงกลม คุณสามารถทำได้ทุกครั้งที่คุณวาดบิตแมปซึ่งหมายความว่าคุณจะไม่เคยสร้างบิตแมปที่มีพิกเซลโปร่งใสหรือคุณสามารถวาดบิตแมปที่ถูกตัดไว้ในบัฟเฟอร์ที่ถูกลบให้โปร่งใสก่อน ฉันคิดว่ามันจะเร็วและง่ายกว่านี้สักหน่อย
ยีน

1
ขอบคุณ. รหัสของคุณใช้งานได้ดี ตอนนี้ฉันยังสามารถครอบตัดโดยใช้เส้นทาง (รูปหลายเหลี่ยม)
DearDhruv

2
วิธีนี้สามารถสร้างstaticและใช้ในคลาสยูทิลิตี้ที่ไม่สร้างอินสแตนซ์ของวิธีการคงที่ที่คล้ายกัน
Matt Logan

1
คุณไม่ควรใช้ความสูงและความกว้างขั้นต่ำหารด้วย 2 เป็นรัศมีที่นี่หรือ? canvas.drawCircle(bitmap.getWidth() / 2, bitmap.getHeight() / 2, bitmap.getWidth() / 2, paint);
Varvara Kalinina

3
ประเด็นสำคัญมี 3 ประการคือ1) สร้างบิตแมปเปล่าและวาดวงกลม 2) ตั้งค่า xfermode เป็น SRC_IN 3) วาดบิตแมปจริงไปยังขอบเขตผ้าใบนั้น ดังนั้นสีของสีและภาพวาดผ้าใบอื่น ๆ จึงไม่มีประโยชน์
Lym Zoy

44

เพื่อสร้างวงกลมจากสี่เหลี่ยม

public static Bitmap getCircularBitmap(Bitmap bitmap) {
    Bitmap output;

    if (bitmap.getWidth() > bitmap.getHeight()) {
        output = Bitmap.createBitmap(bitmap.getHeight(), bitmap.getHeight(), Config.ARGB_8888);
    } else {
        output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getWidth(), Config.ARGB_8888);
    }

    Canvas canvas = new Canvas(output);

    final int color = 0xff424242;
    final Paint paint = new Paint();
    final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());

    float r = 0;

    if (bitmap.getWidth() > bitmap.getHeight()) {
        r = bitmap.getHeight() / 2;
    } else {
        r = bitmap.getWidth() / 2;
    }

    paint.setAntiAlias(true);
    canvas.drawARGB(0, 0, 0, 0);
    paint.setColor(color);
    canvas.drawCircle(r, r, r, paint);
    paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
    canvas.drawBitmap(bitmap, rect, rect, paint);
    return output;
}

1
ฉันขอแนะนำให้ใช้การใช้มุมมองภาพสองภาพในเฟรมเลย์เอาต์ที่มีมุมมองภาพด้านบนที่ตัดวงกลมโปร่งใสออก
ดีเซล

36

คุณสามารถทำให้มุมมองภาพของคุณเป็นวงกลมโดยใช้RoundedBitmapDrawable

นี่คือรหัสสำหรับการบรรลุผล RoundImageview:

ImageView profilePic=(ImageView)findViewById(R.id.user_image);

//get bitmap of the image
Bitmap imageBitmap=BitmapFactory.decodeResource(getResources(),  R.drawable.large_icon);
RoundedBitmapDrawable roundedBitmapDrawable=RoundedBitmapDrawableFactory.create(getResources(), imageBitmap);

//setting radius
roundedBitmapDrawable.setCornerRadius(50.0f);
roundedBitmapDrawable.setAntiAlias(true);
profilePic.setImageDrawable(roundedBitmapDrawable);

ขอขอบคุณที่แสดงตัวเลือกไลบรารี v4 Support นี้ ฉันไม่คิดว่าฉันจะได้พบกับมันเป็นอย่างอื่น
CFJ90210

8
และการใช้ setCircular (จริง) แทน setCornerRadius (50.0f) ทำให้วงกลมที่วาดได้ หมายเหตุ: ภาพต้องเป็นสี่เหลี่ยมจัตุรัสไม่เช่นนั้นอัตราส่วนภาพมีข้อบกพร่อง ...
Marco Schmitz

31

@Gene แสดงความคิดเห็นเกี่ยวกับคำตอบข้างต้นที่แนะนำให้ใช้clipPathเป็นตัวเลือกในการครอบตัดรูปภาพเป็นวงกลม

ต่อไปนี้เป็นการนำไปใช้อย่างสะอาดหมดจด:

    public static Bitmap GetBitmapClippedCircle(Bitmap bitmap) {

        final int width = bitmap.getWidth();
        final int height = bitmap.getHeight();
        final Bitmap outputBitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);

        final Path path = new Path();
        path.addCircle(
                  (float)(width / 2)
                , (float)(height / 2)
                , (float) Math.min(width, (height / 2))
                , Path.Direction.CCW);

        final Canvas canvas = new Canvas(outputBitmap);
        canvas.clipPath(path);
        canvas.drawBitmap(bitmap, 0, 0, null);
        return outputBitmap;
    }

สิ่งนี้สามารถเพิ่มลงในคลาสยูทิลิตี้


4
ฉันกำลังจะโพสต์โค้ดที่คล้ายกันมาก ปัญหาเป็นไปตามdeveloper.android.com/guide/topics/graphics/hardware-accel.htmlไม่รองรับ clipPath กับการเร่งฮาร์ดแวร์ จริงๆแล้วฉันพบปัญหานั้นในแอพและสงสัยว่าเกิดอะไรขึ้น ฮาร์ดแวร์รุ่นใหม่ดูเหมือนจะแก้ไขปัญหานี้ได้ (เช่นแท็บเล็ต Google) การล้างข้อมูลโค้ดของคุณเพิ่มเติมที่เป็นไปได้อีกวิธีหนึ่ง: คุณไม่จำเป็นต้องใช้การแปลง rect-to-rect เมื่อวาดบิตแมป คุณสามารถพูดc.drawBitmap(b, 0, 0, null);ได้ซึ่งใช้การแปลงข้อมูลประจำตัวเริ่มต้น
ยีน

คุณใช้ clipPath ในขณะที่ใช้การเร่งฮาร์ดแวร์ได้อย่างไร
speedynomads

เดิมทีฉันใช้โซลูชันนี้มาก่อน แต่ผลลัพธ์มีขอบหยัก วิธีแก้ปัญหาจาก @Altaf ทำงานได้ดีขึ้น
dirkoneill

ใช้งานได้ดีสำหรับการครอบตัดรูปภาพที่ใช้ในการแจ้งเตือนบนแถบสถานะ
Damian Petla

14

ฉันคิดว่าโซลูชันนี้ทำงานได้ดีขึ้นกับสี่เหลี่ยมผืนผ้าทุกประเภทเปลี่ยนขนาดพิกเซลหากคุณต้องการให้ภาพเล็กหรือใหญ่:

public static Bitmap getCircleBitmap(Bitmap bm) {

        int sice = Math.min((bm.getWidth()), (bm.getHeight()));

        Bitmap bitmap = ThumbnailUtils.extractThumbnail(bm, sice, sice);

        Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);

        Canvas canvas = new Canvas(output);

        final int color = 0xffff0000;
        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);
        paint.setDither(true);
        paint.setFilterBitmap(true);
        canvas.drawARGB(0, 0, 0, 0);
        paint.setColor(color);
        canvas.drawOval(rectF, paint);

        paint.setColor(Color.BLUE);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth((float) 4);
        paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
        canvas.drawBitmap(bitmap, rect, rect, paint);

        return output;
    }

11

สิ่งนี้สามารถทำได้อย่างง่ายดายใน xml เช่นกันโดยไม่ต้องครอบตัดบิตแมปจริงคุณเพียงแค่สร้างมาสก์รูปภาพแบบวงกลมแล้ววางทับรูปภาพจริงของคุณ นี่คือส่วนของรหัสที่ฉันใช้:

circle.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval" >
    <gradient android:startColor="#00FFFFFF" android:endColor="#00FFFFFF"
        android:angle="270"/>
     <stroke android:width="10dp" android:color="#FFAAAAAA"/>

your_layout.xml (ละเว้น "android: scaleType =" fitXY "" ถ้าคุณไม่ต้องการ)

<RelativeLayout

        android:id="@+id/icon_layout"
        android:layout_width="@dimen/icon_mask"
        android:layout_height="@dimen/icon_mask"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true" >

        <ImageView
            android:id="@+id/icon"
            android:layout_width="@dimen/icon"
            android:layout_height="@dimen/icon"
            android:layout_centerInParent="true"
            android:scaleType="fitXY" >
        </ImageView>

        <ImageView
            android:id="@+id/icon_mask"
            android:layout_width="@dimen/icon_mask"
            android:layout_height="@dimen/icon_mask"
            android:layout_centerInParent="true"
            android:background="@drawable/circle"
            android:scaleType="fitXY" >
        </ImageView>
    </RelativeLayout>

dimen.xml


<dimen name="icon">36dp</dimen>
<dimen name="icon_mask">55dp</dimen>

ใส่คำอธิบายภาพที่นี่

มุมมองภาพ OutPut:

หวังว่าคงจะมีประโยชน์สำหรับใครบางคน !!! :)


ดูเหมือนว่าจะใช้งานได้ก็ต่อเมื่อภาพมีพื้นหลังโปร่งใสที่เล็กกว่าวงกลม ...
meeDamian

4
คุณแค่ใส่ ImageView ไว้ด้านบนอีกอันนี่ไม่ใช่มาสก์ :)
Climbatize

@ คุณแน่ใจใช่ไหม :) ฉันแค่นี้แหละที่คุณไม่ได้ "ครอบตัด" ภาพต้นฉบับจริงๆตามที่ผู้โพสต์ถามมา;)
Climbatize

8

คุณสามารถใช้รหัสนี้มันจะทำงาน

 private Bitmap getCircleBitmap(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.RED;
        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;
    }

ทำงานได้ดีบน Android API <28 แต่ขัดข้องกับ java.lang.IllegalArgumentException: การแสดงผลซอฟต์แวร์ไม่รองรับบิตแมปฮาร์ดแวร์บน Android 28
Lucian Iacob

7

คุณสามารถใช้รหัสนี้มันจะทำงาน

public Bitmap getRoundedShape(Bitmap scaleBitmapImage) {
    int targetWidth = 110;
    int targetHeight = 110;
    Bitmap targetBitmap = Bitmap.createBitmap(targetWidth, 
            targetHeight,Bitmap.Config.ARGB_8888);

    Canvas canvas = new Canvas(targetBitmap);
    Path path = new Path();
    path.addCircle(((float) targetWidth - 1) / 2,
            ((float) targetHeight - 1) / 2,
            (Math.min(((float) targetWidth), 
                    ((float) targetHeight)) / 2),
                    Path.Direction.CCW);

    canvas.clipPath(path);
    Bitmap sourceBitmap = scaleBitmapImage;
    canvas.drawBitmap(sourceBitmap, 
            new Rect(0, 0, sourceBitmap.getWidth(),
                    sourceBitmap.getHeight()), 
                    new Rect(0, 0, targetWidth, targetHeight), new Paint(Paint.FILTER_BITMAP_FLAG));
    return targetBitmap;
}

3

ฉันขอแนะนำให้เพิ่มbitmap.recycle()หากคุณไม่ต้องการอีกต่อไปจะป้องกันข้อผิดพลาด OutOfMemory


3

นี่คือตัวแปร Kotlin โดยใช้วิธีการขยาย

/**
 * Creates new circular bitmap based on original one.
 */
fun Bitmap.getCircularBitmap(config: Bitmap.Config = Bitmap.Config.ARGB_8888): Bitmap {
    // circle configuration
    val circlePaint = Paint().apply { isAntiAlias = true }
    val circleRadius = Math.max(width, height) / 2f

    // output bitmap
    val outputBitmapPaint = Paint(circlePaint).apply { xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN) }
    val outputBounds = Rect(0, 0, width, height)
    val output = Bitmap.createBitmap(width, height, config)

    return Canvas(output).run {
        drawCircle(circleRadius, circleRadius, circleRadius, circlePaint)
        drawBitmap(this@getCircularBitmap, outputBounds, outputBounds, outputBitmapPaint)
        output
    }
}

2

สำหรับ Peaple ที่ต้องการตรงกลางของสี่เหลี่ยมผืนผ้า (me) ให้เพิ่มสิ่งนี้ก่อนตัด:

    public static Bitmap cropBitmapToBlock(Bitmap bitmap) {
    if (bitmap.getWidth() >= bitmap.getHeight()){
        return Bitmap.createBitmap(
                bitmap,
                bitmap.getWidth()/2 - bitmap.getHeight()/2,
                0,
                bitmap.getHeight(),
                bitmap.getHeight()
        );
    }else{
        return Bitmap.createBitmap(
                bitmap,
                0,
                bitmap.getHeight()/2 - bitmap.getWidth()/2,
                bitmap.getWidth(),
                bitmap.getWidth()
        );
    }
} 

Android Crop Center ของ Bitmap


2

จากคำตอบของ [Jachumbelechao Unto Mantekilla] รหัสนี้ใช้งานได้ดีสำหรับผู้ที่กำลังมองหาโซลูชัน Kotlin:

fun cropCircleFromBitmap(originalBitmap: Bitmap): Bitmap {
    val size = Math.min(originalBitmap.width, originalBitmap.height)
    val bitmap = ThumbnailUtils.extractThumbnail(originalBitmap, size, size)
    var output = Bitmap.createBitmap(bitmap.width, bitmap.height, Bitmap.Config.ARGB_8888)
    val canvas = Canvas(output)
    val paint = Paint()
    val rect = Rect(0, 0, bitmap.width, bitmap.height)
    val rectF = RectF(rect)
    paint.isAntiAlias = true
    paint.isDither = true
    paint.isFilterBitmap = true
    canvas.drawARGB(0, 0, 0, 0)
    paint.color = 0xffff0000.toInt()
    canvas.drawOval(rectF, paint)
    paint.color = Color.BLUE
    paint.style = Paint.Style.STROKE
    paint.strokeWidth = 4f
    paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
    canvas.drawBitmap(bitmap, rect, rect, paint)
    return output
}

คุณสามารถแปลงสิ่งนี้เป็นฟังก์ชันส่วนขยายได้
greenspand

บางอย่างเช่น Bitmap.getCircleCroppedBitmap (): Bitmap {} และใช้สิ่งนี้แทน originalBitmap
greenspand

จากนั้นคุณสามารถใช้มันได้ดังนี้: img_user_photo.setImageBitmap (photo.getCircleCroppedBitmap ())
greenspand

โดยที่ภาพถ่ายเป็นวัตถุบิตแมปที่ขยายด้วยฟังก์ชัน
greenspand

1

ตอนนี้คำตอบที่ถูกต้อง:

private Bitmap getCroppedBitmap(Bitmap bitmap, Integer cx, Integer cy, Integer radius) {
    int diam = radius << 1;
    Bitmap targetBitmap = Bitmap.createBitmap(diam, diam, Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(targetBitmap);
    final int color = 0xff424242;
    final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    canvas.drawARGB(0, 0, 0, 0);
    paint.setColor(color);
    canvas.drawCircle(radius, radius, radius, paint);
    paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
    canvas.drawBitmap(bitmap, -cx+radius, -cy+radius, paint);
    return targetBitmap;
}

1
cx และ cy คืออะไร?
Kartik Chugh

1
@ K_7 นี่คือจุดศูนย์กลางของวงกลม
Master

1

โคตินฟูคอนติออน

 fun getRoundedCornerBitmap(bitmap: Bitmap, pixels: Int): Bitmap {
            val output = Bitmap.createBitmap(bitmap.width, bitmap.height, Bitmap.Config.ARGB_8888)
            val canvas = Canvas(output)

            val color = -0xbdbdbe
            val paint = Paint()
            val rect = Rect(0, 0, bitmap.width, bitmap.height)
            val rectF = RectF(rect)
            val roundPx = pixels.toFloat()

            paint.isAntiAlias = true
            canvas.drawARGB(0, 0, 0, 0)
            paint.color = color
            canvas.drawRoundRect(rectF, roundPx, roundPx, paint)

            paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
            canvas.drawBitmap(bitmap, rect, rect, paint)

            return output
        }

เรียกตามรหัสนี้

 holder.itemFriendImage.setImageBitmap(ImageConverter.getRoundedCornerBitmap(bitmap,600))

1

ฉันเชื่อว่าวิธีแก้ปัญหาที่ง่ายที่สุดคือการสร้าง BitmapShader ของ Bitmap ของคุณส่งผ่านไปยังวัตถุสีของคุณแล้วเรียกสิ่งที่ต้องการ canvas.drawCircle (cx, cy, radius, paint);

ตัวอย่างเช่น

Paint p = new Paint();
p.setShader(new BitmapShader(myBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
canvas.drawCircle(getWidth() / 2, getHeight() / 2, getHeight() / 2, p);

นี่คือวิธีที่https://github.com/hdodenhof/CircleImageViewได้ทำเช่นกันคุณสามารถอ่านซอร์สโค้ดได้ที่นี่: https://github.com/hdodenhof/CircleImageView/blob/master/circleimageview/src/main/java /de/hdodenhof/circleimageview/CircleImageView.java


0
**Jst Add this to your image Id and get the circuler image.**

 imgUserProfile.setImageBitmap(getCircularCenterCropBitmap(bitmap, (int) (150 * denisty)));

Method:-

public void Bitmap getCircularCenterCropBitmap(Bitmap originalBmp, int diameter) {
        Bitmap resizedBmp = BitmapUtils.getScaledCroppedBitmap(originalBmp, diameter, diameter);
        return BitmapUtils.getRoundedCircularBitmap(resizedBmp, diameter / 2);
    }

-1

ไม่แน่ใจว่านี่เป็นคำถามเกี่ยวกับการเขียนโปรแกรม แต่ ...

วิธีแก้ปัญหาที่ง่ายที่สุดคือทำให้พื้นที่ภายนอกโปร่งใสในบิตแมปต้นทาง มิฉะนั้นคุณจะต้องคำนวณพิกเซลที่อยู่นอกวงกลมและตั้งค่าอัลฟาตามนั้น (alpha = 0 เพื่อความโปร่งใสทั้งหมด)


พูดตามตรงฉันเป็นทางของคุณดูเหมือนว่าจะได้ผล แต่เราไม่สามารถแก้ปัญหาเส้นขอบหยักได้ใช่ไหม
VinceStyling

"jaggyness" ของเส้นขอบได้รับการแก้ไขโดยการลบและ / หรือการลบรอยหยัก คุณสามารถค้นหาอัลกอริทึมบางอย่างทางออนไลน์เพื่อทำสิ่งที่ดูเหมือนจะยอมรับได้ แต่โปรดทราบว่าพิกเซลสี่เหลี่ยมและเส้นโค้งจะมีปัญหาเหล่านี้เสมอ
MandisaW
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.