GlifPatternDrawable.java revision 515c0b156d03f7ea348303c75e37ee4c16d90557
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 17package com.android.setupwizardlib; 18 19import android.annotation.SuppressLint; 20import android.content.Context; 21import android.content.res.TypedArray; 22import android.graphics.Bitmap; 23import android.graphics.Canvas; 24import android.graphics.Color; 25import android.graphics.ColorFilter; 26import android.graphics.ColorMatrixColorFilter; 27import android.graphics.Paint; 28import android.graphics.Path; 29import android.graphics.PorterDuff; 30import android.graphics.PorterDuffXfermode; 31import android.graphics.Rect; 32import android.graphics.drawable.Drawable; 33import android.os.Build; 34 35import com.android.setupwizardlib.annotations.VisibleForTesting; 36 37import java.lang.ref.SoftReference; 38 39/** 40 * This class draws the GLIF pattern used as the status bar background for phones and background for 41 * tablets in GLIF layout. 42 */ 43public class GlifPatternDrawable extends Drawable { 44 /* 45 * This class essentially implements a simple SVG in Java code, with some special handling of 46 * scaling when given different bounds. 47 */ 48 49 /* static section */ 50 51 @SuppressLint("InlinedApi") 52 private static final int[] ATTRS_PRIMARY_COLOR = new int[]{ android.R.attr.colorPrimary }; 53 54 private static final float VIEWBOX_HEIGHT = 768f; 55 private static final float VIEWBOX_WIDTH = 1366f; 56 // X coordinate of scale focus, as a fraction of of the width. (Range is 0 - 1) 57 private static final float SCALE_FOCUS_X = .146f; 58 // Y coordinate of scale focus, as a fraction of of the height. (Range is 0 - 1) 59 private static final float SCALE_FOCUS_Y = .228f; 60 61 // Alpha component of the color to be drawn, on top of the grayscale pattern. (Range is 0 - 1) 62 private static final float COLOR_ALPHA = .8f; 63 // Int version of COLOR_ALPHA. (Range is 0 - 255) 64 private static final int COLOR_ALPHA_INT = (int) (COLOR_ALPHA * 255); 65 66 // Cap the bitmap size, such that it won't hurt the performance too much 67 // and it won't crash due to a very large scale. 68 // The drawable will look blurry above this size. 69 // This is a multiplier applied on top of the viewbox size. 70 // Resulting max cache size = (1.5 x 1366, 1.5 x 768) = (2049, 1152) 71 private static final float MAX_CACHED_BITMAP_SCALE = 1.5f; 72 73 private static final int NUM_PATHS = 7; 74 75 private static SoftReference<Bitmap> sBitmapCache; 76 private static Path[] sPatternPaths; 77 private static int[] sPatternLightness; 78 79 public static GlifPatternDrawable getDefault(Context context) { 80 int colorPrimary = 0; 81 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 82 final TypedArray a = context.obtainStyledAttributes(ATTRS_PRIMARY_COLOR); 83 colorPrimary = a.getColor(0, Color.BLACK); 84 a.recycle(); 85 } 86 return new GlifPatternDrawable(colorPrimary); 87 } 88 89 @VisibleForTesting 90 public static void invalidatePattern() { 91 sBitmapCache = null; 92 } 93 94 /* non-static section */ 95 96 private int mColor; 97 private Paint mTempPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 98 private ColorFilter mColorFilter; 99 100 public GlifPatternDrawable(int color) { 101 setColor(color); 102 } 103 104 @Override 105 public void draw(Canvas canvas) { 106 final Rect bounds = getBounds(); 107 int drawableWidth = bounds.width(); 108 int drawableHeight = bounds.height(); 109 Bitmap bitmap = null; 110 if (sBitmapCache != null) { 111 bitmap = sBitmapCache.get(); 112 } 113 if (bitmap != null) { 114 final int bitmapWidth = bitmap.getWidth(); 115 final int bitmapHeight = bitmap.getHeight(); 116 // Invalidate the cache if this drawable is bigger and we can still create a bigger 117 // cache. 118 if (drawableWidth > bitmapWidth 119 && bitmapWidth < VIEWBOX_WIDTH * MAX_CACHED_BITMAP_SCALE) { 120 bitmap = null; 121 } else if (drawableHeight > bitmapHeight 122 && bitmapHeight < VIEWBOX_HEIGHT * MAX_CACHED_BITMAP_SCALE) { 123 bitmap = null; 124 } 125 } 126 127 if (bitmap == null) { 128 // Reset the paint so it can be used to draw the paths in renderOnCanvas 129 mTempPaint.reset(); 130 131 bitmap = createBitmapCache(drawableWidth, drawableHeight); 132 sBitmapCache = new SoftReference<>(bitmap); 133 134 // Reset the paint to so it can be used to draw the bitmap 135 mTempPaint.reset(); 136 } 137 138 canvas.save(); 139 canvas.clipRect(bounds); 140 141 scaleCanvasToBounds(canvas, bitmap, bounds); 142 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB 143 && canvas.isHardwareAccelerated()) { 144 mTempPaint.setColorFilter(mColorFilter); 145 canvas.drawBitmap(bitmap, 0, 0, mTempPaint); 146 } else { 147 // Software renderer doesn't work properly with ColorMatrix filter on ALPHA_8 bitmaps. 148 canvas.drawColor(Color.BLACK); 149 mTempPaint.setColor(Color.WHITE); 150 canvas.drawBitmap(bitmap, 0, 0, mTempPaint); 151 canvas.drawColor(mColor); 152 } 153 154 canvas.restore(); 155 } 156 157 @VisibleForTesting 158 public Bitmap createBitmapCache(int drawableWidth, int drawableHeight) { 159 float scaleX = drawableWidth / VIEWBOX_WIDTH; 160 float scaleY = drawableHeight / VIEWBOX_HEIGHT; 161 float scale = Math.max(scaleX, scaleY); 162 scale = Math.min(MAX_CACHED_BITMAP_SCALE, scale); 163 164 165 int scaledWidth = (int) (VIEWBOX_WIDTH * scale); 166 int scaledHeight = (int) (VIEWBOX_HEIGHT * scale); 167 168 // Use ALPHA_8 mask to save memory, since the pattern is grayscale only anyway. 169 Bitmap bitmap = Bitmap.createBitmap( 170 scaledWidth, 171 scaledHeight, 172 Bitmap.Config.ALPHA_8); 173 Canvas bitmapCanvas = new Canvas(bitmap); 174 renderOnCanvas(bitmapCanvas, scale); 175 return bitmap; 176 } 177 178 private void renderOnCanvas(Canvas canvas, float scale) { 179 canvas.save(); 180 canvas.scale(scale, scale); 181 182 mTempPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); 183 184 // Draw the pattern by creating the paths, adjusting the colors and drawing them. The path 185 // values are extracted from the SVG of the pattern file. 186 187 if (sPatternPaths == null) { 188 sPatternPaths = new Path[NUM_PATHS]; 189 // Lightness values of the pattern, range 0 - 255 190 sPatternLightness = new int[] { 10, 40, 51, 66, 91, 112, 130 }; 191 192 Path p = sPatternPaths[0] = new Path(); 193 p.moveTo(1029.4f, 357.5f); 194 p.lineTo(1366f, 759.1f); 195 p.lineTo(1366f, 0f); 196 p.lineTo(1137.7f, 0f); 197 p.close(); 198 199 p = sPatternPaths[1] = new Path(); 200 p.moveTo(1138.1f, 0f); 201 p.rLineTo(-144.8f, 768f); 202 p.rLineTo(372.7f, 0f); 203 p.rLineTo(0f, -524f); 204 p.cubicTo(1290.7f, 121.6f, 1219.2f, 41.1f, 1178.7f, 0f); 205 p.close(); 206 207 p = sPatternPaths[2] = new Path(); 208 p.moveTo(949.8f, 768f); 209 p.rCubicTo(92.6f, -170.6f, 213f, -440.3f, 269.4f, -768f); 210 p.lineTo(585f, 0f); 211 p.rLineTo(2.1f, 766f); 212 p.close(); 213 214 p = sPatternPaths[3] = new Path(); 215 p.moveTo(471.1f, 768f); 216 p.rMoveTo(704.5f, 0f); 217 p.cubicTo(1123.6f, 563.3f, 1027.4f, 275.2f, 856.2f, 0f); 218 p.lineTo(476.4f, 0f); 219 p.rLineTo(-5.3f, 768f); 220 p.close(); 221 222 p = sPatternPaths[4] = new Path(); 223 p.moveTo(323.1f, 768f); 224 p.moveTo(777.5f, 768f); 225 p.cubicTo(661.9f, 348.8f, 427.2f, 21.4f, 401.2f, 25.4f); 226 p.lineTo(323.1f, 768f); 227 p.close(); 228 229 p = sPatternPaths[5] = new Path(); 230 p.moveTo(178.44286f, 766.85714f); 231 p.lineTo(308.7f, 768f); 232 p.cubicTo(381.7f, 604.6f, 481.6f, 344.3f, 562.2f, 0f); 233 p.lineTo(0f, 0f); 234 p.close(); 235 236 p = sPatternPaths[6] = new Path(); 237 p.moveTo(146f, 0f); 238 p.lineTo(0f, 0f); 239 p.lineTo(0f, 768f); 240 p.lineTo(394.2f, 768f); 241 p.cubicTo(327.7f, 475.3f, 228.5f, 201f, 146f, 0f); 242 p.close(); 243 } 244 245 for (int i = 0; i < NUM_PATHS; i++) { 246 // Color is 0xAARRGGBB, so alpha << 24 will create a color with (alpha)% black. 247 // Although the color components don't really matter, since the backing bitmap cache is 248 // ALPHA_8. 249 mTempPaint.setColor(sPatternLightness[i] << 24); 250 canvas.drawPath(sPatternPaths[i], mTempPaint); 251 } 252 253 canvas.restore(); 254 mTempPaint.reset(); 255 } 256 257 @VisibleForTesting 258 public void scaleCanvasToBounds(Canvas canvas, Bitmap bitmap, Rect drawableBounds) { 259 int bitmapWidth = bitmap.getWidth(); 260 int bitmapHeight = bitmap.getHeight(); 261 float scaleX = drawableBounds.width() / (float) bitmapWidth; 262 float scaleY = drawableBounds.height() / (float) bitmapHeight; 263 264 // First scale both sides to fit independently. 265 canvas.scale(scaleX, scaleY); 266 if (scaleY > scaleX) { 267 // Adjust x-scale to maintain aspect ratio using the pivot, so that more of the texture 268 // and less of the blank space on the left edge is seen. 269 canvas.scale(scaleY / scaleX, 1f, SCALE_FOCUS_X * bitmapWidth, 0f); 270 } else if (scaleX > scaleY) { 271 // Adjust y-scale to maintain aspect ratio using the pivot, so that an intersection of 272 // two "circles" can always be seen. 273 canvas.scale(1f, scaleX / scaleY, 0f, SCALE_FOCUS_Y * bitmapHeight); 274 } 275 } 276 277 @Override 278 public void setAlpha(int i) { 279 // Ignore 280 } 281 282 @Override 283 public void setColorFilter(ColorFilter colorFilter) { 284 // Ignore 285 } 286 287 @Override 288 public int getOpacity() { 289 return 0; 290 } 291 292 /** 293 * Sets the color used as the base color of this pattern drawable. The alpha component of the 294 * color will be ignored. 295 */ 296 public void setColor(int color) { 297 final int r = Color.red(color); 298 final int g = Color.green(color); 299 final int b = Color.blue(color); 300 mColor = Color.argb(COLOR_ALPHA_INT, r, g, b); 301 mColorFilter = new ColorMatrixColorFilter(new float[] { 302 0, 0, 0, 1 - COLOR_ALPHA, r * COLOR_ALPHA, 303 0, 0, 0, 1 - COLOR_ALPHA, g * COLOR_ALPHA, 304 0, 0, 0, 1 - COLOR_ALPHA, b * COLOR_ALPHA, 305 0, 0, 0, 0, 255 306 }); 307 invalidateSelf(); 308 } 309 310 /** 311 * @return The color used as the base color of this pattern drawable. The alpha component of 312 * this is always 255. 313 */ 314 public int getColor() { 315 return Color.argb(255, Color.red(mColor), Color.green(mColor), Color.blue(mColor)); 316 } 317} 318