Comment recadrer une zone circulaire à partir d'un bitmap sous Android


108

J'ai un bitmap et je souhaite recadrer une région circulaire à partir de ce bitmap. Tous les pixels à l'extérieur du cercle doivent être transparents. Comment puis-je faire ceci?

entrez la description de l'image ici

Réponses:


216

Après un long brainstorming, j'ai trouvé la solution

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
Vous pouvez également le faire en découpant le bitmap par rapport à un chemin de découpage circulaire. Vous pouvez soit le faire à chaque fois que vous dessinez le bitmap, ce qui signifie que vous ne créez jamais réellement de bitmap avec des pixels transparents, soit vous pouvez dessiner le bitmap découpé dans un tampon qui a été effacé au préalable en transparent. Je pense que l'un ou l'autre serait un peu plus rapide et plus simple que cela.
Gene

1
Merci. votre code fonctionne de manière spectaculaire. Maintenant, je peux également recadrer en utilisant path (Polygon).
DearDhruv

2
Cette méthode pourrait être créée staticet utilisée dans une classe d'utilité non instanciée de méthodes statiques similaires.
Matt Logan

1
Ne devriez-vous pas utiliser un minimum de hauteur et de largeur divisés par 2 comme rayon ici? canvas.drawCircle(bitmap.getWidth() / 2, bitmap.getHeight() / 2, bitmap.getWidth() / 2, paint);
Varvara Kalinina

3
Il y a 3 points clés: 1) Créez une image bitmap vide et dessinez un cercle. 2) Définissez xfermode sur SRC_IN. 3) Dessinez le vrai bitmap aux limites de ce canevas. Ainsi, la couleur de la peinture et les autres dessins sur toile ne sont d'aucune utilité.
Lym Zoy

44

pour générer un cercle à partir de rectangles

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
Je suggérerais d'utiliser deux vues d'image dans une mise en page avec la vue d'image supérieure avec un cercle transparent découpé.
diesel

36

Vous pouvez rendre votre imageview circulaire en utilisant RoundedBitmapDrawable

voici le code pour obtenir une vue arrondie:

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

Merci d'avoir montré cette option de bibliothèque de support v4. Je ne pense pas que je l'aurais découvert autrement.
CFJ90210

8
et l'utilisation de setCircular (true) au lieu de setCornerRadius (50.0f) fait du dessin un cercle. note: l'image doit être carrée ou le rapport hauteur / largeur est défectueux ...
Marco Schmitz

31

@Gene a fait un commentaire sur la réponse ci-dessus suggérant de l'utiliser clipPathcomme option pour recadrer une image en cercle.

Ce qui suit est une mise en œuvre propre de ceci:

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

Cela pourrait être ajouté à une classe utilitaire.


4
J'étais sur le point de publier un code très similaire. Le problème est que selon developer.android.com/guide/topics/graphics/hardware-accel.html , clipPath n'est pas pris en charge avec l'accélération matérielle. J'ai rencontré ce problème dans une application et je me suis demandé ce qui se passait. Un matériel plus récent semble toutefois résoudre ce problème (comme les tablettes Google). Un nettoyage supplémentaire possible de votre code: vous n'avez pas besoin de la conversion rect-à-rect lors du dessin du bitmap. Vous pouvez simplement dire c.drawBitmap(b, 0, 0, null);, qui utilise la transformation d'identité par défaut.
Gene

comment avez-vous utilisé clipPath tout en utilisant l'accélération matérielle?
speedynomads

J'utilisais à l'origine cette solution avant mais la sortie avait des bords irréguliers. La solution de @Altaf fonctionne mieux
dirkoneill

Fonctionne très bien pour recadrer les images utilisées dans la notification sur la barre d'état
Damian Petla

14

Je pense que cette solution fonctionne mieux avec tout type de rectangle, changez la taille des pixels si vous voulez une image petite 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;
    }

11

Cela peut également être fait facilement en XML sans recadrer le bitmap réel.Il vous suffit de créer un masque d'image circulaire et de le placer sur votre image réelle. Voici le morceau de code que j'ai utilisé:

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 (Ignorez "android: scaleType =" fitXY "" si vous n'en avez pas besoin)

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

entrez la description de l'image ici

Vue d'image OutPut:

J'espère que cela pourrait être utile pour quelqu'un !!! :)


On dirait que cela ne fonctionne que si l'image a un fond transparent plus petit qu'un cercle ...
meeDamian

4
Vous venez de mettre un ImageView sur un autre, ce n'est pas un masque :)
Climbatize

@Ash bien sûr, vous avez raison :) C'est juste que de cette façon vous ne "recadrez" pas vraiment l'image originale, comme demandé par l'affiche originale;)
Climbatize

8

vous pouvez utiliser ce code, cela fonctionnera

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

Il fonctionne bien sur l'API Android <28, mais plante avec java.lang.IllegalArgumentException: le rendu logiciel ne prend pas en charge les bitmaps matériels sur Android 28
Lucian Iacob

7

vous pouvez utiliser ce code, cela fonctionnera

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

Je recommande d'ajouter bitmap.recycle()si vous n'en avez plus besoin, cela évitera l'erreur OutOfMemory.


3

Voici la variante de Kotlin utilisant la méthode d'extension

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

Pour les paons qui veulent le centre du rectangle (moi), ajoutez ceci avant de couper:

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

Centre de cultures Android de Bitmap


2

Basé sur la réponse [Jachumbelechao Unto Mantekilla], ce code fonctionne comme un charme pour les personnes à la recherche d'une solution 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
}

Vous pouvez le convertir en une fonction d'extension
greenspand

quelque chose comme amusant Bitmap.getCircleCroppedBitmap (): Bitmap {} et utilisez ceci à la place de originalBitmap
greenspand

alors vous pouvez l'utiliser comme ceci: img_user_photo.setImageBitmap (photo.getCircleCroppedBitmap ())
greenspand

où photo est l'objet bitmap étendu avec la fonction
greenspand

1

Maintenant, bonne réponse:

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
Que sont cx et cy?
Kartik Chugh

1
@ K_7, c'est le centre d'un cercle
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
        }

appelez-le par ce code

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

1

Je pense que la solution la plus simple est de créer un BitmapShader de votre Bitmap, de le passer à votre objet de peinture, puis d'appeler simplement quelque chose comme canvas.drawCircle (cx, cy, radius, paint);

par exemple

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

C'est ainsi que https://github.com/hdodenhof/CircleImageView l'a également fait, vous pouvez lire le code source ici: 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

Je ne suis pas sûr que ce soit une question de programmation mais ...

La solution la plus simple serait de rendre la zone extérieure transparente dans le bitmap source. Sinon, vous devrez calculer quels pixels sont en dehors du cercle et définir l'alpha en conséquence (alpha = 0 pour une transparence totale).


pour être honnête, j'ai été votre chemin, cela semble fonctionner mais nous ne pouvons pas résoudre le problème des frontières irrégulières, n'est-ce pas?
VinceStyling

Le "jaggyness" de la frontière est résolu par le tramage et / ou l'anticrénelage. Vous pouvez rechercher en ligne certains algorithmes pour accomplir quelque chose qui semble acceptable. Mais gardez à l'esprit que les pixels rectangulaires et les courbes auront toujours ces problèmes.
MandisaW
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.