Como cortar área circular de bitmap no Android

108

Eu tenho um bitmap e quero cortar uma região circular desse bitmap. Todos os pixels fora do círculo devem ser transparentes. Como posso fazer isso?

insira a descrição da imagem aqui

Altaf
fonte

Respostas:

216

Após um longo brainstorming, encontrei a solução

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;
}
Altaf
fonte
1
Você também pode fazer isso recortando o bitmap em um caminho de recorte circular. Você pode fazer isso sempre que desenhar o bitmap, o que significa que nunca realmente criará um bitmap com pixels transparentes, ou pode desenhar o bitmap recortado em um buffer que foi apagado para transparente anteriormente. Acho que qualquer um dos dois seria um pouco mais rápido e mais simples do que isso.
Gene
1
Obrigado. seu código funciona espetacularmente. Agora também posso cortar usando o caminho (polígono).
DearDhruv
2
Este método pode ser feito statice usado em uma classe de utilitário não instanciada de métodos estáticos semelhantes.
Matt Logan
1
Você não deveria usar o mínimo de altura e largura dividido por 2 como raio aqui? canvas.drawCircle(bitmap.getWidth() / 2, bitmap.getHeight() / 2, bitmap.getWidth() / 2, paint);
Varvara Kalinina
3
Existem 3 pontos principais: 1) Crie um bitmap vazio e desenhe um círculo. 2) Defina xfermode para SRC_IN. 3) Desenhe o bitmap real para os limites da tela. Portanto, a cor da pintura e outros desenhos da tela são inúteis.
Lym Zoy
44

para gerar um círculo a partir de retângulos

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;
}
diesel
fonte
1
Eu sugeriria o uso de duas visualizações de imagem em um framelayout com a visualização de imagem superior com recorte de círculo transparente.
diesel de
36

Você pode tornar sua visualização de imagem circular usando RoundedBitmapDrawable

aqui está o código para obter roundedImageview:

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);
sree
fonte
Obrigado por mostrar esta opção de biblioteca de suporte v4. Eu não acho que teria descoberto de outra forma.
CFJ90210
8
e usar setCircular (true) em vez de setCornerRadius (50.0f) transforma o drawable em um círculo. observação: a imagem deve ser quadrada ou a proporção está com defeito ...
Marco Schmitz
31

@Gene fez um comentário sobre a resposta acima que sugeria o uso clipPath como opção para recortar uma imagem como um círculo.

O seguinte é uma implementação limpa disso:

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

Isso pode ser adicionado a uma classe de utilitário.

HGPB
fonte
4
Eu estava prestes a postar um código muito semelhante. O problema é de acordo com developer.android.com/guide/topics/graphics/hardware-accel.html , clipPath não é compatível com aceleração de hardware. Na verdade, encontrei esse problema em um aplicativo e me perguntei o que estava acontecendo. Hardware mais novo parece corrigir isso, no entanto (como os tablets do Google). Uma possível limpeza adicional em seu código: você não precisa da conversão de reto para reto ao desenhar o bitmap. Você pode apenas dizer c.drawBitmap(b, 0, 0, null);, que usa a transformação de identidade padrão.
Gene
como você conseguiu usar o clipPath enquanto usava a aceleração de hardware?
speedynomads
Eu estava usando essa solução antes, mas a saída tinha bordas irregulares. A solução de @Altaf funciona melhor
dirkoneill
Funciona muito bem para cortar imagens usadas na notificação na barra de status
Damian Petla
14

Acho que essa solução funciona melhor com qualquer tipo de retângulo, mude o tamanho do pixel se quiser uma imagem pequena ou grande:

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;
    }
Jachumbelechao Unto Mantekilla
fonte
11

Isso pode ser feito facilmente em xml, sem cortar o bitmap real. Você só precisa criar uma máscara de imagem circular e colocá-la sobre a imagem real. Aqui está o trecho de código que usei:

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 (Ignore "android: scaleType =" fitXY "" se não precisar dele)

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

insira a descrição da imagem aqui

Visualização da imagem de saída:

Espero, pode ser útil para alguém !!! :)

Cinza
fonte
Parece que está funcionando apenas se a imagem tiver um fundo transparente menor que um círculo ...
meeDamian,
4
Você acabou de colocar um ImageView em cima de outro, isso não é uma máscara :)
Climbatize
@Claro, você está certo :) Só que desta forma você realmente não "corta" a imagem original, conforme solicitado pelo autor original;)
Climbatize
8

você pode usar este código, vai funcionar

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

fonte
Funciona bem na API Android <28, mas trava com java.lang.IllegalArgumentException: a renderização de software não oferece suporte a bitmaps de hardware no Android 28
Lucian Iacob,
7

você pode usar este código, vai funcionar

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;
}
anupam sharma
fonte
3

Eu recomendo adicionar, bitmap.recycle()se você não precisar mais, evitará o erro OutOfMemory.

badoualy
fonte
3

Aqui está a variante do Kotlin usando o método de extensão

/**
 * 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
    }
}
Yuriy Seredyuk
fonte
2

Para pessoas que desejam o centro do retângulo (eu), adicione isto antes de cortar:

    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 of Bitmap

jobbert
fonte
2

Com base na resposta [Jachumbelechao Unto Mantekilla], este código funciona como um encanto para quem procura uma solução 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
}
Rodrigo Salinas
fonte
Você pode converter isso em uma função de extensão
greenspand
algo como divertido Bitmap.getCircleCroppedBitmap (): Bitmap {} e use-o em vez de originalBitmap
greenspand
então você poderia usá-lo assim: img_user_photo.setImageBitmap (photo.getCircleCroppedBitmap ())
greenspand
onde foto é o objeto de bitmap estendido com a função
greenspand
1

Agora, resposta certa:

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;
}
mestre
fonte
1
O que são cx e cy?
Kartik Chugh
1
@ K_7, este é o centro de um círculo
Mestre em
1

Kotin Fucntion

 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
        }

chame-o por este código

 holder.itemFriendImage.setImageBitmap(ImageConverter.getRoundedCornerBitmap(bitmap,600))
Samet ÖZTOPRAK
fonte
1

Eu acredito que a solução mais fácil é criar um BitmapShader do seu Bitmap, passá-lo para o seu objeto de pintura e então simplesmente chamar algo como canvas.drawCircle (cx, cy, radius, paint);

por exemplo

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

É assim que https://github.com/hdodenhof/CircleImageView também fez, você pode ler o código-fonte aqui: https://github.com/hdodenhof/CircleImageView/blob/master/circleimageview/src/main/java /de/hdodenhof/circleimageview/CircleImageView.java

Martin Nowosad
fonte
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);
    }
Alok Singh
fonte
-1

Não tenho certeza se esta é uma questão de programação, mas ...

A solução mais fácil seria tornar a área externa transparente no bitmap de origem. Caso contrário, você terá que calcular quais pixels estão fora do círculo e definir o alfa de acordo (alfa = 0 para transparência total).

MandisaW
fonte
para ser sincero, tenho sido do seu jeito, parece que funciona, mas não podemos resolver o problema da borda irregular, não é?
VinceStyling
A "irregularidade" da borda é tratada por meio de pontilhamento e / ou suavização. Você pode procurar na Internet alguns algoritmos para realizar algo que pareça aceitável. Mas lembre-se de que os pixels retangulares e as curvas sempre terão esses problemas.
MandisaW