1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.bitmap.drawable;
18 
19 import android.content.res.Resources;
20 import android.graphics.Bitmap;
21 import android.graphics.BitmapShader;
22 import android.graphics.Canvas;
23 import android.graphics.Color;
24 import android.graphics.ColorFilter;
25 import android.graphics.Matrix;
26 import android.graphics.Paint;
27 import android.graphics.Paint.Style;
28 import android.graphics.Rect;
29 import android.graphics.Shader.TileMode;
30 import android.graphics.drawable.BitmapDrawable;
31 
32 import com.android.bitmap.BitmapCache;
33 
34 /**
35  * Custom BasicBitmapDrawable implementation for circular images.
36  *
37  * This draws all bitmaps as a circle with an optional border stroke.
38  */
39 public class CircularBitmapDrawable extends ExtendedBitmapDrawable {
40     private final Paint mBitmapPaint = new Paint();
41     private final Paint mBorderPaint = new Paint();
42     private final Rect mRect = new Rect();
43     private final Matrix mMatrix = new Matrix();
44 
45     private float mBorderWidth;
46     private Bitmap mShaderBitmap;
47 
CircularBitmapDrawable(Resources res, BitmapCache cache, boolean limitDensity)48     public CircularBitmapDrawable(Resources res,
49             BitmapCache cache, boolean limitDensity) {
50         this(res, cache, limitDensity, null);
51     }
52 
CircularBitmapDrawable(Resources res, BitmapCache cache, boolean limitDensity, ExtendedOptions opts)53     public CircularBitmapDrawable(Resources res,
54             BitmapCache cache, boolean limitDensity, ExtendedOptions opts) {
55         super(res, cache, limitDensity, opts);
56 
57         mBitmapPaint.setAntiAlias(true);
58         mBitmapPaint.setFilterBitmap(true);
59         mBitmapPaint.setDither(true);
60 
61         mBorderPaint.setColor(Color.TRANSPARENT);
62         mBorderPaint.setStyle(Style.STROKE);
63         mBorderPaint.setStrokeWidth(mBorderWidth);
64         mBorderPaint.setAntiAlias(true);
65     }
66 
67     /**
68      * Set the border stroke width of this drawable.
69      */
setBorderWidth(final float borderWidth)70     public void setBorderWidth(final float borderWidth) {
71         final boolean changed = mBorderPaint.getStrokeWidth() != borderWidth;
72         mBorderPaint.setStrokeWidth(borderWidth);
73         mBorderWidth = borderWidth;
74 
75         if (changed) {
76             invalidateSelf();
77         }
78     }
79 
80     /**
81      * Set the border stroke color of this drawable. Set to {@link Color#TRANSPARENT} to disable.
82      */
setBorderColor(final int color)83     public void setBorderColor(final int color) {
84         final boolean changed = mBorderPaint.getColor() != color;
85         mBorderPaint.setColor(color);
86 
87         if (changed) {
88             invalidateSelf();
89         }
90     }
91 
92     @Override
onDrawBitmap(final Canvas canvas, final Rect src, final Rect dst)93     protected void onDrawBitmap(final Canvas canvas, final Rect src,
94             final Rect dst) {
95         onDrawCircularBitmap(getBitmap().bmp, canvas, src, dst, 1f);
96     }
97 
98     @Override
onDrawPlaceholderOrProgress(final Canvas canvas, final TileDrawable drawable)99     protected void onDrawPlaceholderOrProgress(final Canvas canvas,
100             final TileDrawable drawable) {
101         Rect bounds = getBounds();
102         if (drawable.getInnerDrawable() instanceof BitmapDrawable) {
103             BitmapDrawable placeholder =
104                 (BitmapDrawable) drawable.getInnerDrawable();
105             Bitmap bitmap = placeholder.getBitmap();
106             float alpha = placeholder.getPaint().getAlpha() / 255f;
107             mRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight());
108             onDrawCircularBitmap(bitmap, canvas, mRect, bounds, alpha);
109         } else {
110           super.onDrawPlaceholderOrProgress(canvas, drawable);
111         }
112 
113         // Then draw the border.
114         canvas.drawCircle(bounds.centerX(), bounds.centerY(),
115                 bounds.width() / 2f - mBorderWidth / 2, mBorderPaint);
116     }
117 
118     /**
119      * Call this method with a given bitmap to draw it onto the given canvas, masked by a circular
120      * BitmapShader.
121      */
onDrawCircularBitmap(final Bitmap bitmap, final Canvas canvas, final Rect src, final Rect dst)122     protected void onDrawCircularBitmap(final Bitmap bitmap, final Canvas canvas,
123             final Rect src, final Rect dst) {
124         onDrawCircularBitmap(bitmap, canvas, src, dst, 1f);
125     }
126 
127     /**
128      * Call this method with a given bitmap to draw it onto the given canvas, masked by a circular
129      * BitmapShader. The alpha parameter is the value from 0f to 1f to attenuate the alpha by.
130      */
onDrawCircularBitmap(final Bitmap bitmap, final Canvas canvas, final Rect src, final Rect dst, final float alpha)131     protected void onDrawCircularBitmap(final Bitmap bitmap, final Canvas canvas,
132             final Rect src, final Rect dst, final float alpha) {
133         // Draw bitmap through shader first.
134         BitmapShader shader = (BitmapShader) mBitmapPaint.getShader();
135         if (shader == null || mShaderBitmap != bitmap) {
136           shader = new BitmapShader(bitmap, TileMode.CLAMP, TileMode.CLAMP);
137           mShaderBitmap = bitmap;
138         }
139 
140         mMatrix.reset();
141         // Fit bitmap to bounds.
142         float scale = Math.max((float) dst.width() / src.width(),
143                 (float) dst.height() / src.height());
144         mMatrix.postScale(scale, scale);
145         // Translate bitmap to dst bounds.
146         mMatrix.postTranslate(dst.left, dst.top);
147         shader.setLocalMatrix(mMatrix);
148         mBitmapPaint.setShader(shader);
149 
150         int oldAlpha = mBitmapPaint.getAlpha();
151         mBitmapPaint.setAlpha((int) (oldAlpha * alpha));
152         canvas.drawCircle(dst.centerX(), dst.centerY(), dst.width() / 2,
153                 mBitmapPaint);
154         mBitmapPaint.setAlpha(oldAlpha);
155 
156         // Then draw the border.
157         canvas.drawCircle(dst.centerX(), dst.centerY(),
158                 dst.width() / 2f - mBorderWidth / 2, mBorderPaint);
159     }
160 
161     @Override
setAlpha(int alpha)162     public void setAlpha(int alpha) {
163         super.setAlpha(alpha);
164 
165         final int old = mBitmapPaint.getAlpha();
166         mBitmapPaint.setAlpha(alpha);
167         if (alpha != old) {
168             invalidateSelf();
169         }
170     }
171 
172     @Override
setColorFilter(ColorFilter cf)173     public void setColorFilter(ColorFilter cf) {
174         super.setColorFilter(cf);
175         mPaint.setColorFilter(cf);
176     }
177 }
178