CircularBitmapDrawable.java revision 8dd5cac690e42383d6bfc20af04eb781abd14f54
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
47    public CircularBitmapDrawable(Resources res,
48            BitmapCache cache, boolean limitDensity) {
49        this(res, cache, limitDensity, null);
50    }
51
52    public CircularBitmapDrawable(Resources res,
53            BitmapCache cache, boolean limitDensity, ExtendedOptions opts) {
54        super(res, cache, limitDensity, opts);
55
56        mBitmapPaint.setAntiAlias(true);
57        mBitmapPaint.setFilterBitmap(true);
58        mBitmapPaint.setDither(true);
59
60        mBorderPaint.setColor(Color.TRANSPARENT);
61        mBorderPaint.setStyle(Style.STROKE);
62        mBorderPaint.setStrokeWidth(mBorderWidth);
63        mBorderPaint.setAntiAlias(true);
64    }
65
66    /**
67     * Set the border stroke width of this drawable.
68     */
69    public void setBorderWidth(final float borderWidth) {
70        final boolean changed = mBorderPaint.getStrokeWidth() != borderWidth;
71        mBorderPaint.setStrokeWidth(borderWidth);
72        mBorderWidth = borderWidth;
73
74        if (changed) {
75            invalidateSelf();
76        }
77    }
78
79    /**
80     * Set the border stroke color of this drawable. Set to {@link Color#TRANSPARENT} to disable.
81     */
82    public void setBorderColor(final int color) {
83        final boolean changed = mBorderPaint.getColor() != color;
84        mBorderPaint.setColor(color);
85
86        if (changed) {
87            invalidateSelf();
88        }
89    }
90
91    @Override
92    protected void onDrawBitmap(final Canvas canvas, final Rect src,
93            final Rect dst) {
94        onDrawCircularBitmap(getBitmap().bmp, canvas, src, dst, 1f);
95    }
96
97    @Override
98    protected void onDrawPlaceholderOrProgress(final Canvas canvas,
99            final TileDrawable drawable) {
100        BitmapDrawable placeholder = (BitmapDrawable) drawable.getInnerDrawable();
101        Bitmap bitmap = placeholder.getBitmap();
102        float alpha = placeholder.getPaint().getAlpha() / 255f;
103        sRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight());
104        onDrawCircularBitmap(bitmap, canvas, sRect, getBounds(), alpha);
105    }
106
107    /**
108     * Call this method with a given bitmap to draw it onto the given canvas, masked by a circular
109     * BitmapShader.
110     */
111    protected void onDrawCircularBitmap(final Bitmap bitmap, final Canvas canvas,
112            final Rect src, final Rect dst) {
113        onDrawCircularBitmap(bitmap, canvas, src, dst, 1f);
114    }
115
116    /**
117     * Call this method with a given bitmap to draw it onto the given canvas, masked by a circular
118     * BitmapShader. The alpha parameter is the value from 0f to 1f to attenuate the alpha by.
119     */
120    protected void onDrawCircularBitmap(final Bitmap bitmap, final Canvas canvas,
121            final Rect src, final Rect dst, final float alpha) {
122        // Draw bitmap through shader first.
123        BitmapShader shader = new BitmapShader(bitmap, TileMode.CLAMP,
124                TileMode.CLAMP);
125        sMatrix.reset();
126
127        // Fit bitmap to bounds.
128        float scale = Math.max((float) dst.width() / src.width(),
129                (float) dst.height() / src.height());
130        sMatrix.postScale(scale, scale);
131
132        // Translate bitmap to dst bounds.
133        sMatrix.postTranslate(dst.left, dst.top);
134
135        shader.setLocalMatrix(sMatrix);
136        mBitmapPaint.setShader(shader);
137        int oldAlpha = mBitmapPaint.getAlpha();
138        mBitmapPaint.setAlpha((int) (oldAlpha * alpha));
139        canvas.drawCircle(dst.centerX(), dst.centerY(), dst.width() / 2,
140                mBitmapPaint);
141        mBitmapPaint.setAlpha(oldAlpha);
142
143        // Then draw the border.
144        canvas.drawCircle(dst.centerX(), dst.centerY(),
145                dst.width() / 2f - mBorderWidth / 2, mBorderPaint);
146    }
147
148    @Override
149    public void setAlpha(int alpha) {
150        super.setAlpha(alpha);
151
152        final int old = mBitmapPaint.getAlpha();
153        mBitmapPaint.setAlpha(alpha);
154        if (alpha != old) {
155            invalidateSelf();
156        }
157    }
158
159    @Override
160    public void setColorFilter(ColorFilter cf) {
161        super.setColorFilter(cf);
162        mPaint.setColorFilter(cf);
163    }
164}
165