• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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 package androidx.leanback.graphics;
17 
18 import android.graphics.Bitmap;
19 import android.graphics.Canvas;
20 import android.graphics.ColorFilter;
21 import android.graphics.Paint;
22 import android.graphics.PixelFormat;
23 import android.graphics.Rect;
24 import android.graphics.drawable.Drawable;
25 import android.os.Build;
26 import android.util.IntProperty;
27 import android.util.Property;
28 
29 import androidx.annotation.NonNull;
30 import androidx.annotation.RequiresApi;
31 
32 /**
33  * Subclass of {@link Drawable} that can be used to draw a bitmap into a region. Bitmap
34  * will be scaled to fit the full width of the region and will be aligned to the top left corner.
35  * Any region outside the bounds will be clipped during {@link #draw(Canvas)} call. Top
36  * position of the bitmap can be controlled by {@link #setVerticalOffset(int)} call or
37  * {@link #PROPERTY_VERTICAL_OFFSET}.
38  */
39 public class FitWidthBitmapDrawable extends Drawable {
40 
41     static class BitmapState extends Drawable.ConstantState {
42         Paint mPaint;
43         Bitmap mBitmap;
44         Rect mSource;
45         final Rect mDefaultSource = new Rect();
46         int mOffset;
47 
BitmapState()48         BitmapState() {
49             mPaint = new Paint();
50         }
51 
BitmapState(BitmapState other)52         BitmapState(BitmapState other) {
53             mBitmap = other.mBitmap;
54             mPaint = new Paint(other.mPaint);
55             mSource = other.mSource != null ? new Rect(other.mSource) : null;
56             mDefaultSource.set(other.mDefaultSource);
57             mOffset = other.mOffset;
58         }
59 
60         @NonNull
61         @Override
newDrawable()62         public Drawable newDrawable() {
63             return new FitWidthBitmapDrawable(this);
64         }
65 
66         @Override
getChangingConfigurations()67         public int getChangingConfigurations() {
68             return 0;
69         }
70     }
71 
72     final Rect mDest = new Rect();
73     BitmapState mBitmapState;
74     boolean mMutated = false;
75 
FitWidthBitmapDrawable()76     public FitWidthBitmapDrawable() {
77         mBitmapState = new BitmapState();
78     }
79 
FitWidthBitmapDrawable(BitmapState state)80     FitWidthBitmapDrawable(BitmapState state) {
81         mBitmapState = state;
82     }
83 
84     @Override
getConstantState()85     public ConstantState getConstantState() {
86         return mBitmapState;
87     }
88 
89     @Override
mutate()90     public Drawable mutate() {
91         if (!mMutated && super.mutate() == this) {
92             mBitmapState = new BitmapState(mBitmapState);
93             mMutated = true;
94         }
95         return this;
96     }
97 
98     /**
99      * Sets the bitmap.
100      */
setBitmap(Bitmap bitmap)101     public void setBitmap(Bitmap bitmap) {
102         mBitmapState.mBitmap = bitmap;
103         if (bitmap != null) {
104             mBitmapState.mDefaultSource.set(0, 0, bitmap.getWidth(), bitmap.getHeight());
105         } else {
106             mBitmapState.mDefaultSource.set(0, 0, 0, 0);
107         }
108         mBitmapState.mSource = null;
109     }
110 
111     /**
112      * Returns the bitmap.
113      */
getBitmap()114     public Bitmap getBitmap() {
115         return mBitmapState.mBitmap;
116     }
117 
118     /**
119      * Sets the {@link Rect} used for extracting the bitmap.
120      */
setSource(Rect source)121     public void setSource(Rect source) {
122         mBitmapState.mSource = source;
123     }
124 
125     /**
126      * Returns the {@link Rect} used for extracting the bitmap.
127      */
getSource()128     public Rect getSource() {
129         return mBitmapState.mSource;
130     }
131 
132     /**
133      * Sets the vertical offset which will be used for drawing the bitmap. The bitmap drawing
134      * will start the provided vertical offset.
135      * @see #PROPERTY_VERTICAL_OFFSET
136      */
setVerticalOffset(int offset)137     public void setVerticalOffset(int offset) {
138         mBitmapState.mOffset = offset;
139         invalidateSelf();
140     }
141 
142     /**
143      * Returns the current vertical offset.
144      * @see #PROPERTY_VERTICAL_OFFSET
145      */
getVerticalOffset()146     public int getVerticalOffset() {
147         return mBitmapState.mOffset;
148     }
149 
150     @Override
draw(Canvas canvas)151     public void draw(Canvas canvas) {
152         if (mBitmapState.mBitmap != null) {
153             Rect bounds = getBounds();
154             mDest.left = 0;
155             mDest.top = mBitmapState.mOffset;
156             mDest.right = bounds.width();
157 
158             Rect source = validateSource();
159             float scale = (float) bounds.width() / source.width();
160             mDest.bottom = mDest.top + (int) (source.height() * scale);
161             int i = canvas.save();
162             canvas.clipRect(bounds);
163             canvas.drawBitmap(mBitmapState.mBitmap, source, mDest, mBitmapState.mPaint);
164             canvas.restoreToCount(i);
165         }
166     }
167 
168     @Override
setAlpha(int alpha)169     public void setAlpha(int alpha) {
170         final int oldAlpha = mBitmapState.mPaint.getAlpha();
171         if (alpha != oldAlpha) {
172             mBitmapState.mPaint.setAlpha(alpha);
173             invalidateSelf();
174         }
175     }
176 
177     /**
178      * @return Alpha value between 0(inclusive) and 255(inclusive)
179      */
180     @Override
getAlpha()181     public int getAlpha() {
182         return mBitmapState.mPaint.getAlpha();
183     }
184 
185     @Override
setColorFilter(ColorFilter colorFilter)186     public void setColorFilter(ColorFilter colorFilter) {
187         mBitmapState.mPaint.setColorFilter(colorFilter);
188         invalidateSelf();
189     }
190 
191     @Override
getOpacity()192     public int getOpacity() {
193         final Bitmap bitmap = mBitmapState.mBitmap;
194         return (bitmap == null || bitmap.hasAlpha() || mBitmapState.mPaint.getAlpha() < 255)
195                 ? PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
196     }
197 
validateSource()198     private Rect validateSource() {
199         if (mBitmapState.mSource == null) {
200             return mBitmapState.mDefaultSource;
201         } else {
202             return mBitmapState.mSource;
203         }
204     }
205 
206     /**
207      * Property for {@link #setVerticalOffset(int)} and {@link #getVerticalOffset()}.
208      */
209     public static final Property<FitWidthBitmapDrawable, Integer> PROPERTY_VERTICAL_OFFSET;
210 
211     static {
212         if (Build.VERSION.SDK_INT >= 24) {
213             // use IntProperty
214             PROPERTY_VERTICAL_OFFSET = getVerticalOffsetIntProperty();
215         } else {
216             // use Property
217             PROPERTY_VERTICAL_OFFSET = new Property<FitWidthBitmapDrawable, Integer>(Integer.class,
218                     "verticalOffset") {
219                 @Override
220                 public void set(FitWidthBitmapDrawable object, Integer value) {
221                     object.setVerticalOffset(value);
222                 }
223 
224                 @Override
225                 public Integer get(FitWidthBitmapDrawable object) {
226                     return object.getVerticalOffset();
227                 }
228             };
229         }
230     }
231 
232     @RequiresApi(24)
getVerticalOffsetIntProperty()233     static IntProperty<FitWidthBitmapDrawable> getVerticalOffsetIntProperty() {
234         return new IntProperty<FitWidthBitmapDrawable>("verticalOffset") {
235             @Override
236             public void setValue(FitWidthBitmapDrawable fitWidthBitmapDrawable, int value) {
237                 fitWidthBitmapDrawable.setVerticalOffset(value);
238             }
239 
240             @Override
241             public Integer get(FitWidthBitmapDrawable fitWidthBitmapDrawable) {
242                 return fitWidthBitmapDrawable.getVerticalOffset();
243             }
244         };
245     }
246 }
247