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