/
ShadowFactory.java
168 lines (155 loc) · 7.37 KB
/
ShadowFactory.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
package com.reactnativefastshadow;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.NinePatch;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.NinePatchDrawable;
import android.renderscript.Allocation;
import android.renderscript.RenderScript;
import android.renderscript.ScriptIntrinsicBlur;
import android.util.DisplayMetrics;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class ShadowFactory {
public ShadowSpecs getShadowSpecs(int width, int height, float[] cornerRadii, float blurRadius) {
NinePatchInsets ninePatchInsets = getNinePatchInsets(cornerRadii, blurRadius);
int ninePatchWidth = ninePatchInsets.left + ninePatchInsets.right + 1;
int ninePatchHeight = ninePatchInsets.top + ninePatchInsets.bottom + 1;
boolean useNinePatchHorizontally = width >= ninePatchWidth;
boolean useNinePatchVertically = height >= ninePatchHeight;
int shapeWidth = useNinePatchHorizontally ? ninePatchWidth : width;
int shapeHeight = useNinePatchVertically ? ninePatchHeight : height;
return new ShadowSpecs(
shapeWidth, shapeHeight, cornerRadii, blurRadius,
ninePatchInsets, useNinePatchHorizontally, useNinePatchVertically
);
}
public Shadow createShadow(Context context, ShadowSpecs specs) {
if (specs.shapeWidth <= 0 || specs.shapeHeight <= 0) {
return null;
}
float[] cornerRadii = specs.cornerRadii;
float blurRadius = specs.blurRadius;
NinePatchInsets ninePatchInsets = specs.ninePatchInsets;
boolean useNinePatchHorizontally = specs.useNinePatchHorizontally;
boolean useNinePatchVertically = specs.useNinePatchVertically;
int inset = (int)Math.ceil(blurRadius);
int bitmapWidth = specs.shapeWidth + 2 * inset;
int bitmapHeight = specs.shapeHeight + 2 * inset;
Bitmap bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
bitmap.setDensity(DisplayMetrics.DENSITY_DEFAULT);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(0xff000000);
Path roundedRect = new Path();
roundedRect.addRoundRect(
new RectF(inset, inset, bitmapWidth - inset, bitmapHeight - inset),
new float[]{
cornerRadii[0],
cornerRadii[0],
cornerRadii[1],
cornerRadii[1],
cornerRadii[2],
cornerRadii[2],
cornerRadii[3],
cornerRadii[3],
},
Path.Direction.CW
);
canvas.drawPath(roundedRect, paint);
try {
if (blurRadius > 0) {
blurBitmap(context, bitmap, blurRadius);
}
Drawable drawable;
if (useNinePatchHorizontally || useNinePatchVertically) {
NinePatch ninePatch = createNinePatch(bitmap, ninePatchInsets, inset, useNinePatchHorizontally, useNinePatchVertically);
drawable = new NinePatchDrawable(context.getResources(), ninePatch);
} else {
drawable = new BitmapDrawable(context.getResources(), bitmap);
}
return new Shadow(specs, drawable, bitmap);
} catch (Exception e) {
bitmap.recycle();
return null;
}
}
private void blurBitmap(Context context, Bitmap bitmap, float blurRadius) {
RenderScript rs = null;
Allocation input = null;
Allocation output = null;
ScriptIntrinsicBlur blurEffect = null;
try {
rs = RenderScript.create(context);
input = Allocation.createFromBitmap(rs, bitmap, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT);
output = Allocation.createTyped(rs, input.getType());
blurEffect = ScriptIntrinsicBlur.create(rs, input.getElement());
blurEffect.setRadius(blurRadius);
blurEffect.setInput(input);
blurEffect.forEach(output);
output.copyTo(bitmap);
} finally {
if (blurEffect != null) blurEffect.destroy();
if (output != null) output.destroy();
if (input != null) input.destroy();
if (rs != null) rs.destroy();
}
}
// The format of the chunk param is not documented by Android.
// Here, it is inferred from Android source code:
// https://android.googlesource.com/platform/frameworks/base/+/master/libs/androidfw/include/androidfw/ResourceTypes.h#80
// https://android.googlesource.com/platform/frameworks/base/+/master/libs/androidfw/ResourceTypes.cpp#221
private NinePatch createNinePatch(Bitmap bitmap, NinePatchInsets insets, int extraInset, boolean horizontally, boolean vertically) {
int numXDivs = horizontally ? 2 : 0;
int numYDivs = vertically ? 2 : 0;
int numColors = (numXDivs + 1) * (numYDivs + 1);
boolean isNinePatch = horizontally && vertically;
int length = 4 + (4 * 4) + ((numXDivs + numYDivs) * 4) + (numColors * 4) + (3 * 4); // Header + paddings + divs + colors + unused bytes
ByteBuffer chunk = ByteBuffer.allocate(length);
chunk.order(ByteOrder.nativeOrder());
chunk.put((byte) 1); // wasDeserialized
chunk.put((byte) numXDivs); // numXDivs
chunk.put((byte) numYDivs); // numYDivs
chunk.put((byte) numColors); // numColors
chunk.position(chunk.position() + 8); // skip 8 bytes
chunk.putInt(0); // paddingLeft: unused as there will be no child views
chunk.putInt(0); // paddingRight: unused as there will be no child views
chunk.putInt(0); // paddingTop: unused as there will be no child views
chunk.putInt(0); // paddingBottom: unused as there will be no child views
chunk.position(chunk.position() + 4); // skip 4 bytes
if (horizontally) {
chunk.putInt(insets.left + extraInset); // xDivs[0]
chunk.putInt(bitmap.getWidth() - insets.right - extraInset); // xDivs[1]
}
if (vertically) {
chunk.putInt(insets.top + extraInset); // yDivs[0]
chunk.putInt(bitmap.getHeight() - insets.bottom - extraInset); // yDivs[1]
}
if (isNinePatch) chunk.putInt(0x1); // top-left corner color: 0x1 as it's not a solid color
if (vertically) chunk.putInt(0x1); // top edge color: 0x1 as it's not a solid color
if (isNinePatch) chunk.putInt(0x1); // top-right corner color: 0x1 as it's not a solid color
if (horizontally) chunk.putInt(0x1); // left edge color: 0x1 as it's not a solid color
chunk.putInt(isNinePatch ? 0xff000000 : 0x1); // center: this is solid black in 9-patches
if (horizontally) chunk.putInt(0x1); // right edge color: 0x1 as it's not a solid color
if (isNinePatch) chunk.putInt(0x1); // bottom-left corner color: 0x1 as it's not a solid color
if (vertically) chunk.putInt(0x1); // bottom edge color: 0x1 as it's not a solid color
if (isNinePatch) chunk.putInt(0x1); // bottom-right corner color: 0x1 as it's not a solid color
return new NinePatch(bitmap, chunk.array());
}
private NinePatchInsets getNinePatchInsets(float[] cornerRadii, float blurRadius) {
return new NinePatchInsets(
getNinePatchInsetForCorner(Math.max(cornerRadii[0], cornerRadii[3]), blurRadius),
getNinePatchInsetForCorner(Math.max(cornerRadii[1], cornerRadii[2]), blurRadius),
getNinePatchInsetForCorner(Math.max(cornerRadii[0], cornerRadii[1]), blurRadius),
getNinePatchInsetForCorner(Math.max(cornerRadii[2], cornerRadii[3]), blurRadius)
);
}
private int getNinePatchInsetForCorner(float cornerRadius, float blurRadius) {
return (int) Math.ceil(blurRadius + cornerRadius);
}
}