CircularBitmapDrawable.java revision df01966f999ddcc69b3e479c9efbd733ad86bc84
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
17package com.android.bitmap.drawable;
18
19import android.content.res.Resources;
20import android.graphics.Bitmap;
21import android.graphics.BitmapShader;
22import android.graphics.Canvas;
23import android.graphics.Color;
24import android.graphics.ColorFilter;
25import android.graphics.Matrix;
26import android.graphics.Paint;
27import android.graphics.Paint.Style;
28import android.graphics.Rect;
29import android.graphics.Shader.TileMode;
30import android.graphics.drawable.BitmapDrawable;
31
32import 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 */
39public class CircularBitmapDrawable extends ExtendedBitmapDrawable {
40    private static Matrix sMatrix = new Matrix();
41
42    private final Paint mBitmapPaint = new Paint();
43    private final Paint mBorderPaint = new Paint();
44
45    private float mBorderWidth;
46    private Bitmap mShaderBitmap;
47
48    public CircularBitmapDrawable(Resources res,
49            BitmapCache cache, boolean limitDensity) {
50        this(res, cache, limitDensity, null);
51    }
52
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     */
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     */
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
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
99    protected void onDrawPlaceholderOrProgress(final Canvas canvas,
100            final TileDrawable drawable) {
101        BitmapDrawable placeholder = (BitmapDrawable) drawable.getInnerDrawable();
102        Bitmap bitmap = placeholder.getBitmap();
103        float alpha = placeholder.getPaint().getAlpha() / 255f;
104        sRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight());
105        onDrawCircularBitmap(bitmap, canvas, sRect, getBounds(), alpha);
106    }
107
108    /**
109     * Call this method with a given bitmap to draw it onto the given canvas, masked by a circular
110     * BitmapShader.
111     */
112    protected void onDrawCircularBitmap(final Bitmap bitmap, final Canvas canvas,
113            final Rect src, final Rect dst) {
114        onDrawCircularBitmap(bitmap, canvas, src, dst, 1f);
115    }
116
117    /**
118     * Call this method with a given bitmap to draw it onto the given canvas, masked by a circular
119     * BitmapShader. The alpha parameter is the value from 0f to 1f to attenuate the alpha by.
120     */
121    protected void onDrawCircularBitmap(final Bitmap bitmap, final Canvas canvas,
122            final Rect src, final Rect dst, final float alpha) {
123        // Draw bitmap through shader first.
124        BitmapShader shader = (BitmapShader) mBitmapPaint.getShader();
125        if (shader == null || mShaderBitmap != bitmap) {
126          shader = new BitmapShader(bitmap, TileMode.CLAMP, TileMode.CLAMP);
127          mShaderBitmap = bitmap;
128          mBitmapPaint.setShader(shader);
129        }
130
131        sMatrix.reset();
132        // Fit bitmap to bounds.
133        float scale = Math.max((float) dst.width() / src.width(),
134                (float) dst.height() / src.height());
135        sMatrix.postScale(scale, scale);
136        // Translate bitmap to dst bounds.
137        sMatrix.postTranslate(dst.left, dst.top);
138        shader.setLocalMatrix(sMatrix);
139
140        int oldAlpha = mBitmapPaint.getAlpha();
141        mBitmapPaint.setAlpha((int) (oldAlpha * alpha));
142        canvas.drawCircle(dst.centerX(), dst.centerY(), dst.width() / 2,
143                mBitmapPaint);
144        mBitmapPaint.setAlpha(oldAlpha);
145
146        // Then draw the border.
147        canvas.drawCircle(dst.centerX(), dst.centerY(),
148                dst.width() / 2f - mBorderWidth / 2, mBorderPaint);
149    }
150
151    @Override
152    public void setAlpha(int alpha) {
153        super.setAlpha(alpha);
154
155        final int old = mBitmapPaint.getAlpha();
156        mBitmapPaint.setAlpha(alpha);
157        if (alpha != old) {
158            invalidateSelf();
159        }
160    }
161
162    @Override
163    public void setColorFilter(ColorFilter cf) {
164        super.setColorFilter(cf);
165        mPaint.setColorFilter(cf);
166    }
167}
168