Come ritagliare un'area circolare da bitmap in Android


108

Ho una bitmap e desidero ritagliare una regione circolare da questa bitmap. Tutti i pixel all'esterno del cerchio dovrebbero essere trasparenti. Come posso fare questo?

inserisci qui la descrizione dell'immagine

Risposte:


216

Dopo un lungo brainstorming ho trovato la soluzione

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
Puoi farlo anche ritagliando la bitmap su un tracciato di ritaglio circolare. Puoi farlo ogni volta che disegni la bitmap, il che significa che non creeresti mai effettivamente una bitmap con pixel trasparenti, oppure potresti disegnare la bitmap ritagliata in un buffer che è stato precedentemente cancellato in trasparente. Penso che uno dei due sarebbe un po 'più veloce e più semplice di questo.
Gene

1
Grazie. il tuo codice funziona in modo spettacolare. Ora posso anche ritagliare usando path (Polygon).
DearDhruv

2
Questo metodo può essere creato statice utilizzato in una classe di utilità non istanziata di metodi statici simili.
Matt Logan

1
Non dovresti usare il minimo di altezza e larghezza diviso per 2 come raggio qui? canvas.drawCircle(bitmap.getWidth() / 2, bitmap.getHeight() / 2, bitmap.getWidth() / 2, paint);
Varvara Kalinina

3
Ci sono 3 punti chiave: 1) Crea una bitmap vuota e disegna un cerchio. 2) Imposta xfermode su SRC_IN. 3) Disegna la bitmap reale ai limiti di quella tela. Quindi, il colore della vernice e altri disegni su tela non servono.
Lym Zoy

44

per generare Circle da rettangoli

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
Suggerirei di utilizzare due visualizzazioni di immagini in un framelayout con l'immagine in alto con un cerchio trasparente ritagliato.
diesel

36

È possibile rendere circolare la visualizzazione dell'immagine utilizzando RoundedBitmapDrawable

ecco il codice per ottenere 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);

Grazie per aver mostrato questa opzione della libreria di supporto v4. Non credo che l'avrei scoperto altrimenti.
CFJ90210

8
e l'uso di setCircular (true) invece di setCornerRadius (50.0f) rende il drawable un cerchio. nota: l'immagine deve essere quadrata o le proporzioni sono difettose ...
Marco Schmitz

31

@Gene ha commentato la risposta sopra che ha suggerito di utilizzare clipPathcome opzione per ritagliare un'immagine come un cerchio.

Quello che segue è un'implementazione pulita di questo:

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

Questo potrebbe essere aggiunto a una classe di utilità.


4
Stavo per pubblicare un codice molto simile. Il problema è secondo developer.android.com/guide/topics/graphics/hardware-accel.html , clipPath non è supportato con l'accelerazione hardware. In realtà mi sono imbattuto in quel problema in un'app e mi sono chiesto cosa stesse succedendo. Tuttavia, l'hardware più recente sembra risolvere questo problema (come i tablet Google). Una possibile ulteriore pulizia del codice: non è necessaria la conversione da rettangolo a rettangolo quando si disegna la bitmap. Puoi semplicemente dire c.drawBitmap(b, 0, 0, null);, che utilizza la trasformazione dell'identità predefinita.
Gene

come hai fatto a muoverti usando clipPath durante l'utilizzo dell'accelerazione hardware?
speedynomads

Inizialmente stavo usando questa soluzione prima, ma l'output aveva bordi frastagliati. La soluzione di @Altaf funziona meglio
dirkoneill

Funziona alla grande per ritagliare le immagini utilizzate nella notifica sulla barra di stato
Damian Petla,

14

Penso che questa soluzione funzioni meglio con qualsiasi tipo di rettangolo, cambia la dimensione dei pixel se vuoi un'immagine piccola o 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;
    }

11

Questo può essere fatto facilmente anche in xml senza ritagliare la bitmap effettiva, devi solo creare una maschera di immagine circolare e posizionarla sull'immagine reale. Ecco il pezzo di codice che ho usato:

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 (Ignora "android: scaleType =" fitXY "" se non ti serve)

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

inserisci qui la descrizione dell'immagine

Visualizzazione immagine in uscita:

Spero, potrebbe essere utile per qualcuno !!! :)


Sembra che funzioni solo se l'immagine ha uno sfondo trasparente più piccolo del cerchio ...
meeDamian

4
Hai appena messo un ImageView sopra un altro, questa non è una maschera :)
Climbatize

@Assicurati, hai ragione :) È solo che in questo modo non "ritagli" davvero l'immagine originale, come richiesto dal poster originale;)
Climbatize

8

puoi usare questo codice, funzionerà

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

Funziona bene su Android API <28, ma si arresta in modo anomalo con java.lang.IllegalArgumentException: il rendering software non supporta bitmap hardware su Android 28
Lucian Iacob

7

puoi usare questo codice, funzionerà

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

Consiglio di aggiungere bitmap.recycle()se non ne hai più bisogno, eviterà l'errore OutOfMemory.


3

Ecco la variante Kotlin che utilizza il metodo di estensione

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

Per le persone che vogliono il centro del rettangolo (io), aggiungi questo prima di tagliare:

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

Centro di ritaglio Android di Bitmap


2

Basato sulla risposta di [Jachumbelechao Unto Mantekilla], questo codice funziona come un fascino per le persone che cercano una soluzione 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
}

Potresti convertirlo in una funzione di estensione
greenspand

qualcosa di simile a divertente Bitmap.getCircleCroppedBitmap (): Bitmap {} e usa questo invece di originalBitmap
greenspand

allora potresti usarlo in questo modo: img_user_photo.setImageBitmap (photo.getCircleCroppedBitmap ())
greenspand

dove foto è l'oggetto bitmap esteso con la funzione
greenspand

1

Ora, risposta giusta:

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
Cosa sono cx e cy?
Kartik Chugh

1
@ K_7, questo è il centro di un cerchio
Master

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
        }

chiamalo con questo codice

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

1

Credo che la soluzione più semplice sia creare un BitmapShader della tua Bitmap, passarlo al tuo oggetto paint e quindi chiamare semplicemente qualcosa come canvas.drawCircle (cx, cy, radius, paint);

per esempio

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

Ecco come l' ha fatto anche https://github.com/hdodenhof/CircleImageView , puoi leggere il codice sorgente qui: 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

Non sono sicuro che questa sia una domanda di programmazione ma ...

La soluzione più semplice sarebbe rendere l'area esterna trasparente nella bitmap di origine. Altrimenti, dovrai calcolare quali pixel sono fuori dal cerchio e impostare l'alfa di conseguenza (alpha = 0 per la piena trasparenza).


ad essere onesti, sono stato a modo tuo, sembra funzionare ma non possiamo risolvere il problema del confine frastagliato, vero?
VinceStyling

La "frastagliatura" del confine viene affrontata mediante dithering e / o antialiasing. Potresti cercare online alcuni algoritmi per realizzare qualcosa che sembra accettabile. Ma tieni presente che i pixel rettangolari e le curve avranno sempre questi problemi.
MandisaW
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.