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 
17 package com.android.setupwizardlib;
18 
19 import android.annotation.SuppressLint;
20 import android.content.Context;
21 import android.content.res.TypedArray;
22 import android.graphics.Bitmap;
23 import android.graphics.Canvas;
24 import android.graphics.Color;
25 import android.graphics.ColorFilter;
26 import android.graphics.Paint;
27 import android.graphics.Path;
28 import android.graphics.Rect;
29 import android.graphics.drawable.Drawable;
30 import android.os.Build;
31 
32 import com.android.setupwizardlib.annotations.VisibleForTesting;
33 
34 public class GlifPatternDrawable extends Drawable {
35 
36     /* static section */
37 
38     @SuppressLint("InlinedApi")
39     private static final int[] ATTRS_PRIMARY_COLOR = new int[]{ android.R.attr.colorPrimary };
40 
41     private static final float VIEWBOX_HEIGHT = 768f;
42     private static final float VIEWBOX_WIDTH = 1366f;
43     private static final float SCALE_FOCUS_X = 200f;
44     private static final float SCALE_FOCUS_Y = 175f;
45 
getDefault(Context context)46     public static GlifPatternDrawable getDefault(Context context) {
47         int colorPrimary = 0;
48         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
49             final TypedArray a = context.obtainStyledAttributes(ATTRS_PRIMARY_COLOR);
50             colorPrimary = a.getColor(0, Color.BLACK);
51             a.recycle();
52         }
53         return new GlifPatternDrawable(colorPrimary);
54     }
55 
56     /* non-static section */
57 
58     private int mColor;
59     private Paint mPaint;
60     private float[] mTempHsv = new float[3];
61     private Path mTempPath = new Path();
62     private Bitmap mBitmap;
63 
GlifPatternDrawable(int color)64     public GlifPatternDrawable(int color) {
65         mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
66         setColor(color);
67     }
68 
69     @Override
draw(Canvas canvas)70     public void draw(Canvas canvas) {
71         if (mBitmap == null) {
72             mBitmap = Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(),
73                     Bitmap.Config.ARGB_8888);
74             Canvas bitmapCanvas = new Canvas(mBitmap);
75             renderOnCanvas(bitmapCanvas);
76         }
77         canvas.drawBitmap(mBitmap, 0, 0, null);
78     }
79 
renderOnCanvas(Canvas canvas)80     private void renderOnCanvas(Canvas canvas) {
81         canvas.save();
82         canvas.clipRect(getBounds());
83 
84         Color.colorToHSV(mColor, mTempHsv);
85         scaleCanvasToBounds(canvas);
86 
87         // Draw the pattern by creating the paths, adjusting the colors and drawing them. The path
88         // values are extracted from the SVG of the pattern file.
89 
90         mTempHsv[2] = 0.753f;
91         Path p = mTempPath;
92         p.reset();
93         p.moveTo(1029.4f, 357.5f);
94         p.lineTo(1366f, 759.1f);
95         p.lineTo(1366f, 0f);
96         p.lineTo(1137.7f, 0f);
97         p.close();
98         drawPath(canvas, p, mTempHsv);
99 
100         mTempHsv[2] = 0.78f;
101         p.reset();
102         p.moveTo(1138.1f, 0f);
103         p.rLineTo(-144.8f, 768f);
104         p.rLineTo(372.7f, 0f);
105         p.rLineTo(0f, -524f);
106         p.cubicTo(1290.7f, 121.6f, 1219.2f, 41.1f, 1178.7f, 0f);
107         p.close();
108         drawPath(canvas, p, mTempHsv);
109 
110         mTempHsv[2] = 0.804f;
111         p.reset();
112         p.moveTo(949.8f, 768f);
113         p.rCubicTo(92.6f, -170.6f, 213f, -440.3f, 269.4f, -768f);
114         p.lineTo(585f, 0f);
115         p.rLineTo(2.1f, 766f);
116         p.close();
117         drawPath(canvas, p, mTempHsv);
118 
119         mTempHsv[2] = 0.827f;
120         p.reset();
121         p.moveTo(471.1f, 768f);
122         p.rMoveTo(704.5f, 0f);
123         p.cubicTo(1123.6f, 563.3f, 1027.4f, 275.2f, 856.2f, 0f);
124         p.lineTo(476.4f, 0f);
125         p.rLineTo(-5.3f, 768f);
126         p.close();
127         drawPath(canvas, p, mTempHsv);
128 
129         mTempHsv[2] = 0.867f;
130         p.reset();
131         p.moveTo(323.1f, 768f);
132         p.moveTo(777.5f, 768f);
133         p.cubicTo(661.9f, 348.8f, 427.2f, 21.4f, 401.2f, 25.4f);
134         p.lineTo(323.1f, 768f);
135         p.close();
136         drawPath(canvas, p, mTempHsv);
137 
138         mTempHsv[2] = 0.894f;
139         p.reset();
140         p.moveTo(178.44286f, 766.85714f);
141         p.lineTo(308.7f, 768f);
142         p.cubicTo(381.7f, 604.6f, 481.6f, 344.3f, 562.2f, 0f);
143         p.lineTo(0f, 0f);
144         p.close();
145         drawPath(canvas, p, mTempHsv);
146 
147         mTempHsv[2] = 0.929f;
148         p.reset();
149         p.moveTo(146f, 0f);
150         p.lineTo(0f, 0f);
151         p.lineTo(0f, 768f);
152         p.lineTo(394.2f, 768f);
153         p.cubicTo(327.7f, 475.3f, 228.5f, 201f, 146f, 0f);
154         p.close();
155         drawPath(canvas, p, mTempHsv);
156 
157         canvas.restore();
158     }
159 
160     @VisibleForTesting
scaleCanvasToBounds(Canvas canvas)161     public void scaleCanvasToBounds(Canvas canvas) {
162         final Rect bounds = getBounds();
163         final int height = bounds.height();
164         final int width = bounds.width();
165 
166         float scaleY = height / VIEWBOX_HEIGHT;
167         float scaleX = width / VIEWBOX_WIDTH;
168         // First scale both sides to fit independently.
169         canvas.scale(scaleX, scaleY);
170         if (scaleY > scaleX) {
171             // Adjust x-scale to maintain aspect ratio, but using (200, 0) as the pivot instead,
172             // so that more of the texture and less of the blank space on the left edge is seen.
173             canvas.scale(scaleY / scaleX, 1f, SCALE_FOCUS_X, 0f);
174         } else {
175             // Adjust y-scale to maintain aspect ratio, but using (0, 175) as the pivot instead,
176             // so that an intersection of two "circles" can always be seen.
177             canvas.scale(1f, scaleX / scaleY, 0f, SCALE_FOCUS_Y);
178         }
179     }
180 
drawPath(Canvas canvas, Path path, float[] hsv)181     private void drawPath(Canvas canvas, Path path, float[] hsv) {
182         mPaint.setColor(Color.HSVToColor(hsv));
183         canvas.drawPath(path, mPaint);
184     }
185 
186     @Override
setAlpha(int i)187     public void setAlpha(int i) {
188         // Ignore
189     }
190 
191     @Override
setColorFilter(ColorFilter colorFilter)192     public void setColorFilter(ColorFilter colorFilter) {
193         // Ignore
194     }
195 
196     @Override
getOpacity()197     public int getOpacity() {
198         return 0;
199     }
200 
setColor(int color)201     public void setColor(int color) {
202         mColor = color;
203         mPaint.setColor(color);
204         mBitmap = null;
205         invalidateSelf();
206     }
207 
getColor()208     public int getColor() {
209         return mColor;
210     }
211 }
212