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

Altaf
la source

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;
}
Altaf
la source
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;
}
diesel
la source
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);
sree
la source
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.

HGPB
la source
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;
    }
Jachumbelechao Unto Mantekilla
la source
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 !!! :)

Cendre
la source
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;
    }

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

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

Badoualy
la source
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
    }
}
Yuriy Seredyuk
la source
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

Jobbert
la source
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
}
Rodrigo Salinas
la source
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;
}
Maître
la source
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))
Samet ÖZTOPRAK
la source
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

Martin Nowosad
la source
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
la source
-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).

MandisaW
la source
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