Android: Circular Drawable
Asked Answered
C

6

7

I made this Custom Drawable that should clip any Drawable in circle. But with my implementation, the drawable passed is being the output in the original form not in the circular form.

public class CircularDrawable extends Drawable {
    Paint mPaint,xfermodePaint;
    Drawable mDrawable;
    int[] vinylCenter = new int[2];
    int radius;
    Bitmap src;
    PorterDuffXfermode xfermode;
    Rect rect;
    Canvas testCanvas;
    public CircularDrawable(Drawable drawable) {
        mDrawable = drawable;
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(0xffffffff);
        mPaint.setStyle(Paint.Style.FILL);
        xfermodePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        xfermode=new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
        xfermodePaint.setXfermode(xfermode);
        testCanvas=new Canvas();
    }

    @Override
    public void setBounds(Rect bounds) {
        super.setBounds(bounds);
        mDrawable.setBounds(bounds);
    }

    @Override
    public void setBounds(int left, int top, int right, int bottom) {
        super.setBounds(left, top, right, bottom);
        mDrawable.setBounds(left, top, right, bottom);
    }

    @Override
    protected void onBoundsChange(Rect bounds) {
        super.onBoundsChange(bounds);
        vinylCenter[0] = bounds.width() / 2;
        vinylCenter[1] = bounds.height() / 2;
        radius = (bounds.right - bounds.left) / 2;
        src = Bitmap.createBitmap(bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888);
        testCanvas.setBitmap(src);
    }


    @Override
    public void draw(Canvas canvas) {
        canvas.save();
        canvas.drawARGB(0, 0, 0, 0);
        canvas.drawCircle(vinylCenter[0],vinylCenter[1],radius,mPaint);
        mDrawable.draw(testCanvas);
        canvas.drawBitmap(src,0f,0f,xfermodePaint);
    }



    @Override
    public void setAlpha(int alpha) {/*ignored*/}

    @Override
    public void setColorFilter(ColorFilter colorFilter) {/*ignored*/}

    @Override
    public int getOpacity() {
        /*ignored*/
        return 0;
    }
}

What is my mistake?

Also I am using a SquareImageview to display this drawable that just makes the view square in onMeasure. This question is not meant for Circular imageview.

Chappy answered 3/12, 2015 at 11:58 Comment(9)
see this as a referenceXavierxaviera
@Xavierxaviera but that is other implementation. I want to know what is my mistake in the Porter/Duff implementation.Chappy
if it is square it means that you are drawing the whole "DST" areaXavierxaviera
No I tried both the methods. You try this whole code. I tried both. What we draw with paint is "Source" and where it is drawn is "Destination", meaning the Image is source and the circle on the canvas is destination.Chappy
"""I want to know what is my mistake in the Porter/Duff implementation.""" i dont think so, if you are responding after 3 days...Xavierxaviera
I am actually having my semester exams and really busy in them as well. But I spend lot of hours resolving this issue.Chappy
ok so what have you done so far? did you try to run APiDemos? it has a porter-duff example there. did you try to switch off hardware acceleration?Xavierxaviera
Do you need circular ImageView?Schramm
@Chirag No. I am creating drawing something that needs cropping.Chappy
C
3

The canvas doesnt have alpha channel on by default when using Porter/Duff xfermode mode (As per my experience with my this in android). That is the reason the whole image(src) is being the output. Porter/Duff xfermode acts based on Alpha Channel.

In many implementation of Circular Drawable 2 Canvas are taken, out of which one is given RGB image bitmap and other is given only alpha channel bitmap with circular mask. Porter/Duff xfermodeis applied and the result is drawn on the main canvas.

My mistake is that I am not specifying the draw function's canvas that alpha channel is to be considered on using the layer on the canvas for xfermode.

The draw function will become

@Override
public void draw(Canvas canvas) {
    int sc = canvas.saveLayer(null, null,
                    Canvas.HAS_ALPHA_LAYER_SAVE_FLAG |
                    );
    canvas.drawCircle(vinylCenter[0],vinylCenter[1],radius,mPaint);
    mDrawable.draw(testCanvas);
    canvas.drawBitmap(src,0f,0f,xfermodePaint);
    canvas.restoreToCount(sc);
}

Saving the Layer to indicate alpha channel with the flag and then using the xfermode. Finally restoring it to its save count.

Chappy answered 6/12, 2015 at 16:36 Comment(0)
B
4

You already have optimized implementation for circular drawable in Support library v4:

Google reference

 /**
* A Drawable that wraps a bitmap and can be drawn with rounded corners. You can create a
* RoundedBitmapDrawable from a file path, an input stream, or from a
* {@link android.graphics.Bitmap} object.
* <p>
* Also see the {@link android.graphics.Bitmap} class, which handles the management and
* transformation of raw bitmap graphics, and should be used when drawing to a
* {@link android.graphics.Canvas}.
* </p>
*/
public abstract class RoundedBitmapDrawable extends Drawable ....
Byng answered 10/12, 2015 at 13:55 Comment(0)
C
3

