如何在Android中从位图中裁剪圆形区域

121
我有一个位图,想从中裁剪出一个圆形区域。圆外的所有像素都应该是透明的。我该如何做?

图片描述

19个回答

230

经过长时间的头脑风暴,我找到了解决方案

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
你也可以通过将位图剪切为圆形裁剪路径来实现这一点。你可以每次绘制位图时都这么做,这意味着你实际上不会创建有透明像素的位图,或者你可以将被裁剪的位图绘制到先前已被擦除为透明的缓冲区中。我认为这两种方法都比这个更快更简单。 - Gene
1
谢谢。你的代码运行得非常好。现在我也可以使用路径(多边形)进行裁剪了。 - DearDhruv
2
这个方法可以被设置为静态,并且用于一个非实例化的工具类中,该类包含了类似的静态方法。 - Matt Logan
2
这里不应该使用高度和宽度的最小值除以2作为半径吗?canvas.drawCircle(bitmap.getWidth() / 2, bitmap.getHeight() / 2, bitmap.getWidth() / 2, paint); - Varvara Kalinina
3
有三个关键点:1)创建一个空位图并绘制一个圆形。2)将xfermode设置为SRC_IN。3)将真正的位图绘制到该画布边界上。因此,绘画颜色和其他画布绘制都没有用处。 - Lym Zoy
显示剩余7条评论

49

从矩形生成圆形

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
我建议在FrameLayout中使用两个ImageView,其中顶部的ImageView带有透明圆形剪切。 - diesel

42

你可以使用RoundedBitmapDrawable使你的ImageView变成圆形。

这是实现圆形ImageView的代码:

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支持库选项。否则我可能不会发现它。 - CFJ90210
8
使用setCircular(true)而不是setCornerRadius(50.0f),可以将可绘制对象变成圆形。请注意:图像必须是正方形,否则宽高比将出现问题。 - Marco Schmitz
已弃用!不再维护 - ucMedia

34

@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
我正要发布非常相似的代码。问题是根据http://developer.android.com/guide/topics/graphics/hardware-accel.html,clipPath不支持硬件加速。我在一个应用程序中遇到了这个问题,想知道发生了什么。然而,较新的硬件似乎可以解决这个问题(如谷歌平板电脑)。你可能需要进一步清理代码:在绘制位图时,你不需要进行矩形到矩形的转换。你可以只写`c.drawBitmap(b, 0, 0, null);`,它使用默认的恒等变换。 - Gene
你在使用硬件加速时如何避免使用clipPath? - speedynomads
我之前用了这个解决方案,但输出边缘有锯齿。@Altaf的解决方案效果更好。 - YoungDinosaur
非常适合裁剪用于状态栏通知的图像。 - 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>

enter image description here

输出图像视图:

希望这对某些人有用!!! :)


似乎只有当图像具有小于圆形的透明背景时才有效... - meeDamian
4
你只是把一个ImageView放在另一个上面,这不是遮罩 :) - Climbatize
@Ash 确实,你说得对 :) 只是这样做并没有真正地“裁剪”原始图像,正如原帖所要求的那样 ;) - 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;
    }

1
它在Android API < 28上运行良好,但在Android 28上崩溃,报错java.lang.IllegalArgumentException:软件渲染不支持硬件位图。 - Lucian Iacob
它正在创建椭圆形位图而不是圆形。 - Krutika Chotara

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

很遗憾,clipPath没有反锯齿效果。 - ucMedia

4

我认为最简单的解决方案是创建一个位图着色器(BitmapShader),将其传递给你的画笔对象,然后简单地调用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

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
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接