1 #include <vector>
2
3 #include "Gainmap.h"
4 #include "GraphicsJNI.h"
5 #include "SkBitmap.h"
6 #include "SkBlendMode.h"
7 #include "SkColor.h"
8 #include "SkColorFilter.h"
9 #include "SkGradientShader.h"
10 #include "SkImage.h"
11 #include "SkImagePriv.h"
12 #include "SkMatrix.h"
13 #include "SkPoint.h"
14 #include "SkRefCnt.h"
15 #include "SkSamplingOptions.h"
16 #include "SkScalar.h"
17 #include "SkShader.h"
18 #include "SkString.h"
19 #include "SkTileMode.h"
20 #include "effects/GainmapRenderer.h"
21 #include "include/effects/SkRuntimeEffect.h"
22
23 using namespace android::uirenderer;
24
25 /**
26 * By default Skia gradients will interpolate their colors in unpremul space
27 * and then premultiply each of the results. We must set this flag to preserve
28 * backwards compatibility by premultiplying the colors of the gradient first,
29 * and then interpolating between them.
30 */
31 static const uint32_t sGradientShaderFlags = SkGradientShader::kInterpolateColorsInPremul_Flag;
32
33 #define ThrowIAE_IfNull(env, ptr) \
34 if (nullptr == ptr) { \
35 doThrowIAE(env); \
36 return 0; \
37 }
38
39 ///////////////////////////////////////////////////////////////////////////////////////////////
40
Shader_safeUnref(SkShader * shader)41 static void Shader_safeUnref(SkShader* shader) {
42 SkSafeUnref(shader);
43 }
44
Shader_getNativeFinalizer(JNIEnv *,jobject)45 static jlong Shader_getNativeFinalizer(JNIEnv*, jobject) {
46 return static_cast<jlong>(reinterpret_cast<uintptr_t>(&Shader_safeUnref));
47 }
48
49 ///////////////////////////////////////////////////////////////////////////////////////////////
50
51 static SkGainmapInfo sNoOpGainmap = {
52 .fGainmapRatioMin = {1.f, 1.f, 1.f, 1.0},
53 .fGainmapRatioMax = {1.f, 1.f, 1.f, 1.0},
54 .fGainmapGamma = {1.f, 1.f, 1.f, 1.f},
55 .fEpsilonSdr = {0.f, 0.f, 0.f, 1.0},
56 .fEpsilonHdr = {0.f, 0.f, 0.f, 1.0},
57 .fDisplayRatioSdr = 1.f,
58 .fDisplayRatioHdr = 1.f,
59 };
60
BitmapShader_constructor(JNIEnv * env,jobject o,jlong matrixPtr,jlong bitmapHandle,jint tileModeX,jint tileModeY,jint maxAniso,bool filter,bool isDirectSampled,jlong overrideGainmapPtr)61 static jlong BitmapShader_constructor(JNIEnv* env, jobject o, jlong matrixPtr, jlong bitmapHandle,
62 jint tileModeX, jint tileModeY, jint maxAniso, bool filter,
63 bool isDirectSampled, jlong overrideGainmapPtr) {
64 SkSamplingOptions sampling = maxAniso > 0 ? SkSamplingOptions::Aniso(static_cast<int>(maxAniso))
65 : SkSamplingOptions(filter ? SkFilterMode::kLinear
66 : SkFilterMode::kNearest,
67 SkMipmapMode::kNone);
68 const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
69 const Gainmap* gainmap = reinterpret_cast<Gainmap*>(overrideGainmapPtr);
70 sk_sp<SkImage> image;
71 if (bitmapHandle) {
72 // Only pass a valid SkBitmap object to the constructor if the Bitmap exists. Otherwise,
73 // we'll pass an empty SkBitmap to avoid crashing/excepting for compatibility.
74 auto& bitmap = android::bitmap::toBitmap(bitmapHandle);
75 image = bitmap.makeImage();
76 if (!gainmap && bitmap.hasGainmap()) {
77 gainmap = bitmap.gainmap().get();
78 }
79
80 if (!isDirectSampled && gainmap && gainmap->info != sNoOpGainmap) {
81 sk_sp<SkShader> gainmapShader =
82 MakeGainmapShader(image, gainmap->bitmap->makeImage(), gainmap->info,
83 (SkTileMode)tileModeX, (SkTileMode)tileModeY, sampling);
84 if (gainmapShader) {
85 if (matrix) {
86 gainmapShader = gainmapShader->makeWithLocalMatrix(*matrix);
87 }
88 return reinterpret_cast<jlong>(gainmapShader.release());
89 }
90 }
91 }
92
93 if (!image.get()) {
94 SkBitmap bitmap;
95 image = SkMakeImageFromRasterBitmap(bitmap, kNever_SkCopyPixelsMode);
96 }
97
98 sk_sp<SkShader> shader;
99 if (isDirectSampled) {
100 shader = image->makeRawShader((SkTileMode)tileModeX, (SkTileMode)tileModeY, sampling);
101 } else {
102 shader = image->makeShader((SkTileMode)tileModeX, (SkTileMode)tileModeY, sampling);
103 }
104 ThrowIAE_IfNull(env, shader.get());
105
106 if (matrix) {
107 shader = shader->makeWithLocalMatrix(*matrix);
108 }
109
110 return reinterpret_cast<jlong>(shader.release());
111 }
112
113 ///////////////////////////////////////////////////////////////////////////////////////////////
114
convertColorLongs(JNIEnv * env,jlongArray colorArray)115 static std::vector<SkColor4f> convertColorLongs(JNIEnv* env, jlongArray colorArray) {
116 const size_t count = env->GetArrayLength(colorArray);
117 const jlong* colorValues = env->GetLongArrayElements(colorArray, nullptr);
118
119 std::vector<SkColor4f> colors(count);
120 for (size_t i = 0; i < count; ++i) {
121 colors[i] = GraphicsJNI::convertColorLong(colorValues[i]);
122 }
123
124 env->ReleaseLongArrayElements(colorArray, const_cast<jlong*>(colorValues), JNI_ABORT);
125 return colors;
126 }
127
128 ///////////////////////////////////////////////////////////////////////////////////////////////
129
LinearGradient_create(JNIEnv * env,jobject,jlong matrixPtr,jfloat x0,jfloat y0,jfloat x1,jfloat y1,jlongArray colorArray,jfloatArray posArray,jint tileMode,jlong colorSpaceHandle)130 static jlong LinearGradient_create(JNIEnv* env, jobject, jlong matrixPtr,
131 jfloat x0, jfloat y0, jfloat x1, jfloat y1, jlongArray colorArray,
132 jfloatArray posArray, jint tileMode, jlong colorSpaceHandle) {
133 SkPoint pts[2];
134 pts[0].set(x0, y0);
135 pts[1].set(x1, y1);
136
137 std::vector<SkColor4f> colors = convertColorLongs(env, colorArray);
138
139 AutoJavaFloatArray autoPos(env, posArray, colors.size());
140 SkScalar* pos = autoPos.ptr();
141
142 sk_sp<SkShader> shader(SkGradientShader::MakeLinear(pts, &colors[0],
143 GraphicsJNI::getNativeColorSpace(colorSpaceHandle), pos, colors.size(),
144 static_cast<SkTileMode>(tileMode), sGradientShaderFlags, nullptr));
145 ThrowIAE_IfNull(env, shader);
146
147 const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
148 if (matrix) {
149 shader = shader->makeWithLocalMatrix(*matrix);
150 }
151
152 return reinterpret_cast<jlong>(shader.release());
153 }
154
155 ///////////////////////////////////////////////////////////////////////////////////////////////
156
RadialGradient_create(JNIEnv * env,jobject,jlong matrixPtr,jfloat startX,jfloat startY,jfloat startRadius,jfloat endX,jfloat endY,jfloat endRadius,jlongArray colorArray,jfloatArray posArray,jint tileMode,jlong colorSpaceHandle)157 static jlong RadialGradient_create(JNIEnv* env,
158 jobject,
159 jlong matrixPtr,
160 jfloat startX,
161 jfloat startY,
162 jfloat startRadius,
163 jfloat endX,
164 jfloat endY,
165 jfloat endRadius,
166 jlongArray colorArray,
167 jfloatArray posArray,
168 jint tileMode,
169 jlong colorSpaceHandle) {
170
171 SkPoint start;
172 start.set(startX, startY);
173
174 SkPoint end;
175 end.set(endX, endY);
176
177 std::vector<SkColor4f> colors = convertColorLongs(env, colorArray);
178
179 AutoJavaFloatArray autoPos(env, posArray, colors.size());
180 SkScalar* pos = autoPos.ptr();
181
182 auto colorSpace = GraphicsJNI::getNativeColorSpace(colorSpaceHandle);
183 auto skTileMode = static_cast<SkTileMode>(tileMode);
184 sk_sp<SkShader> shader = SkGradientShader::MakeTwoPointConical(start, startRadius, end,
185 endRadius, &colors[0], std::move(colorSpace), pos, colors.size(), skTileMode,
186 sGradientShaderFlags, nullptr);
187 ThrowIAE_IfNull(env, shader);
188
189 // Explicitly create a new shader with the specified matrix to match existing behavior.
190 // Passing in the matrix in the instantiation above can throw exceptions for non-invertible
191 // matrices. However, makeWithLocalMatrix will still allow for the shader to be created
192 // and skia handles null-shaders internally (i.e. is ignored)
193 const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
194 if (matrix) {
195 shader = shader->makeWithLocalMatrix(*matrix);
196 }
197
198 return reinterpret_cast<jlong>(shader.release());
199 }
200
201 ///////////////////////////////////////////////////////////////////////////////
202
SweepGradient_create(JNIEnv * env,jobject,jlong matrixPtr,jfloat x,jfloat y,jlongArray colorArray,jfloatArray jpositions,jlong colorSpaceHandle)203 static jlong SweepGradient_create(JNIEnv* env, jobject, jlong matrixPtr, jfloat x, jfloat y,
204 jlongArray colorArray, jfloatArray jpositions, jlong colorSpaceHandle) {
205 std::vector<SkColor4f> colors = convertColorLongs(env, colorArray);
206
207 AutoJavaFloatArray autoPos(env, jpositions, colors.size());
208 SkScalar* pos = autoPos.ptr();
209
210 sk_sp<SkShader> shader = SkGradientShader::MakeSweep(x, y, &colors[0],
211 GraphicsJNI::getNativeColorSpace(colorSpaceHandle), pos, colors.size(),
212 sGradientShaderFlags, nullptr);
213 ThrowIAE_IfNull(env, shader);
214
215 const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
216 if (matrix) {
217 shader = shader->makeWithLocalMatrix(*matrix);
218 }
219
220 return reinterpret_cast<jlong>(shader.release());
221 }
222
223 ///////////////////////////////////////////////////////////////////////////////////////////////
224
ComposeShader_create(JNIEnv * env,jobject o,jlong matrixPtr,jlong shaderAHandle,jlong shaderBHandle,jint xfermodeHandle)225 static jlong ComposeShader_create(JNIEnv* env, jobject o, jlong matrixPtr,
226 jlong shaderAHandle, jlong shaderBHandle, jint xfermodeHandle) {
227 const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
228 SkShader* shaderA = reinterpret_cast<SkShader *>(shaderAHandle);
229 SkShader* shaderB = reinterpret_cast<SkShader *>(shaderBHandle);
230 SkBlendMode mode = static_cast<SkBlendMode>(xfermodeHandle);
231 sk_sp<SkShader> baseShader(SkShaders::Blend(mode,
232 sk_ref_sp(shaderA), sk_ref_sp(shaderB)));
233
234 SkShader* shader;
235
236 if (matrix) {
237 shader = baseShader->makeWithLocalMatrix(*matrix).release();
238 } else {
239 shader = baseShader.release();
240 }
241 return reinterpret_cast<jlong>(shader);
242 }
243
244 ///////////////////////////////////////////////////////////////////////////////////////////////
245
246 ///////////////////////////////////////////////////////////////////////////////////////////////
247
RuntimeShader_createShaderBuilder(JNIEnv * env,jobject,jstring sksl)248 static jlong RuntimeShader_createShaderBuilder(JNIEnv* env, jobject, jstring sksl) {
249 ScopedUtfChars strSksl(env, sksl);
250 auto result = SkRuntimeEffect::MakeForShader(SkString(strSksl.c_str()),
251 SkRuntimeEffect::Options{});
252 if (result.effect.get() == nullptr) {
253 doThrowIAE(env, result.errorText.c_str());
254 return 0;
255 }
256 return reinterpret_cast<jlong>(new SkRuntimeShaderBuilder(std::move(result.effect)));
257 }
258
SkRuntimeShaderBuilder_delete(SkRuntimeShaderBuilder * builder)259 static void SkRuntimeShaderBuilder_delete(SkRuntimeShaderBuilder* builder) {
260 delete builder;
261 }
262
RuntimeShader_getNativeFinalizer(JNIEnv *,jobject)263 static jlong RuntimeShader_getNativeFinalizer(JNIEnv*, jobject) {
264 return static_cast<jlong>(reinterpret_cast<uintptr_t>(&SkRuntimeShaderBuilder_delete));
265 }
266
RuntimeShader_create(JNIEnv * env,jobject,jlong shaderBuilder,jlong matrixPtr)267 static jlong RuntimeShader_create(JNIEnv* env, jobject, jlong shaderBuilder, jlong matrixPtr) {
268 SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilder);
269 const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
270 sk_sp<SkShader> shader = builder->makeShader(matrix);
271 ThrowIAE_IfNull(env, shader);
272 return reinterpret_cast<jlong>(shader.release());
273 }
274
ThrowIAEFmt(JNIEnv * env,const char * fmt,...)275 static inline int ThrowIAEFmt(JNIEnv* env, const char* fmt, ...) {
276 va_list args;
277 va_start(args, fmt);
278 int ret = jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", fmt, args);
279 va_end(args);
280 return ret;
281 }
282
isIntUniformType(const SkRuntimeEffect::Uniform::Type & type)283 static bool isIntUniformType(const SkRuntimeEffect::Uniform::Type& type) {
284 switch (type) {
285 case SkRuntimeEffect::Uniform::Type::kFloat:
286 case SkRuntimeEffect::Uniform::Type::kFloat2:
287 case SkRuntimeEffect::Uniform::Type::kFloat3:
288 case SkRuntimeEffect::Uniform::Type::kFloat4:
289 case SkRuntimeEffect::Uniform::Type::kFloat2x2:
290 case SkRuntimeEffect::Uniform::Type::kFloat3x3:
291 case SkRuntimeEffect::Uniform::Type::kFloat4x4:
292 return false;
293 case SkRuntimeEffect::Uniform::Type::kInt:
294 case SkRuntimeEffect::Uniform::Type::kInt2:
295 case SkRuntimeEffect::Uniform::Type::kInt3:
296 case SkRuntimeEffect::Uniform::Type::kInt4:
297 return true;
298 }
299 }
300
UpdateFloatUniforms(JNIEnv * env,SkRuntimeShaderBuilder * builder,const char * uniformName,const float values[],int count,bool isColor)301 static void UpdateFloatUniforms(JNIEnv* env, SkRuntimeShaderBuilder* builder,
302 const char* uniformName, const float values[], int count,
303 bool isColor) {
304 SkRuntimeShaderBuilder::BuilderUniform uniform = builder->uniform(uniformName);
305 if (uniform.fVar == nullptr) {
306 ThrowIAEFmt(env, "unable to find uniform named %s", uniformName);
307 } else if (isColor != ((uniform.fVar->flags & SkRuntimeEffect::Uniform::kColor_Flag) != 0)) {
308 if (isColor) {
309 jniThrowExceptionFmt(
310 env, "java/lang/IllegalArgumentException",
311 "attempting to set a color uniform using the non-color specific APIs: %s %x",
312 uniformName, uniform.fVar->flags);
313 } else {
314 ThrowIAEFmt(env,
315 "attempting to set a non-color uniform using the setColorUniform APIs: %s",
316 uniformName);
317 }
318 } else if (isIntUniformType(uniform.fVar->type)) {
319 ThrowIAEFmt(env, "attempting to set a int uniform using the setUniform APIs: %s",
320 uniformName);
321 } else if (!uniform.set<float>(values, count)) {
322 ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
323 uniform.fVar->sizeInBytes(), sizeof(float) * count);
324 }
325 }
326
RuntimeShader_updateFloatUniforms(JNIEnv * env,jobject,jlong shaderBuilder,jstring jUniformName,jfloat value1,jfloat value2,jfloat value3,jfloat value4,jint count)327 static void RuntimeShader_updateFloatUniforms(JNIEnv* env, jobject, jlong shaderBuilder,
328 jstring jUniformName, jfloat value1, jfloat value2,
329 jfloat value3, jfloat value4, jint count) {
330 SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilder);
331 ScopedUtfChars name(env, jUniformName);
332 const float values[4] = {value1, value2, value3, value4};
333 UpdateFloatUniforms(env, builder, name.c_str(), values, count, false);
334 }
335
RuntimeShader_updateFloatArrayUniforms(JNIEnv * env,jobject,jlong shaderBuilder,jstring jUniformName,jfloatArray jvalues,jboolean isColor)336 static void RuntimeShader_updateFloatArrayUniforms(JNIEnv* env, jobject, jlong shaderBuilder,
337 jstring jUniformName, jfloatArray jvalues,
338 jboolean isColor) {
339 SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilder);
340 ScopedUtfChars name(env, jUniformName);
341 AutoJavaFloatArray autoValues(env, jvalues, 0, kRO_JNIAccess);
342 UpdateFloatUniforms(env, builder, name.c_str(), autoValues.ptr(), autoValues.length(), isColor);
343 }
344
UpdateIntUniforms(JNIEnv * env,SkRuntimeShaderBuilder * builder,const char * uniformName,const int values[],int count)345 static void UpdateIntUniforms(JNIEnv* env, SkRuntimeShaderBuilder* builder, const char* uniformName,
346 const int values[], int count) {
347 SkRuntimeShaderBuilder::BuilderUniform uniform = builder->uniform(uniformName);
348 if (uniform.fVar == nullptr) {
349 ThrowIAEFmt(env, "unable to find uniform named %s", uniformName);
350 } else if (!isIntUniformType(uniform.fVar->type)) {
351 ThrowIAEFmt(env, "attempting to set a non-int uniform using the setIntUniform APIs: %s",
352 uniformName);
353 } else if (!uniform.set<int>(values, count)) {
354 ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
355 uniform.fVar->sizeInBytes(), sizeof(float) * count);
356 }
357 }
358
RuntimeShader_updateIntUniforms(JNIEnv * env,jobject,jlong shaderBuilder,jstring jUniformName,jint value1,jint value2,jint value3,jint value4,jint count)359 static void RuntimeShader_updateIntUniforms(JNIEnv* env, jobject, jlong shaderBuilder,
360 jstring jUniformName, jint value1, jint value2,
361 jint value3, jint value4, jint count) {
362 SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilder);
363 ScopedUtfChars name(env, jUniformName);
364 const int values[4] = {value1, value2, value3, value4};
365 UpdateIntUniforms(env, builder, name.c_str(), values, count);
366 }
367
RuntimeShader_updateIntArrayUniforms(JNIEnv * env,jobject,jlong shaderBuilder,jstring jUniformName,jintArray jvalues)368 static void RuntimeShader_updateIntArrayUniforms(JNIEnv* env, jobject, jlong shaderBuilder,
369 jstring jUniformName, jintArray jvalues) {
370 SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilder);
371 ScopedUtfChars name(env, jUniformName);
372 AutoJavaIntArray autoValues(env, jvalues, 0);
373 UpdateIntUniforms(env, builder, name.c_str(), autoValues.ptr(), autoValues.length());
374 }
375
RuntimeShader_updateShader(JNIEnv * env,jobject,jlong shaderBuilder,jstring jUniformName,jlong shaderHandle)376 static void RuntimeShader_updateShader(JNIEnv* env, jobject, jlong shaderBuilder,
377 jstring jUniformName, jlong shaderHandle) {
378 SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilder);
379 ScopedUtfChars name(env, jUniformName);
380 SkShader* shader = reinterpret_cast<SkShader*>(shaderHandle);
381
382 SkRuntimeShaderBuilder::BuilderChild child = builder->child(name.c_str());
383 if (child.fChild == nullptr) {
384 ThrowIAEFmt(env, "unable to find shader named %s", name.c_str());
385 return;
386 }
387
388 builder->child(name.c_str()) = sk_ref_sp(shader);
389 }
390
391 ///////////////////////////////////////////////////////////////////////////////////////////////
392
393 static const JNINativeMethod gShaderMethods[] = {
394 { "nativeGetFinalizer", "()J", (void*)Shader_getNativeFinalizer },
395 };
396
397 static const JNINativeMethod gBitmapShaderMethods[] = {
398 {"nativeCreate", "(JJIIIZZJ)J", (void*)BitmapShader_constructor},
399
400 };
401
402 static const JNINativeMethod gLinearGradientMethods[] = {
403 { "nativeCreate", "(JFFFF[J[FIJ)J", (void*)LinearGradient_create },
404 };
405
406 static const JNINativeMethod gRadialGradientMethods[] = {
407 { "nativeCreate", "(JFFFFFF[J[FIJ)J", (void*)RadialGradient_create },
408 };
409
410 static const JNINativeMethod gSweepGradientMethods[] = {
411 { "nativeCreate", "(JFF[J[FJ)J", (void*)SweepGradient_create },
412 };
413
414 static const JNINativeMethod gComposeShaderMethods[] = {
415 { "nativeCreate", "(JJJI)J", (void*)ComposeShader_create },
416 };
417
418 static const JNINativeMethod gRuntimeShaderMethods[] = {
419 {"nativeGetFinalizer", "()J", (void*)RuntimeShader_getNativeFinalizer},
420 {"nativeCreateShader", "(JJ)J", (void*)RuntimeShader_create},
421 {"nativeCreateBuilder", "(Ljava/lang/String;)J", (void*)RuntimeShader_createShaderBuilder},
422 {"nativeUpdateUniforms", "(JLjava/lang/String;[FZ)V",
423 (void*)RuntimeShader_updateFloatArrayUniforms},
424 {"nativeUpdateUniforms", "(JLjava/lang/String;FFFFI)V",
425 (void*)RuntimeShader_updateFloatUniforms},
426 {"nativeUpdateUniforms", "(JLjava/lang/String;[I)V",
427 (void*)RuntimeShader_updateIntArrayUniforms},
428 {"nativeUpdateUniforms", "(JLjava/lang/String;IIIII)V",
429 (void*)RuntimeShader_updateIntUniforms},
430 {"nativeUpdateShader", "(JLjava/lang/String;J)V", (void*)RuntimeShader_updateShader},
431 };
432
register_android_graphics_Shader(JNIEnv * env)433 int register_android_graphics_Shader(JNIEnv* env)
434 {
435 android::RegisterMethodsOrDie(env, "android/graphics/Shader", gShaderMethods,
436 NELEM(gShaderMethods));
437 android::RegisterMethodsOrDie(env, "android/graphics/BitmapShader", gBitmapShaderMethods,
438 NELEM(gBitmapShaderMethods));
439 android::RegisterMethodsOrDie(env, "android/graphics/LinearGradient", gLinearGradientMethods,
440 NELEM(gLinearGradientMethods));
441 android::RegisterMethodsOrDie(env, "android/graphics/RadialGradient", gRadialGradientMethods,
442 NELEM(gRadialGradientMethods));
443 android::RegisterMethodsOrDie(env, "android/graphics/SweepGradient", gSweepGradientMethods,
444 NELEM(gSweepGradientMethods));
445 android::RegisterMethodsOrDie(env, "android/graphics/ComposeShader", gComposeShaderMethods,
446 NELEM(gComposeShaderMethods));
447 android::RegisterMethodsOrDie(env, "android/graphics/RuntimeShader", gRuntimeShaderMethods,
448 NELEM(gRuntimeShaderMethods));
449
450 return 0;
451 }
452