The canvas doesnt have alpha channel on by default when using Porter/Duff xfermode mode (As per my experience with my this in android). That is the reason the whole image(src) is being the output. Porter/Duff xfermode acts based on Alpha Channel.

In many implementation of Circular Drawable 2 Canvas are taken, out of which one is given RGB image bitmap and other is given only alpha channel bitmap with circular mask. Porter/Duff xfermodeis applied and the result is drawn on the main canvas.

My mistake is that I am not specifying the draw function's canvas that alpha channel is to be considered on using the layer on the canvas for xfermode.

The draw function will become

@Override
public void draw(Canvas canvas) {
    int sc = canvas.saveLayer(null, null,
                    Canvas.HAS_ALPHA_LAYER_SAVE_FLAG |
                    );
    canvas.drawCircle(vinylCenter[0],vinylCenter[1],radius,mPaint);
    mDrawable.draw(testCanvas);
    canvas.drawBitmap(src,0f,0f,xfermodePaint);
    canvas.restoreToCount(sc);
}

Saving the Layer to indicate alpha channel with the flag and then using the xfermode. Finally restoring it to its save count.

Chappy answered 6/12, 2015 at 16:36 Comment(0)
W
3

So basically I use this method to get rounded corner images

public static Bitmap getRoundedCornerBitmap(Bitmap bitmap, int pixels) {
    Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.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());
    final RectF rectF = new RectF(rect);
    final float roundPx = pixels;

    paint.setAntiAlias(true);
    canvas.drawARGB(0, 0, 0, 0);
    paint.setColor(color);
    canvas.drawRoundRect(rectF, roundPx, roundPx, paint);

    paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
    canvas.drawBitmap(bitmap, rect, rect, paint);

    return output;

but like you pointed out sometimes it does not work, well it does work, but what happens most of the time is your imageview might have its scaletype set to centerCrop and other types, what I do is I first compress my image height to the same height and width as my imageview so that the entire image will fit in the imageview and no scaling will take place or I just calculate how much of the corner height to make rounded using this function based on the height

   private int calculatePercentage(int percentage, int target)
{
    int k = (int)(target*(percentage/100.0f));
    return k;

}

