| #include <vector> | |
| #include "Gainmap.h" | |
| #include "GraphicsJNI.h" | |
| #include "SkBitmap.h" | |
| #include "SkBlendMode.h" | |
| #include "SkColor.h" | |
| #include "SkColorFilter.h" | |
| #include "SkGradientShader.h" | |
| #include "SkImage.h" | |
| #include "SkImagePriv.h" | |
| #include "SkMatrix.h" | |
| #include "SkPoint.h" | |
| #include "SkRefCnt.h" | |
| #include "SkSamplingOptions.h" | |
| #include "SkScalar.h" | |
| #include "SkShader.h" | |
| #include "SkString.h" | |
| #include "SkTileMode.h" | |
| #include "effects/GainmapRenderer.h" | |
| #include "include/effects/SkRuntimeEffect.h" | |
| using namespace android::uirenderer; | |
| /** | |
| * By default Skia gradients will interpolate their colors in unpremul space | |
| * and then premultiply each of the results. We must set this flag to preserve | |
| * backwards compatibility by premultiplying the colors of the gradient first, | |
| * and then interpolating between them. | |
| */ | |
| static const uint32_t sGradientShaderFlags = SkGradientShader::kInterpolateColorsInPremul_Flag; | |
| #define ThrowIAE_IfNull(env, ptr) \ | |
| if (nullptr == ptr) { \ | |
| doThrowIAE(env); \ | |
| return 0; \ | |
| } | |
| static void Color_RGBToHSV(JNIEnv* env, jobject, jint red, jint green, jint blue, jfloatArray hsvArray) | |
| { | |
| SkScalar hsv[3]; | |
| SkRGBToHSV(red, green, blue, hsv); | |
| AutoJavaFloatArray autoHSV(env, hsvArray, 3); | |
| float* values = autoHSV.ptr(); | |
| for (int i = 0; i < 3; i++) { | |
| values[i] = SkScalarToFloat(hsv[i]); | |
| } | |
| } | |
| static jint Color_HSVToColor(JNIEnv* env, jobject, jint alpha, jfloatArray hsvArray) | |
| { | |
| AutoJavaFloatArray autoHSV(env, hsvArray, 3); | |
| SkScalar* hsv = autoHSV.ptr(); | |
| return static_cast<jint>(SkHSVToColor(alpha, hsv)); | |
| } | |
| /////////////////////////////////////////////////////////////////////////////////////////////// | |
| static void Shader_safeUnref(SkShader* shader) { | |
| SkSafeUnref(shader); | |
| } | |
| static jlong Shader_getNativeFinalizer(JNIEnv*, jobject) { | |
| return static_cast<jlong>(reinterpret_cast<uintptr_t>(&Shader_safeUnref)); | |
| } | |
| /////////////////////////////////////////////////////////////////////////////////////////////// | |
| static SkGainmapInfo sNoOpGainmap = { | |
| .fGainmapRatioMin = {1.f, 1.f, 1.f, 1.0}, | |
| .fGainmapRatioMax = {1.f, 1.f, 1.f, 1.0}, | |
| .fGainmapGamma = {1.f, 1.f, 1.f, 1.f}, | |
| .fEpsilonSdr = {0.f, 0.f, 0.f, 1.0}, | |
| .fEpsilonHdr = {0.f, 0.f, 0.f, 1.0}, | |
| .fDisplayRatioSdr = 1.f, | |
| .fDisplayRatioHdr = 1.f, | |
| }; | |
| static jlong BitmapShader_constructor(JNIEnv* env, jobject o, jlong matrixPtr, jlong bitmapHandle, | |
| jint tileModeX, jint tileModeY, jint maxAniso, bool filter, | |
| bool isDirectSampled, jlong overrideGainmapPtr) { | |
| SkSamplingOptions sampling = maxAniso > 0 ? SkSamplingOptions::Aniso(static_cast<int>(maxAniso)) | |
| : SkSamplingOptions(filter ? SkFilterMode::kLinear | |
| : SkFilterMode::kNearest, | |
| SkMipmapMode::kNone); | |
| const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr); | |
| const Gainmap* gainmap = reinterpret_cast<Gainmap*>(overrideGainmapPtr); | |
| sk_sp<SkImage> image; | |
| if (bitmapHandle) { | |
| // Only pass a valid SkBitmap object to the constructor if the Bitmap exists. Otherwise, | |
| // we'll pass an empty SkBitmap to avoid crashing/excepting for compatibility. | |
| auto& bitmap = android::bitmap::toBitmap(bitmapHandle); | |
| image = bitmap.makeImage(); | |
| if (!gainmap && bitmap.hasGainmap()) { | |
| gainmap = bitmap.gainmap().get(); | |
| } | |
| if (!isDirectSampled && gainmap && gainmap->info != sNoOpGainmap) { | |
| sk_sp<SkShader> gainmapShader = | |
| MakeGainmapShader(image, gainmap->bitmap->makeImage(), gainmap->info, | |
| (SkTileMode)tileModeX, (SkTileMode)tileModeY, sampling); | |
| if (gainmapShader) { | |
| if (matrix) { | |
| gainmapShader = gainmapShader->makeWithLocalMatrix(*matrix); | |
| } | |
| return reinterpret_cast<jlong>(gainmapShader.release()); | |
| } | |
| } | |
| } | |
| if (!image.get()) { | |
| SkBitmap bitmap; | |
| image = SkMakeImageFromRasterBitmap(bitmap, kNever_SkCopyPixelsMode); | |
| } | |
| sk_sp<SkShader> shader; | |
| if (isDirectSampled) { | |
| shader = image->makeRawShader((SkTileMode)tileModeX, (SkTileMode)tileModeY, sampling); | |
| } else { | |
| shader = image->makeShader((SkTileMode)tileModeX, (SkTileMode)tileModeY, sampling); | |
| } | |
| ThrowIAE_IfNull(env, shader.get()); | |
| if (matrix) { | |
| shader = shader->makeWithLocalMatrix(*matrix); | |
| } | |
| return reinterpret_cast<jlong>(shader.release()); | |
| } | |
| /////////////////////////////////////////////////////////////////////////////////////////////// | |
| static std::vector<SkColor4f> convertColorLongs(JNIEnv* env, jlongArray colorArray) { | |
| const size_t count = env->GetArrayLength(colorArray); | |
| const jlong* colorValues = env->GetLongArrayElements(colorArray, nullptr); | |
| std::vector<SkColor4f> colors(count); | |
| for (size_t i = 0; i < count; ++i) { | |
| colors[i] = GraphicsJNI::convertColorLong(colorValues[i]); | |
| } | |
| env->ReleaseLongArrayElements(colorArray, const_cast<jlong*>(colorValues), JNI_ABORT); | |
| return colors; | |
| } | |
| /////////////////////////////////////////////////////////////////////////////////////////////// | |
| static jlong LinearGradient_create(JNIEnv* env, jobject, jlong matrixPtr, | |
| jfloat x0, jfloat y0, jfloat x1, jfloat y1, jlongArray colorArray, | |
| jfloatArray posArray, jint tileMode, jlong colorSpaceHandle) { | |
| SkPoint pts[2]; | |
| pts[0].set(x0, y0); | |
| pts[1].set(x1, y1); | |
| std::vector<SkColor4f> colors = convertColorLongs(env, colorArray); | |
| AutoJavaFloatArray autoPos(env, posArray, colors.size()); | |
| SkScalar* pos = autoPos.ptr(); | |
| sk_sp<SkShader> shader(SkGradientShader::MakeLinear(pts, &colors[0], | |
| GraphicsJNI::getNativeColorSpace(colorSpaceHandle), pos, colors.size(), | |
| static_cast<SkTileMode>(tileMode), sGradientShaderFlags, nullptr)); | |
| ThrowIAE_IfNull(env, shader); | |
| const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr); | |
| if (matrix) { | |
| shader = shader->makeWithLocalMatrix(*matrix); | |
| } | |
| return reinterpret_cast<jlong>(shader.release()); | |
| } | |
| /////////////////////////////////////////////////////////////////////////////////////////////// | |
| static jlong 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) { | |
| SkPoint start; | |
| start.set(startX, startY); | |
| SkPoint end; | |
| end.set(endX, endY); | |
| std::vector<SkColor4f> colors = convertColorLongs(env, colorArray); | |
| AutoJavaFloatArray autoPos(env, posArray, colors.size()); | |
| SkScalar* pos = autoPos.ptr(); | |
| auto colorSpace = GraphicsJNI::getNativeColorSpace(colorSpaceHandle); | |
| auto skTileMode = static_cast<SkTileMode>(tileMode); | |
| sk_sp<SkShader> shader = SkGradientShader::MakeTwoPointConical(start, startRadius, end, | |
| endRadius, &colors[0], std::move(colorSpace), pos, colors.size(), skTileMode, | |
| sGradientShaderFlags, nullptr); | |
| ThrowIAE_IfNull(env, shader); | |
| // Explicitly create a new shader with the specified matrix to match existing behavior. | |
| // Passing in the matrix in the instantiation above can throw exceptions for non-invertible | |
| // matrices. However, makeWithLocalMatrix will still allow for the shader to be created | |
| // and skia handles null-shaders internally (i.e. is ignored) | |
| const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr); | |
| if (matrix) { | |
| shader = shader->makeWithLocalMatrix(*matrix); | |
| } | |
| return reinterpret_cast<jlong>(shader.release()); | |
| } | |
| /////////////////////////////////////////////////////////////////////////////// | |
| static jlong SweepGradient_create(JNIEnv* env, jobject, jlong matrixPtr, jfloat x, jfloat y, | |
| jlongArray colorArray, jfloatArray jpositions, jlong colorSpaceHandle) { | |
| std::vector<SkColor4f> colors = convertColorLongs(env, colorArray); | |
| AutoJavaFloatArray autoPos(env, jpositions, colors.size()); | |
| SkScalar* pos = autoPos.ptr(); | |
| sk_sp<SkShader> shader = SkGradientShader::MakeSweep(x, y, &colors[0], | |
| GraphicsJNI::getNativeColorSpace(colorSpaceHandle), pos, colors.size(), | |
| sGradientShaderFlags, nullptr); | |
| ThrowIAE_IfNull(env, shader); | |
| const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr); | |
| if (matrix) { | |
| shader = shader->makeWithLocalMatrix(*matrix); | |
| } | |
| return reinterpret_cast<jlong>(shader.release()); | |
| } | |
| /////////////////////////////////////////////////////////////////////////////////////////////// | |
| static jlong ComposeShader_create(JNIEnv* env, jobject o, jlong matrixPtr, | |
| jlong shaderAHandle, jlong shaderBHandle, jint xfermodeHandle) { | |
| const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr); | |
| SkShader* shaderA = reinterpret_cast<SkShader *>(shaderAHandle); | |
| SkShader* shaderB = reinterpret_cast<SkShader *>(shaderBHandle); | |
| SkBlendMode mode = static_cast<SkBlendMode>(xfermodeHandle); | |
| sk_sp<SkShader> baseShader(SkShaders::Blend(mode, | |
| sk_ref_sp(shaderA), sk_ref_sp(shaderB))); | |
| SkShader* shader; | |
| if (matrix) { | |
| shader = baseShader->makeWithLocalMatrix(*matrix).release(); | |
| } else { | |
| shader = baseShader.release(); | |
| } | |
| return reinterpret_cast<jlong>(shader); | |
| } | |
| /////////////////////////////////////////////////////////////////////////////////////////////// | |
| /////////////////////////////////////////////////////////////////////////////////////////////// | |
| static jlong RuntimeShader_createShaderBuilder(JNIEnv* env, jobject, jstring sksl) { | |
| ScopedUtfChars strSksl(env, sksl); | |
| auto result = SkRuntimeEffect::MakeForShader(SkString(strSksl.c_str()), | |
| SkRuntimeEffect::Options{}); | |
| if (result.effect.get() == nullptr) { | |
| doThrowIAE(env, result.errorText.c_str()); | |
| return 0; | |
| } | |
| return reinterpret_cast<jlong>(new SkRuntimeShaderBuilder(std::move(result.effect))); | |
| } | |
| static void SkRuntimeShaderBuilder_delete(SkRuntimeShaderBuilder* builder) { | |
| delete builder; | |
| } | |
| static jlong RuntimeShader_getNativeFinalizer(JNIEnv*, jobject) { | |
| return static_cast<jlong>(reinterpret_cast<uintptr_t>(&SkRuntimeShaderBuilder_delete)); | |
| } | |
| static jlong RuntimeShader_create(JNIEnv* env, jobject, jlong shaderBuilder, jlong matrixPtr) { | |
| SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilder); | |
| const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr); | |
| sk_sp<SkShader> shader = builder->makeShader(matrix); | |
| ThrowIAE_IfNull(env, shader); | |
| return reinterpret_cast<jlong>(shader.release()); | |
| } | |
| static inline int ThrowIAEFmt(JNIEnv* env, const char* fmt, ...) { | |
| va_list args; | |
| va_start(args, fmt); | |
| int ret = jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", fmt, args); | |
| va_end(args); | |
| return ret; | |
| } | |
| static bool isIntUniformType(const SkRuntimeEffect::Uniform::Type& type) { | |
| switch (type) { | |
| case SkRuntimeEffect::Uniform::Type::kFloat: | |
| case SkRuntimeEffect::Uniform::Type::kFloat2: | |
| case SkRuntimeEffect::Uniform::Type::kFloat3: | |
| case SkRuntimeEffect::Uniform::Type::kFloat4: | |
| case SkRuntimeEffect::Uniform::Type::kFloat2x2: | |
| case SkRuntimeEffect::Uniform::Type::kFloat3x3: | |
| case SkRuntimeEffect::Uniform::Type::kFloat4x4: | |
| return false; | |
| case SkRuntimeEffect::Uniform::Type::kInt: | |
| case SkRuntimeEffect::Uniform::Type::kInt2: | |
| case SkRuntimeEffect::Uniform::Type::kInt3: | |
| case SkRuntimeEffect::Uniform::Type::kInt4: | |
| return true; | |
| } | |
| } | |
| static void UpdateFloatUniforms(JNIEnv* env, SkRuntimeShaderBuilder* builder, | |
| const char* uniformName, const float values[], int count, | |
| bool isColor) { | |
| SkRuntimeShaderBuilder::BuilderUniform uniform = builder->uniform(uniformName); | |
| if (uniform.fVar == nullptr) { | |
| ThrowIAEFmt(env, "unable to find uniform named %s", uniformName); | |
| } else if (isColor != ((uniform.fVar->flags & SkRuntimeEffect::Uniform::kColor_Flag) != 0)) { | |
| if (isColor) { | |
| jniThrowExceptionFmt( | |
| env, "java/lang/IllegalArgumentException", | |
| "attempting to set a color uniform using the non-color specific APIs: %s %x", | |
| uniformName, uniform.fVar->flags); | |
| } else { | |
| ThrowIAEFmt(env, | |
| "attempting to set a non-color uniform using the setColorUniform APIs: %s", | |
| uniformName); | |
| } | |
| } else if (isIntUniformType(uniform.fVar->type)) { | |
| ThrowIAEFmt(env, "attempting to set a int uniform using the setUniform APIs: %s", | |
| uniformName); | |
| } else if (!uniform.set<float>(values, count)) { | |
| ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]", | |
| uniform.fVar->sizeInBytes(), sizeof(float) * count); | |
| } | |
| } | |
| static void RuntimeShader_updateFloatUniforms(JNIEnv* env, jobject, jlong shaderBuilder, | |
| jstring jUniformName, jfloat value1, jfloat value2, | |
| jfloat value3, jfloat value4, jint count) { | |
| SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilder); | |
| ScopedUtfChars name(env, jUniformName); | |
| const float values[4] = {value1, value2, value3, value4}; | |
| UpdateFloatUniforms(env, builder, name.c_str(), values, count, false); | |
| } | |
| static void RuntimeShader_updateFloatArrayUniforms(JNIEnv* env, jobject, jlong shaderBuilder, | |
| jstring jUniformName, jfloatArray jvalues, | |
| jboolean isColor) { | |
| SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilder); | |
| ScopedUtfChars name(env, jUniformName); | |
| AutoJavaFloatArray autoValues(env, jvalues, 0, kRO_JNIAccess); | |
| UpdateFloatUniforms(env, builder, name.c_str(), autoValues.ptr(), autoValues.length(), isColor); | |
| } | |
| static void UpdateIntUniforms(JNIEnv* env, SkRuntimeShaderBuilder* builder, const char* uniformName, | |
| const int values[], int count) { | |
| SkRuntimeShaderBuilder::BuilderUniform uniform = builder->uniform(uniformName); | |
| if (uniform.fVar == nullptr) { | |
| ThrowIAEFmt(env, "unable to find uniform named %s", uniformName); | |
| } else if (!isIntUniformType(uniform.fVar->type)) { | |
| ThrowIAEFmt(env, "attempting to set a non-int uniform using the setIntUniform APIs: %s", | |
| uniformName); | |
| } else if (!uniform.set<int>(values, count)) { | |
| ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]", | |
| uniform.fVar->sizeInBytes(), sizeof(float) * count); | |
| } | |
| } | |
| static void RuntimeShader_updateIntUniforms(JNIEnv* env, jobject, jlong shaderBuilder, | |
| jstring jUniformName, jint value1, jint value2, | |
| jint value3, jint value4, jint count) { | |
| SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilder); | |
| ScopedUtfChars name(env, jUniformName); | |
| const int values[4] = {value1, value2, value3, value4}; | |
| UpdateIntUniforms(env, builder, name.c_str(), values, count); | |
| } | |
| static void RuntimeShader_updateIntArrayUniforms(JNIEnv* env, jobject, jlong shaderBuilder, | |
| jstring jUniformName, jintArray jvalues) { | |
| SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilder); | |
| ScopedUtfChars name(env, jUniformName); | |
| AutoJavaIntArray autoValues(env, jvalues, 0); | |
| UpdateIntUniforms(env, builder, name.c_str(), autoValues.ptr(), autoValues.length()); | |
| } | |
| static void RuntimeShader_updateShader(JNIEnv* env, jobject, jlong shaderBuilder, | |
| jstring jUniformName, jlong shaderHandle) { | |
| SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilder); | |
| ScopedUtfChars name(env, jUniformName); | |
| SkShader* shader = reinterpret_cast<SkShader*>(shaderHandle); | |
| SkRuntimeShaderBuilder::BuilderChild child = builder->child(name.c_str()); | |
| if (child.fChild == nullptr) { | |
| ThrowIAEFmt(env, "unable to find shader named %s", name.c_str()); | |
| return; | |
| } | |
| builder->child(name.c_str()) = sk_ref_sp(shader); | |
| } | |
| /////////////////////////////////////////////////////////////////////////////////////////////// | |
| static const JNINativeMethod gColorMethods[] = { | |
| { "nativeRGBToHSV", "(III[F)V", (void*)Color_RGBToHSV }, | |
| { "nativeHSVToColor", "(I[F)I", (void*)Color_HSVToColor } | |
| }; | |
| static const JNINativeMethod gShaderMethods[] = { | |
| { "nativeGetFinalizer", "()J", (void*)Shader_getNativeFinalizer }, | |
| }; | |
| static const JNINativeMethod gBitmapShaderMethods[] = { | |
| {"nativeCreate", "(JJIIIZZJ)J", (void*)BitmapShader_constructor}, | |
| }; | |
| static const JNINativeMethod gLinearGradientMethods[] = { | |
| { "nativeCreate", "(JFFFF[J[FIJ)J", (void*)LinearGradient_create }, | |
| }; | |
| static const JNINativeMethod gRadialGradientMethods[] = { | |
| { "nativeCreate", "(JFFFFFF[J[FIJ)J", (void*)RadialGradient_create }, | |
| }; | |
| static const JNINativeMethod gSweepGradientMethods[] = { | |
| { "nativeCreate", "(JFF[J[FJ)J", (void*)SweepGradient_create }, | |
| }; | |
| static const JNINativeMethod gComposeShaderMethods[] = { | |
| { "nativeCreate", "(JJJI)J", (void*)ComposeShader_create }, | |
| }; | |
| static const JNINativeMethod gRuntimeShaderMethods[] = { | |
| {"nativeGetFinalizer", "()J", (void*)RuntimeShader_getNativeFinalizer}, | |
| {"nativeCreateShader", "(JJ)J", (void*)RuntimeShader_create}, | |
| {"nativeCreateBuilder", "(Ljava/lang/String;)J", (void*)RuntimeShader_createShaderBuilder}, | |
| {"nativeUpdateUniforms", "(JLjava/lang/String;[FZ)V", | |
| (void*)RuntimeShader_updateFloatArrayUniforms}, | |
| {"nativeUpdateUniforms", "(JLjava/lang/String;FFFFI)V", | |
| (void*)RuntimeShader_updateFloatUniforms}, | |
| {"nativeUpdateUniforms", "(JLjava/lang/String;[I)V", | |
| (void*)RuntimeShader_updateIntArrayUniforms}, | |
| {"nativeUpdateUniforms", "(JLjava/lang/String;IIIII)V", | |
| (void*)RuntimeShader_updateIntUniforms}, | |
| {"nativeUpdateShader", "(JLjava/lang/String;J)V", (void*)RuntimeShader_updateShader}, | |
| }; | |
| int register_android_graphics_Shader(JNIEnv* env) | |
| { | |
| android::RegisterMethodsOrDie(env, "android/graphics/Color", gColorMethods, | |
| NELEM(gColorMethods)); | |
| android::RegisterMethodsOrDie(env, "android/graphics/Shader", gShaderMethods, | |
| NELEM(gShaderMethods)); | |
| android::RegisterMethodsOrDie(env, "android/graphics/BitmapShader", gBitmapShaderMethods, | |
| NELEM(gBitmapShaderMethods)); | |
| android::RegisterMethodsOrDie(env, "android/graphics/LinearGradient", gLinearGradientMethods, | |
| NELEM(gLinearGradientMethods)); | |
| android::RegisterMethodsOrDie(env, "android/graphics/RadialGradient", gRadialGradientMethods, | |
| NELEM(gRadialGradientMethods)); | |
| android::RegisterMethodsOrDie(env, "android/graphics/SweepGradient", gSweepGradientMethods, | |
| NELEM(gSweepGradientMethods)); | |
| android::RegisterMethodsOrDie(env, "android/graphics/ComposeShader", gComposeShaderMethods, | |
| NELEM(gComposeShaderMethods)); | |
| android::RegisterMethodsOrDie(env, "android/graphics/RuntimeShader", gRuntimeShaderMethods, | |
| NELEM(gRuntimeShaderMethods)); | |
| return 0; | |
| } |