Cómo recortar el área circular del mapa de bits en Android

Respuestas:

216

Después de una larga lluvia de ideas, he encontrado la solución.

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
fuente
1
También puede hacer esto recortando el mapa de bits contra un trazado de recorte circular. Puede hacer esto cada vez que dibuja el mapa de bits, lo que significa que nunca creará un mapa de bits con píxeles transparentes, o puede dibujar el mapa de bits recortado en un búfer que se ha borrado a transparente de antemano. Creo que cualquiera de los dos sería un poco más rápido y sencillo que esto.
Gene
1
Gracias. tu código funciona espectacular. Ahora también puedo recortar usando ruta (Polígono).
DearDhruv
2
Este método podría crearse staticy usarse en una clase de utilidad no instanciada de métodos estáticos similares.
Matt Logan
1
¿No debería usar un mínimo de altura y ancho dividido por 2 como radio aquí? canvas.drawCircle(bitmap.getWidth() / 2, bitmap.getHeight() / 2, bitmap.getWidth() / 2, paint);
Varvara Kalinina
3
Hay 3 puntos clave: 1) Crea un mapa de bits vacío y dibuja un círculo. 2) Establezca xfermode en SRC_IN. 3) Dibuja el mapa de bits real hasta los límites del lienzo. Por lo tanto, el color de la pintura y otros dibujos del lienzo no sirven de nada.
Lym Zoy
44

para generar círculo a partir de rectá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
fuente
1
Sugeriría usar dos vistas de imagen en un diseño de marco con la vista de imagen superior con un círculo transparente recortado.
diesel
36

Puede hacer que su vista de imagen sea circular usando RoundedBitmapDrawable

aquí está el código para lograr una vista de imagen redondeada:

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
fuente
Gracias por mostrar esta opción de biblioteca de soporte v4. No creo que me hubiera enterado de otra manera.
CFJ90210
8
y el uso de setCircular (true) en lugar de setCornerRadius (50.0f) hace que se pueda dibujar un círculo. nota: la imagen debe ser cuadrada o la relación de aspecto es defectuosa ...
Marco Schmitz
31

@Gene hizo un comentario sobre la respuesta anterior que sugería usar clipPath como una opción para recortar una imagen como un círculo.

La siguiente es una implementación limpia de esto:

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

Esto podría agregarse a una clase de utilidad.

HGPB
fuente
4
Estaba a punto de publicar un código muy similar. Según developer.android.com/guide/topics/graphics/hardware-accel.html , el problema es que clipPath no es compatible con la aceleración de hardware. De hecho, encontré ese problema en una aplicación y me pregunté qué estaba pasando. Sin embargo, el hardware más nuevo parece solucionar este problema (como las tabletas de Google). Una posible limpieza adicional de su código: no necesita la conversión rect-to-rect al dibujar el mapa de bits. Simplemente puede decir c.drawBitmap(b, 0, 0, null);, que usa la transformación de identidad predeterminada.
Gene
¿Cómo se las arregló para usar clipPath mientras usaba la aceleración de hardware?
speedynomads
Originalmente estaba usando esta solución antes, pero la salida tenía bordes irregulares. La solución de @Altaf funciona mejor
dirkoneill
Funciona muy bien para recortar imágenes utilizadas en la notificación en la barra de estado
Damian Petla
14

Creo que esta solución funciona mejor con cualquier tipo de rectángulo, cambie el tamaño de píxel si desea una imagen pequeña 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;
    }
Jachumbelechao a Mantekilla
fuente
11

Esto también se puede hacer fácilmente en xml sin recortar el mapa de bits real. Solo necesita crear una máscara de imagen circular y colocarla sobre su imagen real. Aquí está el fragmento de código que utilicé:

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 "" si no lo necesita)

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

ingrese la descripción de la imagen aquí

Vista de imagen de salida:

Espero, ¡podría ser útil para alguien! :)

Ceniza
fuente
Parece que funciona sólo si la imagen tiene un fondo transparente que es más pequeño que el círculo ...
meeDamian
4
Sólo hay que poner un ImageView encima de otro, esto no es una máscara :)
Climbatize
@Ash seguro, tienes razón :) Es solo que de esta manera realmente no "recortas" la imagen original, como lo pide el póster original;)
Climbatize
8

puedes usar este código, 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;
    }

fuente
Funciona bien en la API de Android <28, pero se bloquea con java.lang.IllegalArgumentException: el procesamiento de software no admite mapas de bits de hardware en Android 28
Lucian Iacob
7

puedes usar este código, 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
fuente
3

Recomiendo agregar bitmap.recycle()si ya no lo necesita, evitará el error OutOfMemory.

badoualy
fuente
3

Aquí está la variante de Kotlin usando el método de extensión

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

Para las personas que quieren el centro del rectángulo (yo), agregue esto 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()
        );
    }
} 

Centro de recorte de Android de mapa de bits

jobbert
fuente
2

Basado en la respuesta de [Jachumbelechao Unto Mantekilla], este código funciona como un encanto para las personas que buscan una solución 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
fuente
Podría convertir esto en una función de extensión
greenpand
algo como divertido Bitmap.getCircleCroppedBitmap (): Bitmap {} y use esto en lugar de originalBitmap
greenspand
entonces podrías usarlo así: img_user_photo.setImageBitmap (photo.getCircleCroppedBitmap ())
greenspand
donde foto es el objeto de mapa de bits extendido con la función
greenspand
1

Ahora, respuesta correcta:

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;
}
Maestro
fuente
1
¿Qué son cx y cy?
Kartik Chugh
1
@ K_7, este es el centro de un círculo
Maestro
1

Función de Kotin

 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
        }

llámalo por este código

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

Creo que la solución más fácil es crear un BitmapShader de su Bitmap, pasarlo a su objeto de pintura y luego simplemente llamar a algo como canvas.drawCircle (cx, cy, radius, paint);

por ejemplo

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

Así es como https://github.com/hdodenhof/CircleImageView también lo ha hecho, puedes leer el código fuente aquí: https://github.com/hdodenhof/CircleImageView/blob/master/circleimageview/src/main/java /de/hdodenhof/circleimageview/CircleImageView.java

Martín Nowosad
fuente
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
fuente
-1

No estoy seguro de que sea una pregunta de programación, pero ...

La solución más sencilla sería hacer que el área exterior sea transparente en el mapa de bits de origen. De lo contrario, tendrá que calcular qué píxeles están fuera del círculo y configurar el alfa en consecuencia (alfa = 0 para una transparencia total).

MandisaW
fuente
para ser honesto, he sido a tu manera, parece que funciona pero no podemos resolver el problema de la frontera irregular, ¿no?
VinceStyling
La "irregularidad" de los bordes se aborda mediante difuminado y / o suavizado. Puede buscar en línea algunos algoritmos para lograr algo que parezca aceptable. Pero tenga en cuenta que los píxeles rectangulares y las curvas siempre tendrán estos problemas.
MandisaW