so I use my code like: new Bitmap(getRoundedCornerBitmap(bmp, calculatePercentage(5, bmp.getHeight()))); where 5 is 5 of the image height this gives me same curves for all images be it some images are bigger than others `

Willianwillie answered 8/12, 2015 at 1:54 Comment(5)
Another workaround not asked. The assumption you made about scaletype is wrong. Default scaletype is fitCenter.Chappy
@Paritosh Tonk you could take my help or not either way mine works perfectlyWillianwillie
I appreciate your help. The think is not about working. Its about concept and why this happens.Chappy
when am programming I avoid using concepts that will leave me depressed, unless you are a google developer you do not want to spend a week trying to figure out why something does not work when there are other means that work, what you are doing is called re-inventing the wheel there are many tutorials out there that outline how to get a rounded ImageWillianwillie
I am trying to reinvent the wheel becouse Rounded bitmap is not what I want. I am cropping different things and also animating. The code for Rounded Bitmap and rounded Drawable is available but for the purpose of animating this drawing I dont want to put heavy load on memory by creating bitmaps in every frame. And if the thing is about programming there is nothing that is possible without something called a concept. People dont hire and pay you because you can program. I hope you understand what I am trying to say here.Chappy
R
3
For rounded imageview you can use this code 

    public class RoundedImageView extends ImageView {

    public RoundedImageView(Context context) {
        super(context);
        // TODO Auto-generated constructor stub
    }

    public RoundedImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public RoundedImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    protected void onDraw(Canvas canvas) {

        Drawable drawable = getDrawable();

        if (drawable == null) {
            return;
        }

        if (getWidth() == 0 || getHeight() == 0) {
            return;
        }
        Bitmap b = ((BitmapDrawable) drawable).getBitmap();
        Bitmap bitmap = b.copy(Bitmap.Config.ARGB_8888, true);

        int w = getWidth();//, h = getHeight();

        Bitmap roundBitmap = getCroppedBitmap(bitmap, w);
        canvas.drawBitmap(roundBitmap, 0, 0, null);

    }

    public static Bitmap getCroppedBitmap(Bitmap bmp, int radius)
    {
        Bitmap sbmp;
        if (bmp.getWidth() != radius || bmp.getHeight() != radius)
            sbmp = Bitmap.createScaledBitmap(bmp, radius, radius, false);
        else
            sbmp = bmp;
        Bitmap output = Bitmap.createBitmap(sbmp.getWidth(), sbmp.getHeight(),Config.ARGB_8888);
        Canvas canvas = new Canvas(output);

//      final int color = 0xffa19774;
        final Paint paint = new Paint();
        final Rect rect = new Rect(0, 0, sbmp.getWidth(), sbmp.getHeight());

        paint.setAntiAlias(true);
        paint.setFilterBitmap(true);
        paint.setDither(true);
        canvas.drawARGB(0, 0, 0, 0);
//      paint.setColor(Color.parseColor("#BAB399"));
        paint.setColor(Color.parseColor("#FF0000"));
        canvas.drawCircle(sbmp.getWidth() / 2 + 0.7f, sbmp.getHeight() / 2 + 0.7f, sbmp.getWidth() / 2 + 0.1f, paint);
        paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
        canvas.drawBitmap(sbmp, rect, rect, paint);

        return output;
    }
}
Retuse answered 9/12, 2015 at 11:0 Comment(0)
N
3
For circular imageview use the below code

public class RoundedImageView extends ImageView {

    public RoundedImageView(Context context) {
        super(context);
    }

    public RoundedImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public RoundedImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        BitmapDrawable drawable = (BitmapDrawable) getDrawable();

        if (drawable == null) {
            return;
        }

        if (getWidth() == 0 || getHeight() == 0) {
            return;
        }

        Bitmap fullSizeBitmap = drawable.getBitmap();

        int scaledWidth = getMeasuredWidth();
        int scaledHeight = getMeasuredHeight();

        /*
         * scaledWidth = scaledWidth/2; scaledHeight = scaledHeight/2;
         */

        Bitmap mScaledBitmap;
        if (scaledWidth == fullSizeBitmap.getWidth()
                && scaledHeight == fullSizeBitmap.getHeight()) {
            mScaledBitmap = fullSizeBitmap;
        } else {
            mScaledBitmap = Bitmap.createScaledBitmap(fullSizeBitmap,
                    scaledWidth, scaledHeight, true /* filter */);
        }

        // Bitmap roundBitmap = getRoundedCornerBitmap(mScaledBitmap);

        // Bitmap roundBitmap = getRoundedCornerBitmap(getContext(),
        // mScaledBitmap, 10, scaledWidth, scaledHeight, false, false,
        // false, false);
        // canvas.drawBitmap(roundBitmap, 0, 0, null);

        Bitmap circleBitmap = getCircledBitmap(mScaledBitmap);

        canvas.drawBitmap(circleBitmap, 0, 0, null);

    }

    public Bitmap getRoundedCornerBitmap(Context context, Bitmap input,
            int pixels, int w, int h, boolean squareTL, boolean squareTR,
            boolean squareBL, boolean squareBR) {

        Bitmap output = Bitmap.createBitmap(w, h, Config.ARGB_8888);
        Canvas canvas = new Canvas(output);
        final float densityMultiplier = context.getResources()
                .getDisplayMetrics().density;

        final int color = 0xff424242;

        final Paint paint = new Paint();
        final Rect rect = new Rect(0, 0, w, h);
        final RectF rectF = new RectF(rect);

        // make sure that our rounded corner is scaled appropriately
        final float roundPx = pixels * densityMultiplier;

        paint.setAntiAlias(true);
        canvas.drawARGB(0, 0, 0, 0);
        paint.setColor(color);
        canvas.drawRoundRect(rectF, roundPx, roundPx, paint);

        // draw rectangles over the corners we want to be square
        if (squareTL) {
            canvas.drawRect(0, 0, w / 2, h / 2, paint);
        }
        if (squareTR) {
            canvas.drawRect(w / 2, 0, w, h / 2, paint);
        }
        if (squareBL) {
            canvas.drawRect(0, h / 2, w / 2, h, paint);
        }
        if (squareBR) {
            canvas.drawRect(w / 2, h / 2, w, h, paint);
        }

        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        canvas.drawBitmap(input, 0, 0, paint);

        return output;
    }

    Bitmap getCircledBitmap(Bitmap bitmap) {

        Bitmap result = Bitmap.createBitmap(bitmap.getWidth(),
                bitmap.getHeight(), Bitmap.Config.ARGB_8888);

        Canvas canvas = new Canvas(result);

        int color = Color.BLUE;
        Paint paint = new Paint();
        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.getHeight() / 2, paint);

        paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
        canvas.drawBitmap(bitmap, rect, rect, paint);

        return result;
    }

}
Niobic answered 11/12, 2015 at 5:7 Comment(0)
E
0

Try disabling hardware acceleration if you are using PorterDuffXfermode:

In manifest for whole activity:

<activity android:hardwareAccelerated="false" />

Or on a specific view on which you are using xfermode:

myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
Enunciate answered 13/12, 2015 at 15:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.