diff options
author | Derek Sollenberger <djsollen@google.com> | 2020-02-21 11:43:02 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2020-02-21 11:43:02 +0000 |
commit | 76e7430baead21c67fd4c81a36774a6461d2fb99 (patch) | |
tree | 239a62b59c57b4b95d8e82943bf9a48e84439819 /libs/hwui | |
parent | 1ab8e44c06ca05806a81c6737cb3f6e316d8d25a (diff) | |
parent | 2173ea286afff6766043227de0bc2d82d9595f77 (diff) |
Merge changes from topic "HWUI_JNI"
* changes:
Export symbols for the newly exposed APEX/internal headers
Remove dependence on libandroid_runtime from Bitmap.cpp
Update Region.cpp to use AParcel NDK APIs
Cleanup header and build targets for libhwui clients.
Remove dependencies on headers outside UI module
Cleanup LOG_TAG when bundled in HWUI
Move android.graphics JNI & APEX files into HWUI
Diffstat (limited to 'libs/hwui')
94 files changed, 18715 insertions, 6 deletions
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 81dedda5341d..ac2fd98248d0 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -152,9 +152,235 @@ cc_defaults { } // ------------------------ +// APEX +// ------------------------ + +cc_library_headers { + name: "android_graphics_apex_headers", + + host_supported: true, + export_include_dirs: [ + "apex/include", + ], + target: { + windows: { + enabled: true, + }, + } +} + +cc_defaults { + name: "android_graphics_apex", + host_supported: true, + cflags: [ + "-Wno-unused-parameter", + "-Wno-non-virtual-dtor", + "-Wno-maybe-uninitialized", + "-Wno-parentheses", + "-Wall", + "-Werror", + "-Wno-error=deprecated-declarations", + "-Wunused", + "-Wunreachable-code", + ], + + cppflags: ["-Wno-conversion-null"], + + srcs: [ + "apex/android_matrix.cpp", + "apex/android_paint.cpp", + "apex/android_region.cpp", + ], + + header_libs: [ "android_graphics_apex_headers" ], + + target: { + android: { + srcs: [ // sources that depend on android only libraries + "apex/android_bitmap.cpp", + "apex/android_canvas.cpp", + "apex/jni_runtime.cpp", + "apex/renderthread.cpp", + ], + }, + host: { + srcs: [ + "apex/LayoutlibLoader.cpp", + ], + } + }, +} + +// ------------------------ +// Android Graphics JNI +// ------------------------ + +cc_library_headers { + name: "android_graphics_jni_headers", + + host_supported: true, + export_include_dirs: [ + "jni", + ], + target: { + windows: { + enabled: true, + }, + } +} + +cc_defaults { + name: "android_graphics_jni", + host_supported: true, + cflags: [ + "-Wno-unused-parameter", + "-Wno-non-virtual-dtor", + "-Wno-maybe-uninitialized", + "-Wno-parentheses", + + "-DGL_GLEXT_PROTOTYPES", + "-DEGL_EGLEXT_PROTOTYPES", + + "-DU_USING_ICU_NAMESPACE=0", + + "-Wall", + "-Werror", + "-Wno-error=deprecated-declarations", + "-Wunused", + "-Wunreachable-code", + ], + + cppflags: ["-Wno-conversion-null"], + + srcs: [ + "jni/android_graphics_animation_NativeInterpolatorFactory.cpp", + "jni/android_graphics_animation_RenderNodeAnimator.cpp", + "jni/android_graphics_Canvas.cpp", + "jni/android_graphics_ColorSpace.cpp", + "jni/android_graphics_drawable_AnimatedVectorDrawable.cpp", + "jni/android_graphics_drawable_VectorDrawable.cpp", + "jni/android_graphics_HardwareRendererObserver.cpp", + "jni/android_graphics_Matrix.cpp", + "jni/android_graphics_Picture.cpp", + "jni/android_graphics_DisplayListCanvas.cpp", + "jni/android_graphics_RenderNode.cpp", + "jni/android_nio_utils.cpp", + "jni/android_util_PathParser.cpp", + + "jni/Bitmap.cpp", + "jni/BitmapFactory.cpp", + "jni/ByteBufferStreamAdaptor.cpp", + "jni/Camera.cpp", + "jni/CanvasProperty.cpp", + "jni/ColorFilter.cpp", + "jni/CreateJavaOutputStreamAdaptor.cpp", + "jni/FontFamily.cpp", + "jni/FontUtils.cpp", + "jni/Graphics.cpp", + "jni/ImageDecoder.cpp", + "jni/Interpolator.cpp", + "jni/MaskFilter.cpp", + "jni/NinePatch.cpp", + "jni/NinePatchPeeker.cpp", + "jni/Paint.cpp", + "jni/PaintFilter.cpp", + "jni/Path.cpp", + "jni/PathEffect.cpp", + "jni/PathMeasure.cpp", + "jni/Picture.cpp", + "jni/Shader.cpp", + "jni/Typeface.cpp", + "jni/Utils.cpp", + "jni/YuvToJpegEncoder.cpp", + "jni/fonts/Font.cpp", + "jni/fonts/FontFamily.cpp", + "jni/text/LineBreaker.cpp", + "jni/text/MeasuredText.cpp", + ], + + header_libs: [ "android_graphics_jni_headers" ], + + include_dirs: [ + "external/skia/include/private", + "external/skia/src/codec", + "external/skia/src/core", + "external/skia/src/effects", + "external/skia/src/image", + "external/skia/src/images", + ], + + shared_libs: [ + "libbase", + "libcutils", + "libharfbuzz_ng", + "liblog", + "libminikin", + "libnativehelper", + "libz", + "libziparchive", + "libjpeg", + ], + + target: { + android: { + srcs: [ // sources that depend on android only libraries + "jni/AnimatedImageDrawable.cpp", + "jni/android_graphics_TextureLayer.cpp", + "jni/android_graphics_HardwareRenderer.cpp", + "jni/BitmapRegionDecoder.cpp", + "jni/GIFMovie.cpp", + "jni/GraphicsStatsService.cpp", + "jni/Movie.cpp", + "jni/MovieImpl.cpp", + "jni/Region.cpp", // requires libbinder_ndk + "jni/pdf/PdfDocument.cpp", + "jni/pdf/PdfEditor.cpp", + "jni/pdf/PdfRenderer.cpp", + "jni/pdf/PdfUtils.cpp", + ], + shared_libs: [ + "libandroidfw", + "libbinder", + "libbinder_ndk", + "libmediandk", + "libnativedisplay", + "libnativewindow", + "libstatspull", + "libstatssocket", + "libpdfium", + ], + static_libs: [ + "libgif", + "libstatslog", + ], + }, + host: { + cflags: [ + "-Wno-unused-const-variable", + "-Wno-unused-function", + ], + static_libs: [ + "libandroidfw", + ], + } + }, +} + +// ------------------------ // library // ------------------------ +cc_library_headers { + name: "libhwui_internal_headers", + + host_supported: true, + export_include_dirs: [ + ".", + ], + header_libs: [ "android_graphics_jni_headers" ], + export_header_lib_headers: [ "android_graphics_jni_headers" ], +} + cc_defaults { name: "libhwui_defaults", defaults: ["hwui_defaults"], @@ -205,11 +431,8 @@ cc_defaults { export_proto_headers: true, }, - export_include_dirs: ["."], - target: { android: { - srcs: [ "hwui/AnimatedImageThread.cpp", "pipeline/skia/ATraceMemoryDump.cpp", @@ -274,7 +497,10 @@ cc_library { host_supported: true, defaults: [ "libhwui_defaults", + "android_graphics_apex", + "android_graphics_jni", ], + export_header_lib_headers: ["android_graphics_apex_headers"], } cc_library_static { diff --git a/libs/hwui/DeviceInfo.cpp b/libs/hwui/DeviceInfo.cpp index 6d4a0c6421d9..c24224cbbd67 100644 --- a/libs/hwui/DeviceInfo.cpp +++ b/libs/hwui/DeviceInfo.cpp @@ -18,9 +18,6 @@ #include <log/log.h> #include <utils/Errors.h> -#include <mutex> -#include <thread> - #include "Properties.h" namespace android { diff --git a/libs/hwui/apex/LayoutlibLoader.cpp b/libs/hwui/apex/LayoutlibLoader.cpp new file mode 100644 index 000000000000..4bbf1214bdcf --- /dev/null +++ b/libs/hwui/apex/LayoutlibLoader.cpp @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "graphics_jni_helpers.h" + +#include <GraphicsJNI.h> +#include <SkGraphics.h> + +#include <sstream> +#include <iostream> +#include <unicode/putil.h> +#include <unordered_map> +#include <vector> + +using namespace std; + +/* + * This is responsible for setting up the JNI environment for communication between + * the Java and native parts of layoutlib, including registering native methods. + * This is mostly achieved by copying the way it is done in the platform + * (see AndroidRuntime.cpp). + */ + +static JavaVM* javaVM; + +extern int register_android_graphics_Bitmap(JNIEnv*); +extern int register_android_graphics_BitmapFactory(JNIEnv*); +extern int register_android_graphics_ByteBufferStreamAdaptor(JNIEnv* env); +extern int register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv* env); +extern int register_android_graphics_Graphics(JNIEnv* env); +extern int register_android_graphics_ImageDecoder(JNIEnv*); +extern int register_android_graphics_Interpolator(JNIEnv* env); +extern int register_android_graphics_MaskFilter(JNIEnv* env); +extern int register_android_graphics_NinePatch(JNIEnv*); +extern int register_android_graphics_PathEffect(JNIEnv* env); +extern int register_android_graphics_Shader(JNIEnv* env); +extern int register_android_graphics_Typeface(JNIEnv* env); + +namespace android { + +extern int register_android_graphics_Canvas(JNIEnv* env); +extern int register_android_graphics_ColorFilter(JNIEnv* env); +extern int register_android_graphics_ColorSpace(JNIEnv* env); +extern int register_android_graphics_DrawFilter(JNIEnv* env); +extern int register_android_graphics_FontFamily(JNIEnv* env); +extern int register_android_graphics_Matrix(JNIEnv* env); +extern int register_android_graphics_Paint(JNIEnv* env); +extern int register_android_graphics_Path(JNIEnv* env); +extern int register_android_graphics_PathMeasure(JNIEnv* env); +extern int register_android_graphics_Picture(JNIEnv* env); +//extern int register_android_graphics_Region(JNIEnv* env); +extern int register_android_graphics_animation_NativeInterpolatorFactory(JNIEnv* env); +extern int register_android_graphics_animation_RenderNodeAnimator(JNIEnv* env); +extern int register_android_graphics_drawable_AnimatedVectorDrawable(JNIEnv* env); +extern int register_android_graphics_drawable_VectorDrawable(JNIEnv* env); +extern int register_android_graphics_fonts_Font(JNIEnv* env); +extern int register_android_graphics_fonts_FontFamily(JNIEnv* env); +extern int register_android_graphics_text_LineBreaker(JNIEnv* env); +extern int register_android_graphics_text_MeasuredText(JNIEnv* env); +extern int register_android_util_PathParser(JNIEnv* env); +extern int register_android_view_RenderNode(JNIEnv* env); +extern int register_android_view_DisplayListCanvas(JNIEnv* env); + +#define REG_JNI(name) { name } +struct RegJNIRec { + int (*mProc)(JNIEnv*); +}; + +// Map of all possible class names to register to their corresponding JNI registration function pointer +// The actual list of registered classes will be determined at runtime via the 'native_classes' System property +static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = { + {"android.graphics.Bitmap", REG_JNI(register_android_graphics_Bitmap)}, + {"android.graphics.BitmapFactory", REG_JNI(register_android_graphics_BitmapFactory)}, + {"android.graphics.ByteBufferStreamAdaptor", + REG_JNI(register_android_graphics_ByteBufferStreamAdaptor)}, + {"android.graphics.Canvas", REG_JNI(register_android_graphics_Canvas)}, + {"android.graphics.RenderNode", REG_JNI(register_android_view_RenderNode)}, + {"android.graphics.ColorFilter", REG_JNI(register_android_graphics_ColorFilter)}, + {"android.graphics.ColorSpace", REG_JNI(register_android_graphics_ColorSpace)}, + {"android.graphics.CreateJavaOutputStreamAdaptor", + REG_JNI(register_android_graphics_CreateJavaOutputStreamAdaptor)}, + {"android.graphics.DrawFilter", REG_JNI(register_android_graphics_DrawFilter)}, + {"android.graphics.FontFamily", REG_JNI(register_android_graphics_FontFamily)}, + {"android.graphics.Graphics", REG_JNI(register_android_graphics_Graphics)}, + {"android.graphics.ImageDecoder", REG_JNI(register_android_graphics_ImageDecoder)}, + {"android.graphics.Interpolator", REG_JNI(register_android_graphics_Interpolator)}, + {"android.graphics.MaskFilter", REG_JNI(register_android_graphics_MaskFilter)}, + {"android.graphics.Matrix", REG_JNI(register_android_graphics_Matrix)}, + {"android.graphics.NinePatch", REG_JNI(register_android_graphics_NinePatch)}, + {"android.graphics.Paint", REG_JNI(register_android_graphics_Paint)}, + {"android.graphics.Path", REG_JNI(register_android_graphics_Path)}, + {"android.graphics.PathEffect", REG_JNI(register_android_graphics_PathEffect)}, + {"android.graphics.PathMeasure", REG_JNI(register_android_graphics_PathMeasure)}, + {"android.graphics.Picture", REG_JNI(register_android_graphics_Picture)}, + {"android.graphics.RecordingCanvas", REG_JNI(register_android_view_DisplayListCanvas)}, +// {"android.graphics.Region", REG_JNI(register_android_graphics_Region)}, + {"android.graphics.Shader", REG_JNI(register_android_graphics_Shader)}, + {"android.graphics.Typeface", REG_JNI(register_android_graphics_Typeface)}, + {"android.graphics.animation.NativeInterpolatorFactory", + REG_JNI(register_android_graphics_animation_NativeInterpolatorFactory)}, + {"android.graphics.animation.RenderNodeAnimator", + REG_JNI(register_android_graphics_animation_RenderNodeAnimator)}, + {"android.graphics.drawable.AnimatedVectorDrawable", + REG_JNI(register_android_graphics_drawable_AnimatedVectorDrawable)}, + {"android.graphics.drawable.VectorDrawable", + REG_JNI(register_android_graphics_drawable_VectorDrawable)}, + {"android.graphics.fonts.Font", REG_JNI(register_android_graphics_fonts_Font)}, + {"android.graphics.fonts.FontFamily", REG_JNI(register_android_graphics_fonts_FontFamily)}, + {"android.graphics.text.LineBreaker", REG_JNI(register_android_graphics_text_LineBreaker)}, + {"android.graphics.text.MeasuredText", + REG_JNI(register_android_graphics_text_MeasuredText)}, + {"android.util.PathParser", REG_JNI(register_android_util_PathParser)}, +}; + +static int register_jni_procs(const std::unordered_map<std::string, RegJNIRec>& jniRegMap, + const vector<string>& classesToRegister, JNIEnv* env) { + + for (const string& className : classesToRegister) { + if (jniRegMap.at(className).mProc(env) < 0) { + return -1; + } + } + return 0; +} + +static vector<string> parseCsv(const string& csvString) { + vector<string> result; + istringstream stream(csvString); + string segment; + while(getline(stream, segment, ',')) + { + result.push_back(segment); + } + return result; +} + +static vector<string> parseCsv(JNIEnv* env, jstring csvJString) { + const char* charArray = env->GetStringUTFChars(csvJString, 0); + string csvString(charArray); + vector<string> result = parseCsv(csvString); + env->ReleaseStringUTFChars(csvJString, charArray); + return result; +} + +} // namespace android + +using namespace android; + +void init_android_graphics() { + SkGraphics::Init(); +} + +int register_android_graphics_classes(JNIEnv *env) { + JavaVM* vm = nullptr; + env->GetJavaVM(&vm); + GraphicsJNI::setJavaVM(vm); + + // Configuration is stored as java System properties. + // Get a reference to System.getProperty + jclass system = FindClassOrDie(env, "java/lang/System"); + jmethodID getPropertyMethod = GetStaticMethodIDOrDie(env, system, "getProperty", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"); + + // Get the names of classes that need to register their native methods + auto nativesClassesJString = + (jstring) env->CallStaticObjectMethod(system, + getPropertyMethod, env->NewStringUTF("native_classes"), + env->NewStringUTF("")); + vector<string> classesToRegister = parseCsv(env, nativesClassesJString); + + if (register_jni_procs(gRegJNIMap, classesToRegister, env) < 0) { + return JNI_ERR; + } + + return 0; +} + +void zygote_preload_graphics() { } diff --git a/libs/hwui/apex/TypeCast.h b/libs/hwui/apex/TypeCast.h new file mode 100644 index 000000000000..96721d007951 --- /dev/null +++ b/libs/hwui/apex/TypeCast.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_GRAPHICS_TYPECAST_H +#define ANDROID_GRAPHICS_TYPECAST_H + +struct ABitmap; +struct ACanvas; +struct APaint; + +namespace android { + + class Bitmap; + class Canvas; + class Paint; + + class TypeCast { + public: + static inline Bitmap& toBitmapRef(const ABitmap* bitmap) { + return const_cast<Bitmap&>(reinterpret_cast<const Bitmap&>(*bitmap)); + } + + static inline Bitmap* toBitmap(ABitmap* bitmap) { + return reinterpret_cast<Bitmap*>(bitmap); + } + + static inline ABitmap* toABitmap(Bitmap* bitmap) { + return reinterpret_cast<ABitmap*>(bitmap); + } + + static inline Canvas* toCanvas(ACanvas* canvas) { + return reinterpret_cast<Canvas*>(canvas); + } + + static inline ACanvas* toACanvas(Canvas* canvas) { + return reinterpret_cast<ACanvas *>(canvas); + } + + static inline const Paint& toPaintRef(const APaint* paint) { + return reinterpret_cast<const Paint&>(*paint); + } + + static inline const Paint* toPaint(const APaint* paint) { + return reinterpret_cast<const Paint*>(paint); + } + + static inline Paint* toPaint(APaint* paint) { + return reinterpret_cast<Paint*>(paint); + } + + static inline APaint* toAPaint(Paint* paint) { + return reinterpret_cast<APaint*>(paint); + } + }; +}; // namespace android + +#endif // ANDROID_GRAPHICS_TYPECAST_H diff --git a/libs/hwui/apex/android_bitmap.cpp b/libs/hwui/apex/android_bitmap.cpp new file mode 100644 index 000000000000..b56a619b00aa --- /dev/null +++ b/libs/hwui/apex/android_bitmap.cpp @@ -0,0 +1,305 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#undef LOG_TAG +#define LOG_TAG "Bitmap" +#include <log/log.h> + +#include "android/graphics/bitmap.h" +#include "TypeCast.h" +#include "GraphicsJNI.h" + +#include <GraphicsJNI.h> +#include <hwui/Bitmap.h> +#include <utils/Color.h> + +using namespace android; + +ABitmap* ABitmap_acquireBitmapFromJava(JNIEnv* env, jobject bitmapObj) { + Bitmap* bitmap = GraphicsJNI::getNativeBitmap(env, bitmapObj); + if (bitmap) { + bitmap->ref(); + return TypeCast::toABitmap(bitmap); + } + return nullptr; +} + +void ABitmap_acquireRef(ABitmap* bitmap) { + SkSafeRef(TypeCast::toBitmap(bitmap)); +} + +void ABitmap_releaseRef(ABitmap* bitmap) { + SkSafeUnref(TypeCast::toBitmap(bitmap)); +} + +static AndroidBitmapFormat getFormat(const SkImageInfo& info) { + switch (info.colorType()) { + case kN32_SkColorType: + return ANDROID_BITMAP_FORMAT_RGBA_8888; + case kRGB_565_SkColorType: + return ANDROID_BITMAP_FORMAT_RGB_565; + case kARGB_4444_SkColorType: + return ANDROID_BITMAP_FORMAT_RGBA_4444; + case kAlpha_8_SkColorType: + return ANDROID_BITMAP_FORMAT_A_8; + case kRGBA_F16_SkColorType: + return ANDROID_BITMAP_FORMAT_RGBA_F16; + default: + return ANDROID_BITMAP_FORMAT_NONE; + } +} + +static SkColorType getColorType(AndroidBitmapFormat format) { + switch (format) { + case ANDROID_BITMAP_FORMAT_RGBA_8888: + return kN32_SkColorType; + case ANDROID_BITMAP_FORMAT_RGB_565: + return kRGB_565_SkColorType; + case ANDROID_BITMAP_FORMAT_RGBA_4444: + return kARGB_4444_SkColorType; + case ANDROID_BITMAP_FORMAT_A_8: + return kAlpha_8_SkColorType; + case ANDROID_BITMAP_FORMAT_RGBA_F16: + return kRGBA_F16_SkColorType; + default: + return kUnknown_SkColorType; + } +} + +static uint32_t getAlphaFlags(const SkImageInfo& info) { + switch (info.alphaType()) { + case kUnknown_SkAlphaType: + LOG_ALWAYS_FATAL("Bitmap has no alpha type"); + break; + case kOpaque_SkAlphaType: + return ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE; + case kPremul_SkAlphaType: + return ANDROID_BITMAP_FLAGS_ALPHA_PREMUL; + case kUnpremul_SkAlphaType: + return ANDROID_BITMAP_FLAGS_ALPHA_UNPREMUL; + } +} + +static uint32_t getInfoFlags(const SkImageInfo& info, bool isHardware) { + uint32_t flags = getAlphaFlags(info); + if (isHardware) { + flags |= ANDROID_BITMAP_FLAGS_IS_HARDWARE; + } + return flags; +} + +ABitmap* ABitmap_copy(ABitmap* srcBitmapHandle, AndroidBitmapFormat dstFormat) { + SkColorType dstColorType = getColorType(dstFormat); + if (srcBitmapHandle && dstColorType != kUnknown_SkColorType) { + SkBitmap srcBitmap; + TypeCast::toBitmap(srcBitmapHandle)->getSkBitmap(&srcBitmap); + + sk_sp<Bitmap> dstBitmap = + Bitmap::allocateHeapBitmap(srcBitmap.info().makeColorType(dstColorType)); + if (dstBitmap && srcBitmap.readPixels(dstBitmap->info(), dstBitmap->pixels(), + dstBitmap->rowBytes(), 0, 0)) { + return TypeCast::toABitmap(dstBitmap.release()); + } + } + return nullptr; +} + +static AndroidBitmapInfo getInfo(const SkImageInfo& imageInfo, uint32_t rowBytes, bool isHardware) { + AndroidBitmapInfo info; + info.width = imageInfo.width(); + info.height = imageInfo.height(); + info.stride = rowBytes; + info.format = getFormat(imageInfo); + info.flags = getInfoFlags(imageInfo, isHardware); + return info; +} + +AndroidBitmapInfo ABitmap_getInfo(ABitmap* bitmapHandle) { + Bitmap* bitmap = TypeCast::toBitmap(bitmapHandle); + return getInfo(bitmap->info(), bitmap->rowBytes(), bitmap->isHardware()); +} + +ADataSpace ABitmap_getDataSpace(ABitmap* bitmapHandle) { + Bitmap* bitmap = TypeCast::toBitmap(bitmapHandle); + const SkImageInfo& info = bitmap->info(); + return (ADataSpace)uirenderer::ColorSpaceToADataSpace(info.colorSpace(), info.colorType()); +} + +AndroidBitmapInfo ABitmap_getInfoFromJava(JNIEnv* env, jobject bitmapObj) { + uint32_t rowBytes = 0; + bool isHardware = false; + SkImageInfo imageInfo = GraphicsJNI::getBitmapInfo(env, bitmapObj, &rowBytes, &isHardware); + return getInfo(imageInfo, rowBytes, isHardware); +} + +void* ABitmap_getPixels(ABitmap* bitmapHandle) { + Bitmap* bitmap = TypeCast::toBitmap(bitmapHandle); + if (bitmap->isHardware()) { + return nullptr; + } + return bitmap->pixels(); +} + +AndroidBitmapFormat ABitmapConfig_getFormatFromConfig(JNIEnv* env, jobject bitmapConfigObj) { + return GraphicsJNI::getFormatFromConfig(env, bitmapConfigObj); +} + +jobject ABitmapConfig_getConfigFromFormat(JNIEnv* env, AndroidBitmapFormat format) { + return GraphicsJNI::getConfigFromFormat(env, format); +} + +void ABitmap_notifyPixelsChanged(ABitmap* bitmapHandle) { + Bitmap* bitmap = TypeCast::toBitmap(bitmapHandle); + if (bitmap->isImmutable()) { + ALOGE("Attempting to modify an immutable Bitmap!"); + } + return bitmap->notifyPixelsChanged(); +} + +namespace { +SkAlphaType getAlphaType(const AndroidBitmapInfo* info) { + switch (info->flags & ANDROID_BITMAP_FLAGS_ALPHA_MASK) { + case ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE: + return kOpaque_SkAlphaType; + case ANDROID_BITMAP_FLAGS_ALPHA_PREMUL: + return kPremul_SkAlphaType; + case ANDROID_BITMAP_FLAGS_ALPHA_UNPREMUL: + return kUnpremul_SkAlphaType; + default: + return kUnknown_SkAlphaType; + } +} + +class CompressWriter : public SkWStream { +public: + CompressWriter(void* userContext, AndroidBitmap_CompressWriteFunc fn) + : mUserContext(userContext), mFn(fn), mBytesWritten(0) {} + + bool write(const void* buffer, size_t size) override { + if (mFn(mUserContext, buffer, size)) { + mBytesWritten += size; + return true; + } + return false; + } + + size_t bytesWritten() const override { return mBytesWritten; } + +private: + void* mUserContext; + AndroidBitmap_CompressWriteFunc mFn; + size_t mBytesWritten; +}; + +} // anonymous namespace + +int ABitmap_compress(const AndroidBitmapInfo* info, ADataSpace dataSpace, const void* pixels, + AndroidBitmapCompressFormat inFormat, int32_t quality, void* userContext, + AndroidBitmap_CompressWriteFunc fn) { + Bitmap::JavaCompressFormat format; + switch (inFormat) { + case ANDROID_BITMAP_COMPRESS_FORMAT_JPEG: + format = Bitmap::JavaCompressFormat::Jpeg; + break; + case ANDROID_BITMAP_COMPRESS_FORMAT_PNG: + format = Bitmap::JavaCompressFormat::Png; + break; + case ANDROID_BITMAP_COMPRESS_FORMAT_WEBP_LOSSY: + format = Bitmap::JavaCompressFormat::WebpLossy; + break; + case ANDROID_BITMAP_COMPRESS_FORMAT_WEBP_LOSSLESS: + format = Bitmap::JavaCompressFormat::WebpLossless; + break; + default: + // kWEBP_JavaEncodeFormat is a valid parameter for Bitmap::compress, + // for the deprecated Bitmap.CompressFormat.WEBP, but it should not + // be provided via the NDK. Other integers are likewise invalid. + return ANDROID_BITMAP_RESULT_BAD_PARAMETER; + } + + SkColorType colorType; + switch (info->format) { + case ANDROID_BITMAP_FORMAT_RGBA_8888: + colorType = kN32_SkColorType; + break; + case ANDROID_BITMAP_FORMAT_RGB_565: + colorType = kRGB_565_SkColorType; + break; + case ANDROID_BITMAP_FORMAT_A_8: + // FIXME b/146637821: Should this encode as grayscale? We should + // make the same decision as for encoding an android.graphics.Bitmap. + // Note that encoding kAlpha_8 as WebP or JPEG will fail. Encoding + // it to PNG encodes as GRAY+ALPHA with a secret handshake that we + // only care about the alpha. I'm not sure whether Android decoding + // APIs respect that handshake. + colorType = kAlpha_8_SkColorType; + break; + case ANDROID_BITMAP_FORMAT_RGBA_F16: + colorType = kRGBA_F16_SkColorType; + break; + default: + return ANDROID_BITMAP_RESULT_BAD_PARAMETER; + } + + auto alphaType = getAlphaType(info); + if (alphaType == kUnknown_SkAlphaType) { + return ANDROID_BITMAP_RESULT_BAD_PARAMETER; + } + + sk_sp<SkColorSpace> cs; + if (info->format == ANDROID_BITMAP_FORMAT_A_8) { + // FIXME: A Java Bitmap with ALPHA_8 never has a ColorSpace. So should + // we force that here (as I'm doing now) or should we treat anything + // besides ADATASPACE_UNKNOWN as an error? + cs = nullptr; + } else { + cs = uirenderer::DataSpaceToColorSpace((android_dataspace) dataSpace); + // DataSpaceToColorSpace treats UNKNOWN as SRGB, but compress forces the + // client to specify SRGB if that is what they want. + if (!cs || dataSpace == ADATASPACE_UNKNOWN) { + return ANDROID_BITMAP_RESULT_BAD_PARAMETER; + } + } + + { + size_t size; + if (!Bitmap::computeAllocationSize(info->stride, info->height, &size)) { + return ANDROID_BITMAP_RESULT_BAD_PARAMETER; + } + } + + auto imageInfo = + SkImageInfo::Make(info->width, info->height, colorType, alphaType, std::move(cs)); + SkBitmap bitmap; + // We are not going to modify the pixels, but installPixels expects them to + // not be const, since for all it knows we might want to draw to the SkBitmap. + if (!bitmap.installPixels(imageInfo, const_cast<void*>(pixels), info->stride)) { + return ANDROID_BITMAP_RESULT_BAD_PARAMETER; + } + + CompressWriter stream(userContext, fn); + return Bitmap::compress(bitmap, format, quality, &stream) ? ANDROID_BITMAP_RESULT_SUCCESS + : ANDROID_BITMAP_RESULT_JNI_EXCEPTION; +} + +AHardwareBuffer* ABitmap_getHardwareBuffer(ABitmap* bitmapHandle) { + Bitmap* bitmap = TypeCast::toBitmap(bitmapHandle); + AHardwareBuffer* buffer = bitmap->hardwareBuffer(); + if (buffer) { + AHardwareBuffer_acquire(buffer); + } + return buffer; +} diff --git a/libs/hwui/apex/android_canvas.cpp b/libs/hwui/apex/android_canvas.cpp new file mode 100644 index 000000000000..2a939efed9bb --- /dev/null +++ b/libs/hwui/apex/android_canvas.cpp @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "android/graphics/canvas.h" + +#include "TypeCast.h" +#include "GraphicsJNI.h" + +#include <hwui/Canvas.h> +#include <utils/Color.h> + +#include <SkBitmap.h> +#include <SkSurface.h> + +using namespace android; + +/* + * Converts a buffer and dataspace into an SkBitmap only if the resulting bitmap can be treated as a + * rendering destination for a Canvas. If the buffer is null or the format is one that we cannot + * render into with a Canvas then false is returned and the outBitmap param is unmodified. + */ +static bool convert(const ANativeWindow_Buffer* buffer, + int32_t /*android_dataspace_t*/ dataspace, + SkBitmap* outBitmap) { + if (buffer == nullptr) { + return false; + } + + sk_sp<SkColorSpace> cs(uirenderer::DataSpaceToColorSpace((android_dataspace)dataspace)); + SkImageInfo imageInfo = uirenderer::ANativeWindowToImageInfo(*buffer, cs); + size_t rowBytes = buffer->stride * imageInfo.bytesPerPixel(); + + // If SkSurface::MakeRasterDirect fails then we should as well as we will not be able to + // draw into the canvas. + sk_sp<SkSurface> surface = SkSurface::MakeRasterDirect(imageInfo, buffer->bits, rowBytes); + if (surface.get() != nullptr) { + if (outBitmap) { + outBitmap->setInfo(imageInfo, rowBytes); + outBitmap->setPixels(buffer->bits); + } + return true; + } + return false; +} + +bool ACanvas_isSupportedPixelFormat(int32_t bufferFormat) { + char pixels[8]; + ANativeWindow_Buffer buffer { 1, 1, 1, bufferFormat, pixels, {0} }; + return convert(&buffer, HAL_DATASPACE_UNKNOWN, nullptr); +} + +ACanvas* ACanvas_getNativeHandleFromJava(JNIEnv* env, jobject canvasObj) { + return TypeCast::toACanvas(GraphicsJNI::getNativeCanvas(env, canvasObj)); +} + +ACanvas* ACanvas_createCanvas(const ANativeWindow_Buffer* buffer, + int32_t /*android_dataspace_t*/ dataspace) { + SkBitmap bitmap; + bool isValidBuffer = convert(buffer, dataspace, &bitmap); + return isValidBuffer ? TypeCast::toACanvas(Canvas::create_canvas(bitmap)) : nullptr; +} + +void ACanvas_destroyCanvas(ACanvas* canvas) { + delete TypeCast::toCanvas(canvas); +} + +bool ACanvas_setBuffer(ACanvas* canvas, const ANativeWindow_Buffer* buffer, + int32_t /*android_dataspace_t*/ dataspace) { + SkBitmap bitmap; + bool isValidBuffer = (buffer == nullptr) ? false : convert(buffer, dataspace, &bitmap); + TypeCast::toCanvas(canvas)->setBitmap(bitmap); + return isValidBuffer; +} + +void ACanvas_clipRect(ACanvas* canvas, const ARect* clipRect, bool /*doAA*/) { + //TODO update Canvas to take antialias param + TypeCast::toCanvas(canvas)->clipRect(clipRect->left, clipRect->top, clipRect->right, + clipRect->bottom, SkClipOp::kIntersect); +} + +void ACanvas_clipOutRect(ACanvas* canvas, const ARect* clipRect, bool /*doAA*/) { + //TODO update Canvas to take antialias param + TypeCast::toCanvas(canvas)->clipRect(clipRect->left, clipRect->top, clipRect->right, + clipRect->bottom, SkClipOp::kDifference); +} + +void ACanvas_drawRect(ACanvas* canvas, const ARect* rect, const APaint* paint) { + TypeCast::toCanvas(canvas)->drawRect(rect->left, rect->top, rect->right, rect->bottom, + TypeCast::toPaintRef(paint)); +} + +void ACanvas_drawBitmap(ACanvas* canvas, const ABitmap* bitmap, float left, float top, + const APaint* paint) { + TypeCast::toCanvas(canvas)->drawBitmap(TypeCast::toBitmapRef(bitmap), left, top, + TypeCast::toPaint(paint)); +} diff --git a/libs/hwui/apex/android_matrix.cpp b/libs/hwui/apex/android_matrix.cpp new file mode 100644 index 000000000000..693b22b62663 --- /dev/null +++ b/libs/hwui/apex/android_matrix.cpp @@ -0,0 +1,37 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "android/graphics/matrix.h" +#include "android_graphics_Matrix.h" + +bool AMatrix_getContents(JNIEnv* env, jobject matrixObj, float values[9]) { + static_assert(SkMatrix::kMScaleX == 0, "SkMatrix unexpected index"); + static_assert(SkMatrix::kMSkewX == 1, "SkMatrix unexpected index"); + static_assert(SkMatrix::kMTransX == 2, "SkMatrix unexpected index"); + static_assert(SkMatrix::kMSkewY == 3, "SkMatrix unexpected index"); + static_assert(SkMatrix::kMScaleY == 4, "SkMatrix unexpected index"); + static_assert(SkMatrix::kMTransY == 5, "SkMatrix unexpected index"); + static_assert(SkMatrix::kMPersp0 == 6, "SkMatrix unexpected index"); + static_assert(SkMatrix::kMPersp1 == 7, "SkMatrix unexpected index"); + static_assert(SkMatrix::kMPersp2 == 8, "SkMatrix unexpected index"); + + SkMatrix* m = android::android_graphics_Matrix_getSkMatrix(env, matrixObj); + if (m != nullptr) { + m->get9(values); + return true; + } + return false; +} diff --git a/libs/hwui/apex/android_paint.cpp b/libs/hwui/apex/android_paint.cpp new file mode 100644 index 000000000000..70bd085343ce --- /dev/null +++ b/libs/hwui/apex/android_paint.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "android/graphics/paint.h" + +#include "TypeCast.h" + +#include <hwui/Paint.h> + +using namespace android; + + +APaint* APaint_createPaint() { + return TypeCast::toAPaint(new Paint()); +} + +void APaint_destroyPaint(APaint* paint) { + delete TypeCast::toPaint(paint); +} + +static SkBlendMode convertBlendMode(ABlendMode blendMode) { + switch (blendMode) { + case ABLEND_MODE_CLEAR: + return SkBlendMode::kClear; + case ABLEND_MODE_SRC_OVER: + return SkBlendMode::kSrcOver; + case ABLEND_MODE_SRC: + return SkBlendMode::kSrc; + } +} + +void APaint_setBlendMode(APaint* paint, ABlendMode blendMode) { + TypeCast::toPaint(paint)->setBlendMode(convertBlendMode(blendMode)); +} diff --git a/libs/hwui/apex/android_region.cpp b/libs/hwui/apex/android_region.cpp new file mode 100644 index 000000000000..2030e7e69861 --- /dev/null +++ b/libs/hwui/apex/android_region.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "android/graphics/region.h" + +#include "GraphicsJNI.h" + +#include <SkRegion.h> + +static inline SkRegion::Iterator* ARegionIter_to_SkRegionIter(ARegionIterator* iterator) { + return reinterpret_cast<SkRegion::Iterator*>(iterator); +} + +static inline ARegionIterator* SkRegionIter_to_ARegionIter(SkRegion::Iterator* iterator) { + return reinterpret_cast<ARegionIterator*>(iterator); +} + +ARegionIterator* ARegionIterator_acquireIterator(JNIEnv* env, jobject regionObj) { + SkRegion* region = GraphicsJNI::getNativeRegion(env, regionObj); + return (!region) ? nullptr : SkRegionIter_to_ARegionIter(new SkRegion::Iterator(*region)); +} + +void ARegionIterator_releaseIterator(ARegionIterator* iterator) { + delete ARegionIter_to_SkRegionIter(iterator); +} + +bool ARegionIterator_isComplex(ARegionIterator* iterator) { + return ARegionIter_to_SkRegionIter(iterator)->rgn()->isComplex(); +} + +bool ARegionIterator_isDone(ARegionIterator* iterator) { + return ARegionIter_to_SkRegionIter(iterator)->done(); +} + +void ARegionIterator_next(ARegionIterator* iterator) { + ARegionIter_to_SkRegionIter(iterator)->next(); +} + +ARect ARegionIterator_getRect(ARegionIterator* iterator) { + const SkIRect& rect = ARegionIter_to_SkRegionIter(iterator)->rect(); + return { rect.fLeft, rect.fTop, rect.fRight, rect.fBottom }; +} + +ARect ARegionIterator_getTotalBounds(ARegionIterator* iterator) { + const SkIRect& bounds = ARegionIter_to_SkRegionIter(iterator)->rgn()->getBounds(); + return { bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom }; +} diff --git a/libs/hwui/apex/include/android/graphics/bitmap.h b/libs/hwui/apex/include/android/graphics/bitmap.h new file mode 100644 index 000000000000..8c4b439d2a2b --- /dev/null +++ b/libs/hwui/apex/include/android/graphics/bitmap.h @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef ANDROID_GRAPHICS_BITMAP_H +#define ANDROID_GRAPHICS_BITMAP_H + +#include <android/bitmap.h> +#include <android/data_space.h> +#include <cutils/compiler.h> +#include <jni.h> +#include <sys/cdefs.h> + +struct AHardwareBuffer; + +__BEGIN_DECLS + +/** + * Opaque handle for a native graphics bitmap. + */ +typedef struct ABitmap ABitmap; + +/** + * Retrieve bitmapInfo for the provided java bitmap even if it has been recycled. In the case of a + * recycled bitmap the values contained in the bitmap before it was recycled are returned. + * + * NOTE: This API does not need to remain as an APEX API if/when we pull libjnigraphics into the + * UI module. + */ +ANDROID_API AndroidBitmapInfo ABitmap_getInfoFromJava(JNIEnv* env, jobject bitmapObj); + +/** + * + * @return ptr to an opaque handle to the native bitmap or null if the java bitmap has been recycled + * or does not exist. + */ +ANDROID_API ABitmap* ABitmap_acquireBitmapFromJava(JNIEnv* env, jobject bitmapObj); + +ANDROID_API ABitmap* ABitmap_copy(ABitmap* srcBitmap, AndroidBitmapFormat dstFormat); + +ANDROID_API void ABitmap_acquireRef(ABitmap* bitmap); +ANDROID_API void ABitmap_releaseRef(ABitmap* bitmap); + +ANDROID_API AndroidBitmapInfo ABitmap_getInfo(ABitmap* bitmap); +ANDROID_API ADataSpace ABitmap_getDataSpace(ABitmap* bitmap); + +ANDROID_API void* ABitmap_getPixels(ABitmap* bitmap); +ANDROID_API void ABitmap_notifyPixelsChanged(ABitmap* bitmap); + +ANDROID_API AndroidBitmapFormat ABitmapConfig_getFormatFromConfig(JNIEnv* env, jobject bitmapConfigObj); +ANDROID_API jobject ABitmapConfig_getConfigFromFormat(JNIEnv* env, AndroidBitmapFormat format); + +// NDK access +ANDROID_API int ABitmap_compress(const AndroidBitmapInfo* info, ADataSpace dataSpace, const void* pixels, + AndroidBitmapCompressFormat format, int32_t quality, void* userContext, + AndroidBitmap_CompressWriteFunc); +/** + * Retrieve the native object associated with a HARDWARE Bitmap. + * + * Client must not modify it while a Bitmap is wrapping it. + * + * @param bitmap Handle to an android.graphics.Bitmap. + * @return on success, a pointer to the + * AHardwareBuffer associated with bitmap. This acquires + * a reference on the buffer, and the client must call + * AHardwareBuffer_release when finished with it. + */ +ANDROID_API AHardwareBuffer* ABitmap_getHardwareBuffer(ABitmap* bitmap); + +__END_DECLS + +#ifdef __cplusplus +namespace android { +namespace graphics { + class Bitmap { + public: + Bitmap() : mBitmap(nullptr) {} + Bitmap(JNIEnv* env, jobject bitmapObj) : + mBitmap(ABitmap_acquireBitmapFromJava(env, bitmapObj)) {} + Bitmap(const Bitmap& src) : mBitmap(src.mBitmap) { ABitmap_acquireRef(src.mBitmap); } + ~Bitmap() { ABitmap_releaseRef(mBitmap); } + + // copy operator + Bitmap& operator=(const Bitmap& other) { + if (&other != this) { + ABitmap_releaseRef(mBitmap); + mBitmap = other.mBitmap; + ABitmap_acquireRef(mBitmap); + } + return *this; + } + + // move operator + Bitmap& operator=(Bitmap&& other) { + if (&other != this) { + ABitmap_releaseRef(mBitmap); + mBitmap = other.mBitmap; + other.mBitmap = nullptr; + } + return *this; + } + + Bitmap copy(AndroidBitmapFormat dstFormat) const { + return Bitmap(ABitmap_copy(mBitmap, dstFormat)); + } + + bool isValid() const { return mBitmap != nullptr; } + bool isEmpty() const { + AndroidBitmapInfo info = getInfo(); + return info.width <= 0 || info.height <= 0; + } + void reset() { + ABitmap_releaseRef(mBitmap); + mBitmap = nullptr; + } + + ABitmap* get() const { return mBitmap; } + + AndroidBitmapInfo getInfo() const { return ABitmap_getInfo(mBitmap); } + ADataSpace getDataSpace() const { return ABitmap_getDataSpace(mBitmap); } + void* getPixels() const { return ABitmap_getPixels(mBitmap); } + void notifyPixelsChanged() const { ABitmap_notifyPixelsChanged(mBitmap); } + AHardwareBuffer* getHardwareBuffer() const { return ABitmap_getHardwareBuffer(mBitmap); } + + private: + // takes ownership of the provided ABitmap + Bitmap(ABitmap* bitmap) : mBitmap(bitmap) {} + + ABitmap* mBitmap; + }; +}; // namespace graphics +}; // namespace android +#endif // __cplusplus + +#endif // ANDROID_GRAPHICS_BITMAP_H diff --git a/libs/hwui/apex/include/android/graphics/canvas.h b/libs/hwui/apex/include/android/graphics/canvas.h new file mode 100644 index 000000000000..a0cecc04a7e5 --- /dev/null +++ b/libs/hwui/apex/include/android/graphics/canvas.h @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef ANDROID_GRAPHICS_CANVAS_H +#define ANDROID_GRAPHICS_CANVAS_H + +#include <android/graphics/bitmap.h> +#include <android/graphics/paint.h> +#include <android/native_window.h> +#include <android/rect.h> +#include <cutils/compiler.h> +#include <jni.h> + +__BEGIN_DECLS + +/** + * Opaque handle for a native graphics canvas. + */ +typedef struct ACanvas ACanvas; + +// One of AHardwareBuffer_Format. +ANDROID_API bool ACanvas_isSupportedPixelFormat(int32_t bufferFormat); + +/** + * Returns a native handle to a Java android.graphics.Canvas + * + * @return ACanvas* that is only valid for the life of the jobject. + */ +ANDROID_API ACanvas* ACanvas_getNativeHandleFromJava(JNIEnv* env, jobject canvas); + +/** + * Creates a canvas that wraps the buffer + * + * @param buffer is a required param. If no buffer is provided a nullptr will be returned. + */ +ANDROID_API ACanvas* ACanvas_createCanvas(const ANativeWindow_Buffer* buffer, + int32_t /*android_dataspace_t*/ dataspace); + +ANDROID_API void ACanvas_destroyCanvas(ACanvas* canvas); + +/** + * Updates the canvas to render into the pixels in the provided buffer + * + * @param buffer The buffer that will provide the backing store for this canvas. The buffer must + * remain valid until the this method is called again with either another active + * buffer or nullptr. If nullptr is given the canvas will release the previous buffer + * and set an empty backing store. + * @return A boolean value indicating whether or not the buffer was successfully set. If false the + * method will behave as if nullptr were passed as the input buffer and the previous buffer + * will still be released. + */ +ANDROID_API bool ACanvas_setBuffer(ACanvas* canvas, const ANativeWindow_Buffer* buffer, + int32_t /*android_dataspace_t*/ dataspace); + +/** + * Clips operations on the canvas to the intersection of the current clip and the provided clipRect. + * + * @param clipRect required + */ +ANDROID_API void ACanvas_clipRect(ACanvas* canvas, const ARect* clipRect, bool doAntiAlias = false); + +/** + * Clips operations on the canvas to the difference of the current clip and the provided clipRect. + * + * @param clipRect required + */ +ANDROID_API void ACanvas_clipOutRect(ACanvas* canvas, const ARect* clipRect, bool doAntiAlias = false); + +/** + * + * @param rect required + * @param paint required + */ +ANDROID_API void ACanvas_drawRect(ACanvas* canvas, const ARect* rect, const APaint* paint); + +/** + * + * @param bitmap required + * @param left + * @param top + * @param paint + */ +ANDROID_API void ACanvas_drawBitmap(ACanvas* canvas, const ABitmap* bitmap, float left, float top, + const APaint* paint); + +__END_DECLS + +#ifdef __cplusplus +namespace android { +namespace graphics { + class Canvas { + public: + Canvas(JNIEnv* env, jobject canvasObj) : + mCanvas(ACanvas_getNativeHandleFromJava(env, canvasObj)), + mOwnedPtr(false) {} + Canvas(const ANativeWindow_Buffer& buffer, int32_t /*android_dataspace_t*/ dataspace) : + mCanvas(ACanvas_createCanvas(&buffer, dataspace)), + mOwnedPtr(true) {} + ~Canvas() { + if (mOwnedPtr) { + ACanvas_destroyCanvas(mCanvas); + } + } + + bool setBuffer(const ANativeWindow_Buffer* buffer, + int32_t /*android_dataspace_t*/ dataspace) { + return ACanvas_setBuffer(mCanvas, buffer, dataspace); + } + + void clipRect(const ARect& clipRect, bool doAntiAlias = false) { + ACanvas_clipRect(mCanvas, &clipRect, doAntiAlias); + } + + void drawRect(const ARect& rect, const Paint& paint) { + ACanvas_drawRect(mCanvas, &rect, &paint.get()); + } + void drawBitmap(const Bitmap& bitmap, float left, float top, const Paint* paint) { + const APaint* aPaint = (paint) ? &paint->get() : nullptr; + ACanvas_drawBitmap(mCanvas, bitmap.get(), left, top, aPaint); + } + + private: + ACanvas* mCanvas; + const bool mOwnedPtr; + }; +}; // namespace graphics +}; // namespace android +#endif // __cplusplus + +#endif // ANDROID_GRAPHICS_CANVAS_H
\ No newline at end of file diff --git a/libs/hwui/apex/include/android/graphics/jni_runtime.h b/libs/hwui/apex/include/android/graphics/jni_runtime.h new file mode 100644 index 000000000000..487383ed50d5 --- /dev/null +++ b/libs/hwui/apex/include/android/graphics/jni_runtime.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef ANDROID_GRAPHICS_JNI_RUNTIME_H +#define ANDROID_GRAPHICS_JNI_RUNTIME_H + +#include <cutils/compiler.h> +#include <jni.h> + +__BEGIN_DECLS + +ANDROID_API void init_android_graphics(); + +ANDROID_API int register_android_graphics_classes(JNIEnv* env); + +ANDROID_API int register_android_graphics_GraphicsStatsService(JNIEnv* env); + +ANDROID_API void zygote_preload_graphics(); + +__END_DECLS + + +#endif // ANDROID_GRAPHICS_JNI_RUNTIME_H
\ No newline at end of file diff --git a/libs/hwui/apex/include/android/graphics/matrix.h b/libs/hwui/apex/include/android/graphics/matrix.h new file mode 100644 index 000000000000..987ad13f7635 --- /dev/null +++ b/libs/hwui/apex/include/android/graphics/matrix.h @@ -0,0 +1,39 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_GRAPHICS_MATRIX_H +#define ANDROID_GRAPHICS_MATRIX_H + +#include <jni.h> +#include <cutils/compiler.h> +#include <sys/cdefs.h> + +__BEGIN_DECLS + +/** + * Returns an array of floats that represents the 3x3 matrix of the java object. + * @param values The 9 values of the 3x3 matrix in the following order. + * values[0] = scaleX values[1] = skewX values[2] = transX + * values[3] = skewY values[4] = scaleY values[5] = transY + * values[6] = persp0 values[7] = persp1 values[8] = persp2 + * @return true if the values param was populated and false otherwise. + + */ +ANDROID_API bool AMatrix_getContents(JNIEnv* env, jobject matrixObj, float values[9]); + +__END_DECLS + +#endif // ANDROID_GRAPHICS_MATRIX_H diff --git a/libs/hwui/apex/include/android/graphics/paint.h b/libs/hwui/apex/include/android/graphics/paint.h new file mode 100644 index 000000000000..058db8d37619 --- /dev/null +++ b/libs/hwui/apex/include/android/graphics/paint.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef ANDROID_GRAPHICS_PAINT_H +#define ANDROID_GRAPHICS_PAINT_H + +#include <cutils/compiler.h> +#include <sys/cdefs.h> + +__BEGIN_DECLS + +/** + * Opaque handle for a native graphics canvas. + */ +typedef struct APaint APaint; + +/** Bitmap pixel format. */ +enum ABlendMode { + /** replaces destination with zero: fully transparent */ + ABLEND_MODE_CLEAR = 0, + /** source over destination */ + ABLEND_MODE_SRC_OVER = 1, + /** replaces destination **/ + ABLEND_MODE_SRC = 2, +}; + +ANDROID_API APaint* APaint_createPaint(); + +ANDROID_API void APaint_destroyPaint(APaint* paint); + +ANDROID_API void APaint_setBlendMode(APaint* paint, ABlendMode blendMode); + +__END_DECLS + +#ifdef __cplusplus +namespace android { +namespace graphics { + class Paint { + public: + Paint() : mPaint(APaint_createPaint()) {} + ~Paint() { APaint_destroyPaint(mPaint); } + + void setBlendMode(ABlendMode blendMode) { APaint_setBlendMode(mPaint, blendMode); } + + const APaint& get() const { return *mPaint; } + + private: + APaint* mPaint; + }; +}; // namespace graphics +}; // namespace android +#endif // __cplusplus + + +#endif // ANDROID_GRAPHICS_PAINT_H
\ No newline at end of file diff --git a/libs/hwui/apex/include/android/graphics/region.h b/libs/hwui/apex/include/android/graphics/region.h new file mode 100644 index 000000000000..0756d6dd63f6 --- /dev/null +++ b/libs/hwui/apex/include/android/graphics/region.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef ANDROID_GRAPHICS_REGION_H +#define ANDROID_GRAPHICS_REGION_H + +#include <cutils/compiler.h> +#include <android/rect.h> +#include <sys/cdefs.h> +#include <jni.h> + +__BEGIN_DECLS + +/** +* Opaque handle for a native graphics region iterator. +*/ +typedef struct ARegionIterator ARegionIterator; + +/** + * Returns a iterator for a Java android.graphics.Region + * + * @param env + * @param region + * @return ARegionIterator that must be closed and must not live longer than the life + * of the jobject. It returns nullptr if the region is not a valid object. + */ +ANDROID_API ARegionIterator* ARegionIterator_acquireIterator(JNIEnv* env, jobject region); + +ANDROID_API void ARegionIterator_releaseIterator(ARegionIterator* iterator); + +ANDROID_API bool ARegionIterator_isComplex(ARegionIterator* iterator); + +ANDROID_API bool ARegionIterator_isDone(ARegionIterator* iterator); + +ANDROID_API void ARegionIterator_next(ARegionIterator* iterator); + +ANDROID_API ARect ARegionIterator_getRect(ARegionIterator* iterator); + +ANDROID_API ARect ARegionIterator_getTotalBounds(ARegionIterator* iterator); + +__END_DECLS + +#ifdef __cplusplus +namespace android { +namespace graphics { + class RegionIterator { + public: + RegionIterator(JNIEnv* env, jobject region) + : mIterator(ARegionIterator_acquireIterator(env, region)) {} + ~RegionIterator() { ARegionIterator_releaseIterator(mIterator); } + + bool isValid() const { return mIterator != nullptr; } + bool isComplex() { return ARegionIterator_isComplex(mIterator); } + bool isDone() { return ARegionIterator_isDone(mIterator); } + void next() { ARegionIterator_next(mIterator); } + ARect getRect() { return ARegionIterator_getRect(mIterator); } + ARect getTotalBounds() const { return ARegionIterator_getTotalBounds(mIterator); } + private: + ARegionIterator* mIterator; + }; +}; // namespace graphics +}; // namespace android + +#endif // __cplusplus +#endif // ANDROID_GRAPHICS_REGION_H
\ No newline at end of file diff --git a/libs/hwui/apex/include/android/graphics/renderthread.h b/libs/hwui/apex/include/android/graphics/renderthread.h new file mode 100644 index 000000000000..50280a6dd1fb --- /dev/null +++ b/libs/hwui/apex/include/android/graphics/renderthread.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef ANDROID_GRAPHICS_RENDERTHREAD_H +#define ANDROID_GRAPHICS_RENDERTHREAD_H + +#include <cutils/compiler.h> +#include <sys/cdefs.h> + +__BEGIN_DECLS + +/** + * Dumps a textual representation of the graphics stats for this process. + * @param fd The file descriptor that the available graphics stats will be appended to. The + * function requires a valid fd, but does not persist or assume ownership of the fd + * outside the scope of this function. + */ +ANDROID_API void ARenderThread_dumpGraphicsMemory(int fd); + +__END_DECLS + +#endif // ANDROID_GRAPHICS_RENDERTHREAD_H diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp new file mode 100644 index 000000000000..a114e2f42157 --- /dev/null +++ b/libs/hwui/apex/jni_runtime.cpp @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "android/graphics/jni_runtime.h" + +#include <android/log.h> +#include <nativehelper/JNIHelp.h> +#include <sys/cdefs.h> + +#include <EGL/egl.h> +#include <GraphicsJNI.h> +#include <Properties.h> +#include <SkGraphics.h> + +#undef LOG_TAG +#define LOG_TAG "AndroidGraphicsJNI" + +extern int register_android_graphics_Bitmap(JNIEnv*); +extern int register_android_graphics_BitmapFactory(JNIEnv*); +extern int register_android_graphics_BitmapRegionDecoder(JNIEnv*); +extern int register_android_graphics_ByteBufferStreamAdaptor(JNIEnv* env); +extern int register_android_graphics_Camera(JNIEnv* env); +extern int register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv* env); +extern int register_android_graphics_Graphics(JNIEnv* env); +extern int register_android_graphics_ImageDecoder(JNIEnv*); +extern int register_android_graphics_drawable_AnimatedImageDrawable(JNIEnv*); +extern int register_android_graphics_Interpolator(JNIEnv* env); +extern int register_android_graphics_MaskFilter(JNIEnv* env); +extern int register_android_graphics_Movie(JNIEnv* env); +extern int register_android_graphics_NinePatch(JNIEnv*); +extern int register_android_graphics_PathEffect(JNIEnv* env); +extern int register_android_graphics_Shader(JNIEnv* env); +extern int register_android_graphics_Typeface(JNIEnv* env); +extern int register_android_graphics_YuvImage(JNIEnv* env); + +namespace android { + +extern int register_android_graphics_Canvas(JNIEnv* env); +extern int register_android_graphics_CanvasProperty(JNIEnv* env); +extern int register_android_graphics_ColorFilter(JNIEnv* env); +extern int register_android_graphics_ColorSpace(JNIEnv* env); +extern int register_android_graphics_DrawFilter(JNIEnv* env); +extern int register_android_graphics_FontFamily(JNIEnv* env); +extern int register_android_graphics_HardwareRendererObserver(JNIEnv* env); +extern int register_android_graphics_Matrix(JNIEnv* env); +extern int register_android_graphics_Paint(JNIEnv* env); +extern int register_android_graphics_Path(JNIEnv* env); +extern int register_android_graphics_PathMeasure(JNIEnv* env); +extern int register_android_graphics_Picture(JNIEnv*); +extern int register_android_graphics_Region(JNIEnv* env); +extern int register_android_graphics_animation_NativeInterpolatorFactory(JNIEnv* env); +extern int register_android_graphics_animation_RenderNodeAnimator(JNIEnv* env); +extern int register_android_graphics_drawable_AnimatedVectorDrawable(JNIEnv* env); +extern int register_android_graphics_drawable_VectorDrawable(JNIEnv* env); +extern int register_android_graphics_fonts_Font(JNIEnv* env); +extern int register_android_graphics_fonts_FontFamily(JNIEnv* env); +extern int register_android_graphics_pdf_PdfDocument(JNIEnv* env); +extern int register_android_graphics_pdf_PdfEditor(JNIEnv* env); +extern int register_android_graphics_pdf_PdfRenderer(JNIEnv* env); +extern int register_android_graphics_text_MeasuredText(JNIEnv* env); +extern int register_android_graphics_text_LineBreaker(JNIEnv *env); + +extern int register_android_util_PathParser(JNIEnv* env); +extern int register_android_view_DisplayListCanvas(JNIEnv* env); +extern int register_android_view_RenderNode(JNIEnv* env); +extern int register_android_view_TextureLayer(JNIEnv* env); +extern int register_android_view_ThreadedRenderer(JNIEnv* env); + +#ifdef NDEBUG + #define REG_JNI(name) { name } + struct RegJNIRec { + int (*mProc)(JNIEnv*); + }; +#else + #define REG_JNI(name) { name, #name } + struct RegJNIRec { + int (*mProc)(JNIEnv*); + const char* mName; + }; +#endif + +static const RegJNIRec gRegJNI[] = { + REG_JNI(register_android_graphics_Canvas), + // This needs to be before register_android_graphics_Graphics, or the latter + // will not be able to find the jmethodID for ColorSpace.get(). + REG_JNI(register_android_graphics_ColorSpace), + REG_JNI(register_android_graphics_Graphics), + REG_JNI(register_android_graphics_Bitmap), + REG_JNI(register_android_graphics_BitmapFactory), + REG_JNI(register_android_graphics_BitmapRegionDecoder), + REG_JNI(register_android_graphics_ByteBufferStreamAdaptor), + REG_JNI(register_android_graphics_Camera), + REG_JNI(register_android_graphics_CreateJavaOutputStreamAdaptor), + REG_JNI(register_android_graphics_CanvasProperty), + REG_JNI(register_android_graphics_ColorFilter), + REG_JNI(register_android_graphics_DrawFilter), + REG_JNI(register_android_graphics_FontFamily), + REG_JNI(register_android_graphics_HardwareRendererObserver), + REG_JNI(register_android_graphics_ImageDecoder), + REG_JNI(register_android_graphics_drawable_AnimatedImageDrawable), + REG_JNI(register_android_graphics_Interpolator), + REG_JNI(register_android_graphics_MaskFilter), + REG_JNI(register_android_graphics_Matrix), + REG_JNI(register_android_graphics_Movie), + REG_JNI(register_android_graphics_NinePatch), + REG_JNI(register_android_graphics_Paint), + REG_JNI(register_android_graphics_Path), + REG_JNI(register_android_graphics_PathMeasure), + REG_JNI(register_android_graphics_PathEffect), + REG_JNI(register_android_graphics_Picture), + REG_JNI(register_android_graphics_Region), + REG_JNI(register_android_graphics_Shader), + REG_JNI(register_android_graphics_Typeface), + REG_JNI(register_android_graphics_YuvImage), + REG_JNI(register_android_graphics_animation_NativeInterpolatorFactory), + REG_JNI(register_android_graphics_animation_RenderNodeAnimator), + REG_JNI(register_android_graphics_drawable_AnimatedVectorDrawable), + REG_JNI(register_android_graphics_drawable_VectorDrawable), + REG_JNI(register_android_graphics_fonts_Font), + REG_JNI(register_android_graphics_fonts_FontFamily), + REG_JNI(register_android_graphics_pdf_PdfDocument), + REG_JNI(register_android_graphics_pdf_PdfEditor), + REG_JNI(register_android_graphics_pdf_PdfRenderer), + REG_JNI(register_android_graphics_text_MeasuredText), + REG_JNI(register_android_graphics_text_LineBreaker), + + REG_JNI(register_android_util_PathParser), + REG_JNI(register_android_view_RenderNode), + REG_JNI(register_android_view_DisplayListCanvas), + REG_JNI(register_android_view_TextureLayer), + REG_JNI(register_android_view_ThreadedRenderer), +}; + +} // namespace android + +void init_android_graphics() { + SkGraphics::Init(); +} + +int register_android_graphics_classes(JNIEnv *env) { + JavaVM* vm = nullptr; + env->GetJavaVM(&vm); + GraphicsJNI::setJavaVM(vm); + + for (size_t i = 0; i < NELEM(android::gRegJNI); i++) { + if (android::gRegJNI[i].mProc(env) < 0) { +#ifndef NDEBUG + __android_log_print(ANDROID_LOG_FATAL, LOG_TAG, "JNI Error!!! %s failed to load\n", + android::gRegJNI[i].mName); +#endif + return -1; + } + } + return 0; +} + +using android::uirenderer::Properties; +using android::uirenderer::RenderPipelineType; + +void zygote_preload_graphics() { + if (Properties::peekRenderPipelineType() == RenderPipelineType::SkiaGL) { + eglGetDisplay(EGL_DEFAULT_DISPLAY); + } +}
\ No newline at end of file diff --git a/libs/hwui/apex/renderthread.cpp b/libs/hwui/apex/renderthread.cpp new file mode 100644 index 000000000000..5d26afe7a2ab --- /dev/null +++ b/libs/hwui/apex/renderthread.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "android/graphics/renderthread.h" + +#include <renderthread/RenderProxy.h> + +using namespace android; + +void ARenderThread_dumpGraphicsMemory(int fd) { + uirenderer::renderthread::RenderProxy::dumpGraphicsMemory(fd); +} diff --git a/libs/hwui/jni/AnimatedImageDrawable.cpp b/libs/hwui/jni/AnimatedImageDrawable.cpp new file mode 100644 index 000000000000..055075d0c42a --- /dev/null +++ b/libs/hwui/jni/AnimatedImageDrawable.cpp @@ -0,0 +1,271 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "GraphicsJNI.h" +#include "ImageDecoder.h" +#include "Utils.h" + +#include <SkAndroidCodec.h> +#include <SkAnimatedImage.h> +#include <SkColorFilter.h> +#include <SkPicture.h> +#include <SkPictureRecorder.h> +#include <hwui/AnimatedImageDrawable.h> +#include <hwui/ImageDecoder.h> +#include <hwui/Canvas.h> +#include <utils/Looper.h> + +using namespace android; + +static jmethodID gAnimatedImageDrawable_onAnimationEndMethodID; + +// Note: jpostProcess holds a handle to the ImageDecoder. +static jlong AnimatedImageDrawable_nCreate(JNIEnv* env, jobject /*clazz*/, + jlong nativeImageDecoder, jobject jpostProcess, + jint width, jint height, jlong colorSpaceHandle, + jboolean extended, jobject jsubset) { + if (nativeImageDecoder == 0) { + doThrowIOE(env, "Cannot create AnimatedImageDrawable from null!"); + return 0; + } + + auto* imageDecoder = reinterpret_cast<ImageDecoder*>(nativeImageDecoder); + SkIRect subset; + if (jsubset) { + GraphicsJNI::jrect_to_irect(env, jsubset, &subset); + } else { + subset = SkIRect::MakeWH(width, height); + } + + bool hasRestoreFrame = false; + if (imageDecoder->mCodec->getEncodedFormat() != SkEncodedImageFormat::kWEBP) { + const int frameCount = imageDecoder->mCodec->codec()->getFrameCount(); + for (int i = 0; i < frameCount; ++i) { + SkCodec::FrameInfo frameInfo; + if (!imageDecoder->mCodec->codec()->getFrameInfo(i, &frameInfo)) { + doThrowIOE(env, "Failed to read frame info!"); + return 0; + } + if (frameInfo.fDisposalMethod == SkCodecAnimation::DisposalMethod::kRestorePrevious) { + hasRestoreFrame = true; + break; + } + } + } + + auto info = imageDecoder->mCodec->getInfo().makeWH(width, height) + .makeColorSpace(GraphicsJNI::getNativeColorSpace(colorSpaceHandle)); + if (extended) { + info = info.makeColorType(kRGBA_F16_SkColorType); + } + + size_t bytesUsed = info.computeMinByteSize(); + // SkAnimatedImage has one SkBitmap for decoding, plus an extra one if there is a + // kRestorePrevious frame. AnimatedImageDrawable has two SkPictures storing the current + // frame and the next frame. (The former assumes that the image is animated, and the + // latter assumes that it is drawn to a hardware canvas.) + bytesUsed *= hasRestoreFrame ? 4 : 3; + sk_sp<SkPicture> picture; + if (jpostProcess) { + SkRect bounds = SkRect::MakeWH(subset.width(), subset.height()); + + SkPictureRecorder recorder; + SkCanvas* skcanvas = recorder.beginRecording(bounds); + std::unique_ptr<Canvas> canvas(Canvas::create_canvas(skcanvas)); + postProcessAndRelease(env, jpostProcess, std::move(canvas)); + if (env->ExceptionCheck()) { + return 0; + } + picture = recorder.finishRecordingAsPicture(); + bytesUsed += picture->approximateBytesUsed(); + } + + + sk_sp<SkAnimatedImage> animatedImg = SkAnimatedImage::Make(std::move(imageDecoder->mCodec), + info, subset, + std::move(picture)); + if (!animatedImg) { + doThrowIOE(env, "Failed to create drawable"); + return 0; + } + + bytesUsed += sizeof(animatedImg.get()); + + sk_sp<AnimatedImageDrawable> drawable(new AnimatedImageDrawable(std::move(animatedImg), + bytesUsed)); + return reinterpret_cast<jlong>(drawable.release()); +} + +static void AnimatedImageDrawable_destruct(AnimatedImageDrawable* drawable) { + SkSafeUnref(drawable); +} + +static jlong AnimatedImageDrawable_nGetNativeFinalizer(JNIEnv* /*env*/, jobject /*clazz*/) { + return static_cast<jlong>(reinterpret_cast<uintptr_t>(&AnimatedImageDrawable_destruct)); +} + +// Java's FINISHED relies on this being -1 +static_assert(SkAnimatedImage::kFinished == -1); + +static jlong AnimatedImageDrawable_nDraw(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, + jlong canvasPtr) { + auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr); + auto* canvas = reinterpret_cast<Canvas*>(canvasPtr); + return (jlong) canvas->drawAnimatedImage(drawable); +} + +static void AnimatedImageDrawable_nSetAlpha(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, + jint alpha) { + auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr); + drawable->setStagingAlpha(alpha); +} + +static jlong AnimatedImageDrawable_nGetAlpha(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { + auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr); + return drawable->getStagingAlpha(); +} + +static void AnimatedImageDrawable_nSetColorFilter(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, + jlong nativeFilter) { + auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr); + auto* filter = reinterpret_cast<SkColorFilter*>(nativeFilter); + drawable->setStagingColorFilter(sk_ref_sp(filter)); +} + +static jboolean AnimatedImageDrawable_nIsRunning(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { + auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr); + return drawable->isRunning(); +} + +static jboolean AnimatedImageDrawable_nStart(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { + auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr); + return drawable->start(); +} + +static jboolean AnimatedImageDrawable_nStop(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { + auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr); + return drawable->stop(); +} + +// Java's LOOP_INFINITE relies on this being the same. +static_assert(SkCodec::kRepetitionCountInfinite == -1); + +static jint AnimatedImageDrawable_nGetRepeatCount(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { + auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr); + return drawable->getRepetitionCount(); +} + +static void AnimatedImageDrawable_nSetRepeatCount(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, + jint loopCount) { + auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr); + drawable->setRepetitionCount(loopCount); +} + +class InvokeListener : public MessageHandler { +public: + InvokeListener(JNIEnv* env, jobject javaObject) { + LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&mJvm) != JNI_OK); + // Hold a weak reference to break a cycle that would prevent GC. + mWeakRef = env->NewWeakGlobalRef(javaObject); + } + + ~InvokeListener() override { + auto* env = get_env_or_die(mJvm); + env->DeleteWeakGlobalRef(mWeakRef); + } + + virtual void handleMessage(const Message&) override { + auto* env = get_env_or_die(mJvm); + jobject localRef = env->NewLocalRef(mWeakRef); + if (localRef) { + env->CallVoidMethod(localRef, gAnimatedImageDrawable_onAnimationEndMethodID); + } + } + +private: + JavaVM* mJvm; + jweak mWeakRef; +}; + +class JniAnimationEndListener : public OnAnimationEndListener { +public: + JniAnimationEndListener(sp<Looper>&& looper, JNIEnv* env, jobject javaObject) { + mListener = new InvokeListener(env, javaObject); + mLooper = std::move(looper); + } + + void onAnimationEnd() override { mLooper->sendMessage(mListener, 0); } + +private: + sp<InvokeListener> mListener; + sp<Looper> mLooper; +}; + +static void AnimatedImageDrawable_nSetOnAnimationEndListener(JNIEnv* env, jobject /*clazz*/, + jlong nativePtr, jobject jdrawable) { + auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr); + if (!jdrawable) { + drawable->setOnAnimationEndListener(nullptr); + } else { + sp<Looper> looper = Looper::getForThread(); + if (!looper.get()) { + doThrowISE(env, + "Must set AnimatedImageDrawable's AnimationCallback on a thread with a " + "looper!"); + return; + } + + drawable->setOnAnimationEndListener( + std::make_unique<JniAnimationEndListener>(std::move(looper), env, jdrawable)); + } +} + +static jlong AnimatedImageDrawable_nNativeByteSize(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { + auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr); + return drawable->byteSize(); +} + +static void AnimatedImageDrawable_nSetMirrored(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, + jboolean mirrored) { + auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr); + drawable->setStagingMirrored(mirrored); +} + +static const JNINativeMethod gAnimatedImageDrawableMethods[] = { + { "nCreate", "(JLandroid/graphics/ImageDecoder;IIJZLandroid/graphics/Rect;)J",(void*) AnimatedImageDrawable_nCreate }, + { "nGetNativeFinalizer", "()J", (void*) AnimatedImageDrawable_nGetNativeFinalizer }, + { "nDraw", "(JJ)J", (void*) AnimatedImageDrawable_nDraw }, + { "nSetAlpha", "(JI)V", (void*) AnimatedImageDrawable_nSetAlpha }, + { "nGetAlpha", "(J)I", (void*) AnimatedImageDrawable_nGetAlpha }, + { "nSetColorFilter", "(JJ)V", (void*) AnimatedImageDrawable_nSetColorFilter }, + { "nIsRunning", "(J)Z", (void*) AnimatedImageDrawable_nIsRunning }, + { "nStart", "(J)Z", (void*) AnimatedImageDrawable_nStart }, + { "nStop", "(J)Z", (void*) AnimatedImageDrawable_nStop }, + { "nGetRepeatCount", "(J)I", (void*) AnimatedImageDrawable_nGetRepeatCount }, + { "nSetRepeatCount", "(JI)V", (void*) AnimatedImageDrawable_nSetRepeatCount }, + { "nSetOnAnimationEndListener", "(JLandroid/graphics/drawable/AnimatedImageDrawable;)V", (void*) AnimatedImageDrawable_nSetOnAnimationEndListener }, + { "nNativeByteSize", "(J)J", (void*) AnimatedImageDrawable_nNativeByteSize }, + { "nSetMirrored", "(JZ)V", (void*) AnimatedImageDrawable_nSetMirrored }, +}; + +int register_android_graphics_drawable_AnimatedImageDrawable(JNIEnv* env) { + jclass animatedImageDrawable_class = FindClassOrDie(env, "android/graphics/drawable/AnimatedImageDrawable"); + gAnimatedImageDrawable_onAnimationEndMethodID = GetMethodIDOrDie(env, animatedImageDrawable_class, "onAnimationEnd", "()V"); + + return android::RegisterMethodsOrDie(env, "android/graphics/drawable/AnimatedImageDrawable", + gAnimatedImageDrawableMethods, NELEM(gAnimatedImageDrawableMethods)); +} + diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp new file mode 100755 index 000000000000..ba669053ed63 --- /dev/null +++ b/libs/hwui/jni/Bitmap.cpp @@ -0,0 +1,1184 @@ +#undef LOG_TAG +#define LOG_TAG "Bitmap" +#include "Bitmap.h" + +#include "SkBitmap.h" +#include "SkPixelRef.h" +#include "SkImageEncoder.h" +#include "SkImageInfo.h" +#include "SkColor.h" +#include "SkColorSpace.h" +#include "GraphicsJNI.h" +#include "SkStream.h" +#include "SkWebpEncoder.h" + +#include "android_nio_utils.h" +#include "CreateJavaOutputStreamAdaptor.h" +#include <hwui/Paint.h> +#include <hwui/Bitmap.h> +#include <utils/Color.h> + +#ifdef __ANDROID__ // Layoutlib does not support graphic buffer, parcel or render thread +#include <private/android/AHardwareBufferHelpers.h> +#include <binder/Parcel.h> +#include <dlfcn.h> +#include <renderthread/RenderProxy.h> +#endif + +#include <string.h> +#include <memory> +#include <string> + +#define DEBUG_PARCEL 0 + +static jclass gBitmap_class; +static jfieldID gBitmap_nativePtr; +static jmethodID gBitmap_constructorMethodID; +static jmethodID gBitmap_reinitMethodID; + +namespace android { + +class BitmapWrapper { +public: + explicit BitmapWrapper(Bitmap* bitmap) + : mBitmap(bitmap) { } + + void freePixels() { + mInfo = mBitmap->info(); + mHasHardwareMipMap = mBitmap->hasHardwareMipMap(); + mAllocationSize = mBitmap->getAllocationByteCount(); + mRowBytes = mBitmap->rowBytes(); + mGenerationId = mBitmap->getGenerationID(); + mIsHardware = mBitmap->isHardware(); + mBitmap.reset(); + } + + bool valid() { + return mBitmap != nullptr; + } + + Bitmap& bitmap() { + assertValid(); + return *mBitmap; + } + + void assertValid() { + LOG_ALWAYS_FATAL_IF(!valid(), "Error, cannot access an invalid/free'd bitmap here!"); + } + + void getSkBitmap(SkBitmap* outBitmap) { + assertValid(); + mBitmap->getSkBitmap(outBitmap); + } + + bool hasHardwareMipMap() { + if (mBitmap) { + return mBitmap->hasHardwareMipMap(); + } + return mHasHardwareMipMap; + } + + void setHasHardwareMipMap(bool hasMipMap) { + assertValid(); + mBitmap->setHasHardwareMipMap(hasMipMap); + } + + void setAlphaType(SkAlphaType alphaType) { + assertValid(); + mBitmap->setAlphaType(alphaType); + } + + void setColorSpace(sk_sp<SkColorSpace> colorSpace) { + assertValid(); + mBitmap->setColorSpace(colorSpace); + } + + const SkImageInfo& info() { + if (mBitmap) { + return mBitmap->info(); + } + return mInfo; + } + + size_t getAllocationByteCount() const { + if (mBitmap) { + return mBitmap->getAllocationByteCount(); + } + return mAllocationSize; + } + + size_t rowBytes() const { + if (mBitmap) { + return mBitmap->rowBytes(); + } + return mRowBytes; + } + + uint32_t getGenerationID() const { + if (mBitmap) { + return mBitmap->getGenerationID(); + } + return mGenerationId; + } + + bool isHardware() { + if (mBitmap) { + return mBitmap->isHardware(); + } + return mIsHardware; + } + + ~BitmapWrapper() { } + +private: + sk_sp<Bitmap> mBitmap; + SkImageInfo mInfo; + bool mHasHardwareMipMap; + size_t mAllocationSize; + size_t mRowBytes; + uint32_t mGenerationId; + bool mIsHardware; +}; + +// Convenience class that does not take a global ref on the pixels, relying +// on the caller already having a local JNI ref +class LocalScopedBitmap { +public: + explicit LocalScopedBitmap(jlong bitmapHandle) + : mBitmapWrapper(reinterpret_cast<BitmapWrapper*>(bitmapHandle)) {} + + BitmapWrapper* operator->() { + return mBitmapWrapper; + } + + void* pixels() { + return mBitmapWrapper->bitmap().pixels(); + } + + bool valid() { + return mBitmapWrapper && mBitmapWrapper->valid(); + } + +private: + BitmapWrapper* mBitmapWrapper; +}; + +namespace bitmap { + +// Assert that bitmap's SkAlphaType is consistent with isPremultiplied. +static void assert_premultiplied(const SkImageInfo& info, bool isPremultiplied) { + // kOpaque_SkAlphaType and kIgnore_SkAlphaType mean that isPremultiplied is + // irrelevant. This just tests to ensure that the SkAlphaType is not + // opposite of isPremultiplied. + if (isPremultiplied) { + SkASSERT(info.alphaType() != kUnpremul_SkAlphaType); + } else { + SkASSERT(info.alphaType() != kPremul_SkAlphaType); + } +} + +void reinitBitmap(JNIEnv* env, jobject javaBitmap, const SkImageInfo& info, + bool isPremultiplied) +{ + // The caller needs to have already set the alpha type properly, so the + // native SkBitmap stays in sync with the Java Bitmap. + assert_premultiplied(info, isPremultiplied); + + env->CallVoidMethod(javaBitmap, gBitmap_reinitMethodID, + info.width(), info.height(), isPremultiplied); +} + +jobject createBitmap(JNIEnv* env, Bitmap* bitmap, + int bitmapCreateFlags, jbyteArray ninePatchChunk, jobject ninePatchInsets, + int density) { + bool isMutable = bitmapCreateFlags & kBitmapCreateFlag_Mutable; + bool isPremultiplied = bitmapCreateFlags & kBitmapCreateFlag_Premultiplied; + // The caller needs to have already set the alpha type properly, so the + // native SkBitmap stays in sync with the Java Bitmap. + assert_premultiplied(bitmap->info(), isPremultiplied); + bool fromMalloc = bitmap->pixelStorageType() == PixelStorageType::Heap; + BitmapWrapper* bitmapWrapper = new BitmapWrapper(bitmap); + if (!isMutable) { + bitmapWrapper->bitmap().setImmutable(); + } + jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID, + reinterpret_cast<jlong>(bitmapWrapper), bitmap->width(), bitmap->height(), density, + isPremultiplied, ninePatchChunk, ninePatchInsets, fromMalloc); + + if (env->ExceptionCheck() != 0) { + ALOGE("*** Uncaught exception returned from Java call!\n"); + env->ExceptionDescribe(); + } + return obj; +} + +void toSkBitmap(jlong bitmapHandle, SkBitmap* outBitmap) { + LocalScopedBitmap bitmap(bitmapHandle); + bitmap->getSkBitmap(outBitmap); +} + +Bitmap& toBitmap(jlong bitmapHandle) { + LocalScopedBitmap localBitmap(bitmapHandle); + return localBitmap->bitmap(); +} + +} // namespace bitmap + +} // namespace android + +using namespace android; +using namespace android::bitmap; + +Bitmap* GraphicsJNI::getNativeBitmap(JNIEnv* env, jobject bitmap) { + SkASSERT(env); + SkASSERT(bitmap); + SkASSERT(env->IsInstanceOf(bitmap, gBitmap_class)); + jlong bitmapHandle = env->GetLongField(bitmap, gBitmap_nativePtr); + LocalScopedBitmap localBitmap(bitmapHandle); + return localBitmap.valid() ? &localBitmap->bitmap() : nullptr; +} + +SkImageInfo GraphicsJNI::getBitmapInfo(JNIEnv* env, jobject bitmap, uint32_t* outRowBytes, + bool* isHardware) { + SkASSERT(env); + SkASSERT(bitmap); + SkASSERT(env->IsInstanceOf(bitmap, gBitmap_class)); + jlong bitmapHandle = env->GetLongField(bitmap, gBitmap_nativePtr); + LocalScopedBitmap localBitmap(bitmapHandle); + if (outRowBytes) { + *outRowBytes = localBitmap->rowBytes(); + } + if (isHardware) { + *isHardware = localBitmap->isHardware(); + } + return localBitmap->info(); +} + +bool GraphicsJNI::SetPixels(JNIEnv* env, jintArray srcColors, int srcOffset, int srcStride, + int x, int y, int width, int height, SkBitmap* dstBitmap) { + const jint* array = env->GetIntArrayElements(srcColors, NULL); + const SkColor* src = (const SkColor*)array + srcOffset; + + auto sRGB = SkColorSpace::MakeSRGB(); + SkImageInfo srcInfo = SkImageInfo::Make( + width, height, kBGRA_8888_SkColorType, kUnpremul_SkAlphaType, sRGB); + SkPixmap srcPM(srcInfo, src, srcStride * 4); + + dstBitmap->writePixels(srcPM, x, y); + + env->ReleaseIntArrayElements(srcColors, const_cast<jint*>(array), JNI_ABORT); + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +static int getPremulBitmapCreateFlags(bool isMutable) { + int flags = android::bitmap::kBitmapCreateFlag_Premultiplied; + if (isMutable) flags |= android::bitmap::kBitmapCreateFlag_Mutable; + return flags; +} + +static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors, + jint offset, jint stride, jint width, jint height, + jint configHandle, jboolean isMutable, + jlong colorSpacePtr) { + SkColorType colorType = GraphicsJNI::legacyBitmapConfigToColorType(configHandle); + if (NULL != jColors) { + size_t n = env->GetArrayLength(jColors); + if (n < SkAbs32(stride) * (size_t)height) { + doThrowAIOOBE(env); + return NULL; + } + } + + // ARGB_4444 is a deprecated format, convert automatically to 8888 + if (colorType == kARGB_4444_SkColorType) { + colorType = kN32_SkColorType; + } + + sk_sp<SkColorSpace> colorSpace; + if (colorType == kAlpha_8_SkColorType) { + colorSpace = nullptr; + } else { + colorSpace = GraphicsJNI::getNativeColorSpace(colorSpacePtr); + } + + SkBitmap bitmap; + bitmap.setInfo(SkImageInfo::Make(width, height, colorType, kPremul_SkAlphaType, + colorSpace)); + + sk_sp<Bitmap> nativeBitmap = Bitmap::allocateHeapBitmap(&bitmap); + if (!nativeBitmap) { + ALOGE("OOM allocating Bitmap with dimensions %i x %i", width, height); + doThrowOOME(env); + return NULL; + } + + if (jColors != NULL) { + GraphicsJNI::SetPixels(env, jColors, offset, stride, 0, 0, width, height, &bitmap); + } + + return createBitmap(env, nativeBitmap.release(), getPremulBitmapCreateFlags(isMutable)); +} + +static bool bitmapCopyTo(SkBitmap* dst, SkColorType dstCT, const SkBitmap& src, + SkBitmap::Allocator* alloc) { + SkPixmap srcPM; + if (!src.peekPixels(&srcPM)) { + return false; + } + + SkImageInfo dstInfo = srcPM.info().makeColorType(dstCT); + switch (dstCT) { + case kRGB_565_SkColorType: + dstInfo = dstInfo.makeAlphaType(kOpaque_SkAlphaType); + break; + case kAlpha_8_SkColorType: + dstInfo = dstInfo.makeColorSpace(nullptr); + break; + default: + break; + } + + if (!dstInfo.colorSpace() && dstCT != kAlpha_8_SkColorType) { + dstInfo = dstInfo.makeColorSpace(SkColorSpace::MakeSRGB()); + } + + if (!dst->setInfo(dstInfo)) { + return false; + } + if (!dst->tryAllocPixels(alloc)) { + return false; + } + + SkPixmap dstPM; + if (!dst->peekPixels(&dstPM)) { + return false; + } + + return srcPM.readPixels(dstPM); +} + +static jobject Bitmap_copy(JNIEnv* env, jobject, jlong srcHandle, + jint dstConfigHandle, jboolean isMutable) { + SkBitmap src; + reinterpret_cast<BitmapWrapper*>(srcHandle)->getSkBitmap(&src); + if (dstConfigHandle == GraphicsJNI::hardwareLegacyBitmapConfig()) { + sk_sp<Bitmap> bitmap(Bitmap::allocateHardwareBitmap(src)); + if (!bitmap.get()) { + return NULL; + } + return createBitmap(env, bitmap.release(), getPremulBitmapCreateFlags(isMutable)); + } + + SkColorType dstCT = GraphicsJNI::legacyBitmapConfigToColorType(dstConfigHandle); + SkBitmap result; + HeapAllocator allocator; + + if (!bitmapCopyTo(&result, dstCT, src, &allocator)) { + return NULL; + } + auto bitmap = allocator.getStorageObjAndReset(); + return createBitmap(env, bitmap, getPremulBitmapCreateFlags(isMutable)); +} + +static Bitmap* Bitmap_copyAshmemImpl(JNIEnv* env, SkBitmap& src, SkColorType& dstCT) { + SkBitmap result; + + AshmemPixelAllocator allocator(env); + if (!bitmapCopyTo(&result, dstCT, src, &allocator)) { + return NULL; + } + auto bitmap = allocator.getStorageObjAndReset(); + bitmap->setImmutable(); + return bitmap; +} + +static jobject Bitmap_copyAshmem(JNIEnv* env, jobject, jlong srcHandle) { + SkBitmap src; + reinterpret_cast<BitmapWrapper*>(srcHandle)->getSkBitmap(&src); + SkColorType dstCT = src.colorType(); + auto bitmap = Bitmap_copyAshmemImpl(env, src, dstCT); + jobject ret = createBitmap(env, bitmap, getPremulBitmapCreateFlags(false)); + return ret; +} + +static jobject Bitmap_copyAshmemConfig(JNIEnv* env, jobject, jlong srcHandle, jint dstConfigHandle) { + SkBitmap src; + reinterpret_cast<BitmapWrapper*>(srcHandle)->getSkBitmap(&src); + SkColorType dstCT = GraphicsJNI::legacyBitmapConfigToColorType(dstConfigHandle); + auto bitmap = Bitmap_copyAshmemImpl(env, src, dstCT); + jobject ret = createBitmap(env, bitmap, getPremulBitmapCreateFlags(false)); + return ret; +} + +static void Bitmap_destruct(BitmapWrapper* bitmap) { + delete bitmap; +} + +static jlong Bitmap_getNativeFinalizer(JNIEnv*, jobject) { + return static_cast<jlong>(reinterpret_cast<uintptr_t>(&Bitmap_destruct)); +} + +static void Bitmap_recycle(JNIEnv* env, jobject, jlong bitmapHandle) { + LocalScopedBitmap bitmap(bitmapHandle); + bitmap->freePixels(); +} + +static void Bitmap_reconfigure(JNIEnv* env, jobject clazz, jlong bitmapHandle, + jint width, jint height, jint configHandle, jboolean requestPremul) { + LocalScopedBitmap bitmap(bitmapHandle); + bitmap->assertValid(); + SkColorType colorType = GraphicsJNI::legacyBitmapConfigToColorType(configHandle); + + // ARGB_4444 is a deprecated format, convert automatically to 8888 + if (colorType == kARGB_4444_SkColorType) { + colorType = kN32_SkColorType; + } + size_t requestedSize = width * height * SkColorTypeBytesPerPixel(colorType); + if (requestedSize > bitmap->getAllocationByteCount()) { + // done in native as there's no way to get BytesPerPixel in Java + doThrowIAE(env, "Bitmap not large enough to support new configuration"); + return; + } + SkAlphaType alphaType; + if (bitmap->info().colorType() != kRGB_565_SkColorType + && bitmap->info().alphaType() == kOpaque_SkAlphaType) { + // If the original bitmap was set to opaque, keep that setting, unless it + // was 565, which is required to be opaque. + alphaType = kOpaque_SkAlphaType; + } else { + // Otherwise respect the premultiplied request. + alphaType = requestPremul ? kPremul_SkAlphaType : kUnpremul_SkAlphaType; + } + bitmap->bitmap().reconfigure(SkImageInfo::Make(width, height, colorType, alphaType, + sk_ref_sp(bitmap->info().colorSpace()))); +} + +static jboolean Bitmap_compress(JNIEnv* env, jobject clazz, jlong bitmapHandle, + jint format, jint quality, + jobject jstream, jbyteArray jstorage) { + LocalScopedBitmap bitmap(bitmapHandle); + if (!bitmap.valid()) { + return JNI_FALSE; + } + + std::unique_ptr<SkWStream> strm(CreateJavaOutputStreamAdaptor(env, jstream, jstorage)); + if (!strm.get()) { + return JNI_FALSE; + } + + auto fm = static_cast<Bitmap::JavaCompressFormat>(format); + return bitmap->bitmap().compress(fm, quality, strm.get()) ? JNI_TRUE : JNI_FALSE; +} + +static inline void bitmapErase(SkBitmap bitmap, const SkColor4f& color, + const sk_sp<SkColorSpace>& colorSpace) { + SkPaint p; + p.setColor4f(color, colorSpace.get()); + p.setBlendMode(SkBlendMode::kSrc); + SkCanvas canvas(bitmap); + canvas.drawPaint(p); +} + +static void Bitmap_erase(JNIEnv* env, jobject, jlong bitmapHandle, jint color) { + LocalScopedBitmap bitmap(bitmapHandle); + SkBitmap skBitmap; + bitmap->getSkBitmap(&skBitmap); + bitmapErase(skBitmap, SkColor4f::FromColor(color), SkColorSpace::MakeSRGB()); +} + +static void Bitmap_eraseLong(JNIEnv* env, jobject, jlong bitmapHandle, + jlong colorSpaceHandle, jlong colorLong) { + LocalScopedBitmap bitmap(bitmapHandle); + SkBitmap skBitmap; + bitmap->getSkBitmap(&skBitmap); + + SkColor4f color = GraphicsJNI::convertColorLong(colorLong); + sk_sp<SkColorSpace> cs = GraphicsJNI::getNativeColorSpace(colorSpaceHandle); + bitmapErase(skBitmap, color, cs); +} + +static jint Bitmap_rowBytes(JNIEnv* env, jobject, jlong bitmapHandle) { + LocalScopedBitmap bitmap(bitmapHandle); + return static_cast<jint>(bitmap->rowBytes()); +} + +static jint Bitmap_config(JNIEnv* env, jobject, jlong bitmapHandle) { + LocalScopedBitmap bitmap(bitmapHandle); + if (bitmap->isHardware()) { + return GraphicsJNI::hardwareLegacyBitmapConfig(); + } + return GraphicsJNI::colorTypeToLegacyBitmapConfig(bitmap->info().colorType()); +} + +static jint Bitmap_getGenerationId(JNIEnv* env, jobject, jlong bitmapHandle) { + LocalScopedBitmap bitmap(bitmapHandle); + return static_cast<jint>(bitmap->getGenerationID()); +} + +static jboolean Bitmap_isPremultiplied(JNIEnv* env, jobject, jlong bitmapHandle) { + LocalScopedBitmap bitmap(bitmapHandle); + if (bitmap->info().alphaType() == kPremul_SkAlphaType) { + return JNI_TRUE; + } + return JNI_FALSE; +} + +static jboolean Bitmap_hasAlpha(JNIEnv* env, jobject, jlong bitmapHandle) { + LocalScopedBitmap bitmap(bitmapHandle); + return !bitmap->info().isOpaque() ? JNI_TRUE : JNI_FALSE; +} + +static void Bitmap_setHasAlpha(JNIEnv* env, jobject, jlong bitmapHandle, + jboolean hasAlpha, jboolean requestPremul) { + LocalScopedBitmap bitmap(bitmapHandle); + if (hasAlpha) { + bitmap->setAlphaType( + requestPremul ? kPremul_SkAlphaType : kUnpremul_SkAlphaType); + } else { + bitmap->setAlphaType(kOpaque_SkAlphaType); + } +} + +static void Bitmap_setPremultiplied(JNIEnv* env, jobject, jlong bitmapHandle, + jboolean isPremul) { + LocalScopedBitmap bitmap(bitmapHandle); + if (!bitmap->info().isOpaque()) { + if (isPremul) { + bitmap->setAlphaType(kPremul_SkAlphaType); + } else { + bitmap->setAlphaType(kUnpremul_SkAlphaType); + } + } +} + +static jboolean Bitmap_hasMipMap(JNIEnv* env, jobject, jlong bitmapHandle) { + LocalScopedBitmap bitmap(bitmapHandle); + return bitmap->hasHardwareMipMap() ? JNI_TRUE : JNI_FALSE; +} + +static void Bitmap_setHasMipMap(JNIEnv* env, jobject, jlong bitmapHandle, + jboolean hasMipMap) { + LocalScopedBitmap bitmap(bitmapHandle); + bitmap->setHasHardwareMipMap(hasMipMap); +} + +/////////////////////////////////////////////////////////////////////////////// + +#ifdef __ANDROID__ // Layoutlib does not support parcel +static struct parcel_offsets_t +{ + jclass clazz; + jfieldID mNativePtr; +} gParcelOffsets; + +static Parcel* parcelForJavaObject(JNIEnv* env, jobject obj) { + if (obj) { + Parcel* p = (Parcel*)env->GetLongField(obj, gParcelOffsets.mNativePtr); + if (p != NULL) { + return p; + } + jniThrowException(env, "java/lang/IllegalStateException", "Parcel has been finalized!"); + } + return NULL; +} +#endif + +// This is the maximum possible size because the SkColorSpace must be +// representable (and therefore serializable) using a matrix and numerical +// transfer function. If we allow more color space representations in the +// framework, we may need to update this maximum size. +static constexpr uint32_t kMaxColorSpaceSerializedBytes = 80; + +static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) { +#ifdef __ANDROID__ // Layoutlib does not support parcel + if (parcel == NULL) { + SkDebugf("-------- unparcel parcel is NULL\n"); + return NULL; + } + + android::Parcel* p = parcelForJavaObject(env, parcel); + + const SkColorType colorType = (SkColorType)p->readInt32(); + const SkAlphaType alphaType = (SkAlphaType)p->readInt32(); + const uint32_t colorSpaceSize = p->readUint32(); + sk_sp<SkColorSpace> colorSpace; + if (colorSpaceSize > 0) { + if (colorSpaceSize > kMaxColorSpaceSerializedBytes) { + ALOGD("Bitmap_createFromParcel: Serialized SkColorSpace is larger than expected: " + "%d bytes\n", colorSpaceSize); + } + + const void* data = p->readInplace(colorSpaceSize); + if (data) { + colorSpace = SkColorSpace::Deserialize(data, colorSpaceSize); + } else { + ALOGD("Bitmap_createFromParcel: Unable to read serialized SkColorSpace data\n"); + } + } + const int width = p->readInt32(); + const int height = p->readInt32(); + const int rowBytes = p->readInt32(); + const int density = p->readInt32(); + + if (kN32_SkColorType != colorType && + kRGBA_F16_SkColorType != colorType && + kRGB_565_SkColorType != colorType && + kARGB_4444_SkColorType != colorType && + kAlpha_8_SkColorType != colorType) { + SkDebugf("Bitmap_createFromParcel unknown colortype: %d\n", colorType); + return NULL; + } + + std::unique_ptr<SkBitmap> bitmap(new SkBitmap); + if (!bitmap->setInfo(SkImageInfo::Make(width, height, colorType, alphaType, colorSpace), + rowBytes)) { + return NULL; + } + + // Read the bitmap blob. + size_t size = bitmap->computeByteSize(); + android::Parcel::ReadableBlob blob; + android::status_t status = p->readBlob(size, &blob); + if (status) { + doThrowRE(env, "Could not read bitmap blob."); + return NULL; + } + + // Map the bitmap in place from the ashmem region if possible otherwise copy. + sk_sp<Bitmap> nativeBitmap; + if (blob.fd() >= 0 && !blob.isMutable()) { +#if DEBUG_PARCEL + ALOGD("Bitmap.createFromParcel: mapped contents of bitmap from %s blob " + "(fds %s)", + blob.isMutable() ? "mutable" : "immutable", + p->allowFds() ? "allowed" : "forbidden"); +#endif + // Dup the file descriptor so we can keep a reference to it after the Parcel + // is disposed. + int dupFd = fcntl(blob.fd(), F_DUPFD_CLOEXEC, 0); + if (dupFd < 0) { + ALOGE("Error allocating dup fd. Error:%d", errno); + blob.release(); + doThrowRE(env, "Could not allocate dup blob fd."); + return NULL; + } + + // Map the pixels in place and take ownership of the ashmem region. We must also respect the + // rowBytes value already set on the bitmap instead of attempting to compute our own. + nativeBitmap = Bitmap::createFrom(bitmap->info(), bitmap->rowBytes(), dupFd, + const_cast<void*>(blob.data()), size, true); + if (!nativeBitmap) { + close(dupFd); + blob.release(); + doThrowRE(env, "Could not allocate ashmem pixel ref."); + return NULL; + } + + // Clear the blob handle, don't release it. + blob.clear(); + } else { +#if DEBUG_PARCEL + if (blob.fd() >= 0) { + ALOGD("Bitmap.createFromParcel: copied contents of mutable bitmap " + "from immutable blob (fds %s)", + p->allowFds() ? "allowed" : "forbidden"); + } else { + ALOGD("Bitmap.createFromParcel: copied contents from %s blob " + "(fds %s)", + blob.isMutable() ? "mutable" : "immutable", + p->allowFds() ? "allowed" : "forbidden"); + } +#endif + + // Copy the pixels into a new buffer. + nativeBitmap = Bitmap::allocateHeapBitmap(bitmap.get()); + if (!nativeBitmap) { + blob.release(); + doThrowRE(env, "Could not allocate java pixel ref."); + return NULL; + } + memcpy(bitmap->getPixels(), blob.data(), size); + + // Release the blob handle. + blob.release(); + } + + return createBitmap(env, nativeBitmap.release(), + getPremulBitmapCreateFlags(false), NULL, NULL, density); +#else + doThrowRE(env, "Cannot use parcels outside of Android"); + return NULL; +#endif +} + +static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject, + jlong bitmapHandle, jint density, jobject parcel) { +#ifdef __ANDROID__ // Layoutlib does not support parcel + if (parcel == NULL) { + SkDebugf("------- writeToParcel null parcel\n"); + return JNI_FALSE; + } + + android::Parcel* p = parcelForJavaObject(env, parcel); + SkBitmap bitmap; + + auto bitmapWrapper = reinterpret_cast<BitmapWrapper*>(bitmapHandle); + bitmapWrapper->getSkBitmap(&bitmap); + + p->writeInt32(bitmap.colorType()); + p->writeInt32(bitmap.alphaType()); + SkColorSpace* colorSpace = bitmap.colorSpace(); + if (colorSpace != nullptr) { + sk_sp<SkData> data = colorSpace->serialize(); + size_t size = data->size(); + p->writeUint32(size); + if (size > 0) { + if (size > kMaxColorSpaceSerializedBytes) { + ALOGD("Bitmap_writeToParcel: Serialized SkColorSpace is larger than expected: " + "%zu bytes\n", size); + } + + p->write(data->data(), size); + } + } else { + p->writeUint32(0); + } + p->writeInt32(bitmap.width()); + p->writeInt32(bitmap.height()); + p->writeInt32(bitmap.rowBytes()); + p->writeInt32(density); + + // Transfer the underlying ashmem region if we have one and it's immutable. + android::status_t status; + int fd = bitmapWrapper->bitmap().getAshmemFd(); + if (fd >= 0 && p->allowFds()) { +#if DEBUG_PARCEL + ALOGD("Bitmap.writeToParcel: transferring immutable bitmap's ashmem fd as " + "immutable blob (fds %s)", + p->allowFds() ? "allowed" : "forbidden"); +#endif + + status = p->writeDupImmutableBlobFileDescriptor(fd); + if (status) { + doThrowRE(env, "Could not write bitmap blob file descriptor."); + return JNI_FALSE; + } + return JNI_TRUE; + } + + // Copy the bitmap to a new blob. +#if DEBUG_PARCEL + ALOGD("Bitmap.writeToParcel: copying bitmap into new blob (fds %s)", + p->allowFds() ? "allowed" : "forbidden"); +#endif + + size_t size = bitmap.computeByteSize(); + android::Parcel::WritableBlob blob; + status = p->writeBlob(size, false, &blob); + if (status) { + doThrowRE(env, "Could not copy bitmap to parcel blob."); + return JNI_FALSE; + } + + const void* pSrc = bitmap.getPixels(); + if (pSrc == NULL) { + memset(blob.data(), 0, size); + } else { + memcpy(blob.data(), pSrc, size); + } + + blob.release(); + return JNI_TRUE; +#else + doThrowRE(env, "Cannot use parcels outside of Android"); + return JNI_FALSE; +#endif +} + +static jobject Bitmap_extractAlpha(JNIEnv* env, jobject clazz, + jlong srcHandle, jlong paintHandle, + jintArray offsetXY) { + SkBitmap src; + reinterpret_cast<BitmapWrapper*>(srcHandle)->getSkBitmap(&src); + const android::Paint* paint = reinterpret_cast<android::Paint*>(paintHandle); + SkIPoint offset; + SkBitmap dst; + HeapAllocator allocator; + + src.extractAlpha(&dst, paint, &allocator, &offset); + // If Skia can't allocate pixels for destination bitmap, it resets + // it, that is set its pixels buffer to NULL, and zero width and height. + if (dst.getPixels() == NULL && src.getPixels() != NULL) { + doThrowOOME(env, "failed to allocate pixels for alpha"); + return NULL; + } + if (offsetXY != 0 && env->GetArrayLength(offsetXY) >= 2) { + int* array = env->GetIntArrayElements(offsetXY, NULL); + array[0] = offset.fX; + array[1] = offset.fY; + env->ReleaseIntArrayElements(offsetXY, array, 0); + } + + return createBitmap(env, allocator.getStorageObjAndReset(), + getPremulBitmapCreateFlags(true)); +} + +/////////////////////////////////////////////////////////////////////////////// + +static jboolean Bitmap_isSRGB(JNIEnv* env, jobject, jlong bitmapHandle) { + LocalScopedBitmap bitmapHolder(bitmapHandle); + if (!bitmapHolder.valid()) return JNI_TRUE; + + SkColorSpace* colorSpace = bitmapHolder->info().colorSpace(); + return colorSpace == nullptr || colorSpace->isSRGB(); +} + +static jboolean Bitmap_isSRGBLinear(JNIEnv* env, jobject, jlong bitmapHandle) { + LocalScopedBitmap bitmapHolder(bitmapHandle); + if (!bitmapHolder.valid()) return JNI_FALSE; + + SkColorSpace* colorSpace = bitmapHolder->info().colorSpace(); + sk_sp<SkColorSpace> srgbLinear = SkColorSpace::MakeSRGBLinear(); + return colorSpace == srgbLinear.get() ? JNI_TRUE : JNI_FALSE; +} + +static jobject Bitmap_computeColorSpace(JNIEnv* env, jobject, jlong bitmapHandle) { + LocalScopedBitmap bitmapHolder(bitmapHandle); + if (!bitmapHolder.valid()) return nullptr; + + SkColorSpace* colorSpace = bitmapHolder->info().colorSpace(); + if (colorSpace == nullptr) return nullptr; + + return GraphicsJNI::getColorSpace(env, colorSpace, bitmapHolder->info().colorType()); +} + +static void Bitmap_setColorSpace(JNIEnv* env, jobject, jlong bitmapHandle, jlong colorSpacePtr) { + LocalScopedBitmap bitmapHolder(bitmapHandle); + sk_sp<SkColorSpace> cs = GraphicsJNI::getNativeColorSpace(colorSpacePtr); + bitmapHolder->setColorSpace(cs); +} + +/////////////////////////////////////////////////////////////////////////////// + +static jint Bitmap_getPixel(JNIEnv* env, jobject, jlong bitmapHandle, + jint x, jint y) { + SkBitmap bitmap; + reinterpret_cast<BitmapWrapper*>(bitmapHandle)->getSkBitmap(&bitmap); + + auto sRGB = SkColorSpace::MakeSRGB(); + SkImageInfo dstInfo = SkImageInfo::Make( + 1, 1, kBGRA_8888_SkColorType, kUnpremul_SkAlphaType, sRGB); + + SkColor dst; + bitmap.readPixels(dstInfo, &dst, dstInfo.minRowBytes(), x, y); + return static_cast<jint>(dst); +} + +static jlong Bitmap_getColor(JNIEnv* env, jobject, jlong bitmapHandle, + jint x, jint y) { + SkBitmap bitmap; + reinterpret_cast<BitmapWrapper*>(bitmapHandle)->getSkBitmap(&bitmap); + + SkImageInfo dstInfo = SkImageInfo::Make( + 1, 1, kRGBA_F16_SkColorType, kUnpremul_SkAlphaType, bitmap.refColorSpace()); + + uint64_t dst; + bitmap.readPixels(dstInfo, &dst, dstInfo.minRowBytes(), x, y); + return static_cast<jlong>(dst); +} + +static void Bitmap_getPixels(JNIEnv* env, jobject, jlong bitmapHandle, + jintArray pixelArray, jint offset, jint stride, + jint x, jint y, jint width, jint height) { + SkBitmap bitmap; + reinterpret_cast<BitmapWrapper*>(bitmapHandle)->getSkBitmap(&bitmap); + + auto sRGB = SkColorSpace::MakeSRGB(); + SkImageInfo dstInfo = SkImageInfo::Make( + width, height, kBGRA_8888_SkColorType, kUnpremul_SkAlphaType, sRGB); + + jint* dst = env->GetIntArrayElements(pixelArray, NULL); + bitmap.readPixels(dstInfo, dst + offset, stride * 4, x, y); + env->ReleaseIntArrayElements(pixelArray, dst, 0); +} + +/////////////////////////////////////////////////////////////////////////////// + +static void Bitmap_setPixel(JNIEnv* env, jobject, jlong bitmapHandle, + jint x, jint y, jint colorHandle) { + SkBitmap bitmap; + reinterpret_cast<BitmapWrapper*>(bitmapHandle)->getSkBitmap(&bitmap); + SkColor color = static_cast<SkColor>(colorHandle); + + auto sRGB = SkColorSpace::MakeSRGB(); + SkImageInfo srcInfo = SkImageInfo::Make( + 1, 1, kBGRA_8888_SkColorType, kUnpremul_SkAlphaType, sRGB); + SkPixmap srcPM(srcInfo, &color, srcInfo.minRowBytes()); + + bitmap.writePixels(srcPM, x, y); +} + +static void Bitmap_setPixels(JNIEnv* env, jobject, jlong bitmapHandle, + jintArray pixelArray, jint offset, jint stride, + jint x, jint y, jint width, jint height) { + SkBitmap bitmap; + reinterpret_cast<BitmapWrapper*>(bitmapHandle)->getSkBitmap(&bitmap); + GraphicsJNI::SetPixels(env, pixelArray, offset, stride, + x, y, width, height, &bitmap); +} + +static void Bitmap_copyPixelsToBuffer(JNIEnv* env, jobject, + jlong bitmapHandle, jobject jbuffer) { + SkBitmap bitmap; + reinterpret_cast<BitmapWrapper*>(bitmapHandle)->getSkBitmap(&bitmap); + const void* src = bitmap.getPixels(); + + if (NULL != src) { + android::AutoBufferPointer abp(env, jbuffer, JNI_TRUE); + + // the java side has already checked that buffer is large enough + memcpy(abp.pointer(), src, bitmap.computeByteSize()); + } +} + +static void Bitmap_copyPixelsFromBuffer(JNIEnv* env, jobject, + jlong bitmapHandle, jobject jbuffer) { + SkBitmap bitmap; + reinterpret_cast<BitmapWrapper*>(bitmapHandle)->getSkBitmap(&bitmap); + void* dst = bitmap.getPixels(); + + if (NULL != dst) { + android::AutoBufferPointer abp(env, jbuffer, JNI_FALSE); + // the java side has already checked that buffer is large enough + memcpy(dst, abp.pointer(), bitmap.computeByteSize()); + bitmap.notifyPixelsChanged(); + } +} + +static jboolean Bitmap_sameAs(JNIEnv* env, jobject, jlong bm0Handle, jlong bm1Handle) { + SkBitmap bm0; + SkBitmap bm1; + + LocalScopedBitmap bitmap0(bm0Handle); + LocalScopedBitmap bitmap1(bm1Handle); + + // Paying the price for making Hardware Bitmap as Config: + // later check for colorType will pass successfully, + // because Hardware Config internally may be RGBA8888 or smth like that. + if (bitmap0->isHardware() != bitmap1->isHardware()) { + return JNI_FALSE; + } + + bitmap0->bitmap().getSkBitmap(&bm0); + bitmap1->bitmap().getSkBitmap(&bm1); + if (bm0.width() != bm1.width() + || bm0.height() != bm1.height() + || bm0.colorType() != bm1.colorType() + || bm0.alphaType() != bm1.alphaType() + || !SkColorSpace::Equals(bm0.colorSpace(), bm1.colorSpace())) { + return JNI_FALSE; + } + + // if we can't load the pixels, return false + if (NULL == bm0.getPixels() || NULL == bm1.getPixels()) { + return JNI_FALSE; + } + + // now compare each scanline. We can't do the entire buffer at once, + // since we don't care about the pixel values that might extend beyond + // the width (since the scanline might be larger than the logical width) + const int h = bm0.height(); + const size_t size = bm0.width() * bm0.bytesPerPixel(); + for (int y = 0; y < h; y++) { + // SkBitmap::getAddr(int, int) may return NULL due to unrecognized config + // (ex: kRLE_Index8_Config). This will cause memcmp method to crash. Since bm0 + // and bm1 both have pixel data() (have passed NULL == getPixels() check), + // those 2 bitmaps should be valid (only unrecognized), we return JNI_FALSE + // to warn user those 2 unrecognized config bitmaps may be different. + void *bm0Addr = bm0.getAddr(0, y); + void *bm1Addr = bm1.getAddr(0, y); + + if(bm0Addr == NULL || bm1Addr == NULL) { + return JNI_FALSE; + } + + if (memcmp(bm0Addr, bm1Addr, size) != 0) { + return JNI_FALSE; + } + } + return JNI_TRUE; +} + +static void Bitmap_prepareToDraw(JNIEnv* env, jobject, jlong bitmapPtr) { +#ifdef __ANDROID__ // Layoutlib does not support render thread + LocalScopedBitmap bitmapHandle(bitmapPtr); + if (!bitmapHandle.valid()) return; + android::uirenderer::renderthread::RenderProxy::prepareToDraw(bitmapHandle->bitmap()); +#endif +} + +static jint Bitmap_getAllocationByteCount(JNIEnv* env, jobject, jlong bitmapPtr) { + LocalScopedBitmap bitmapHandle(bitmapPtr); + return static_cast<jint>(bitmapHandle->getAllocationByteCount()); +} + +static jobject Bitmap_copyPreserveInternalConfig(JNIEnv* env, jobject, jlong bitmapPtr) { + LocalScopedBitmap bitmapHandle(bitmapPtr); + LOG_ALWAYS_FATAL_IF(!bitmapHandle->isHardware(), + "Hardware config is only supported config in Bitmap_nativeCopyPreserveInternalConfig"); + Bitmap& hwuiBitmap = bitmapHandle->bitmap(); + SkBitmap src; + hwuiBitmap.getSkBitmap(&src); + + if (src.pixelRef() == nullptr) { + doThrowRE(env, "Could not copy a hardware bitmap."); + return NULL; + } + + sk_sp<Bitmap> bitmap = Bitmap::createFrom(src.info(), *src.pixelRef()); + return createBitmap(env, bitmap.release(), getPremulBitmapCreateFlags(false)); +} + +#ifdef __ANDROID__ // Layoutlib does not support graphic buffer +typedef AHardwareBuffer* (*AHB_from_HB)(JNIEnv*, jobject); +AHB_from_HB AHardwareBuffer_fromHardwareBuffer; + +typedef jobject (*AHB_to_HB)(JNIEnv*, AHardwareBuffer*); +AHB_to_HB AHardwareBuffer_toHardwareBuffer; +#endif + +static jobject Bitmap_wrapHardwareBufferBitmap(JNIEnv* env, jobject, jobject hardwareBuffer, + jlong colorSpacePtr) { +#ifdef __ANDROID__ // Layoutlib does not support graphic buffer + AHardwareBuffer* buffer = AHardwareBuffer_fromHardwareBuffer(env, hardwareBuffer); + sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, + GraphicsJNI::getNativeColorSpace(colorSpacePtr)); + if (!bitmap.get()) { + ALOGW("failed to create hardware bitmap from hardware buffer"); + return NULL; + } + return bitmap::createBitmap(env, bitmap.release(), getPremulBitmapCreateFlags(false)); +#else + return NULL; +#endif +} + +static jobject Bitmap_getHardwareBuffer(JNIEnv* env, jobject, jlong bitmapPtr) { +#ifdef __ANDROID__ // Layoutlib does not support graphic buffer + LocalScopedBitmap bitmapHandle(bitmapPtr); + LOG_ALWAYS_FATAL_IF(!bitmapHandle->isHardware(), + "Hardware config is only supported config in Bitmap_getHardwareBuffer"); + + Bitmap& bitmap = bitmapHandle->bitmap(); + return AHardwareBuffer_toHardwareBuffer(env, bitmap.hardwareBuffer()); +#else + return NULL; +#endif +} + +static jboolean Bitmap_isImmutable(CRITICAL_JNI_PARAMS_COMMA jlong bitmapHandle) { + LocalScopedBitmap bitmapHolder(bitmapHandle); + if (!bitmapHolder.valid()) return JNI_FALSE; + + return bitmapHolder->bitmap().isImmutable() ? JNI_TRUE : JNI_FALSE; +} + +static void Bitmap_setImmutable(JNIEnv* env, jobject, jlong bitmapHandle) { + LocalScopedBitmap bitmapHolder(bitmapHandle); + if (!bitmapHolder.valid()) return; + + return bitmapHolder->bitmap().setImmutable(); +} + +/////////////////////////////////////////////////////////////////////////////// + +static const JNINativeMethod gBitmapMethods[] = { + { "nativeCreate", "([IIIIIIZJ)Landroid/graphics/Bitmap;", + (void*)Bitmap_creator }, + { "nativeCopy", "(JIZ)Landroid/graphics/Bitmap;", + (void*)Bitmap_copy }, + { "nativeCopyAshmem", "(J)Landroid/graphics/Bitmap;", + (void*)Bitmap_copyAshmem }, + { "nativeCopyAshmemConfig", "(JI)Landroid/graphics/Bitmap;", + (void*)Bitmap_copyAshmemConfig }, + { "nativeGetNativeFinalizer", "()J", (void*)Bitmap_getNativeFinalizer }, + { "nativeRecycle", "(J)V", (void*)Bitmap_recycle }, + { "nativeReconfigure", "(JIIIZ)V", (void*)Bitmap_reconfigure }, + { "nativeCompress", "(JIILjava/io/OutputStream;[B)Z", + (void*)Bitmap_compress }, + { "nativeErase", "(JI)V", (void*)Bitmap_erase }, + { "nativeErase", "(JJJ)V", (void*)Bitmap_eraseLong }, + { "nativeRowBytes", "(J)I", (void*)Bitmap_rowBytes }, + { "nativeConfig", "(J)I", (void*)Bitmap_config }, + { "nativeHasAlpha", "(J)Z", (void*)Bitmap_hasAlpha }, + { "nativeIsPremultiplied", "(J)Z", (void*)Bitmap_isPremultiplied}, + { "nativeSetHasAlpha", "(JZZ)V", (void*)Bitmap_setHasAlpha}, + { "nativeSetPremultiplied", "(JZ)V", (void*)Bitmap_setPremultiplied}, + { "nativeHasMipMap", "(J)Z", (void*)Bitmap_hasMipMap }, + { "nativeSetHasMipMap", "(JZ)V", (void*)Bitmap_setHasMipMap }, + { "nativeCreateFromParcel", + "(Landroid/os/Parcel;)Landroid/graphics/Bitmap;", + (void*)Bitmap_createFromParcel }, + { "nativeWriteToParcel", "(JILandroid/os/Parcel;)Z", + (void*)Bitmap_writeToParcel }, + { "nativeExtractAlpha", "(JJ[I)Landroid/graphics/Bitmap;", + (void*)Bitmap_extractAlpha }, + { "nativeGenerationId", "(J)I", (void*)Bitmap_getGenerationId }, + { "nativeGetPixel", "(JII)I", (void*)Bitmap_getPixel }, + { "nativeGetColor", "(JII)J", (void*)Bitmap_getColor }, + { "nativeGetPixels", "(J[IIIIIII)V", (void*)Bitmap_getPixels }, + { "nativeSetPixel", "(JIII)V", (void*)Bitmap_setPixel }, + { "nativeSetPixels", "(J[IIIIIII)V", (void*)Bitmap_setPixels }, + { "nativeCopyPixelsToBuffer", "(JLjava/nio/Buffer;)V", + (void*)Bitmap_copyPixelsToBuffer }, + { "nativeCopyPixelsFromBuffer", "(JLjava/nio/Buffer;)V", + (void*)Bitmap_copyPixelsFromBuffer }, + { "nativeSameAs", "(JJ)Z", (void*)Bitmap_sameAs }, + { "nativePrepareToDraw", "(J)V", (void*)Bitmap_prepareToDraw }, + { "nativeGetAllocationByteCount", "(J)I", (void*)Bitmap_getAllocationByteCount }, + { "nativeCopyPreserveInternalConfig", "(J)Landroid/graphics/Bitmap;", + (void*)Bitmap_copyPreserveInternalConfig }, + { "nativeWrapHardwareBufferBitmap", "(Landroid/hardware/HardwareBuffer;J)Landroid/graphics/Bitmap;", + (void*) Bitmap_wrapHardwareBufferBitmap }, + { "nativeGetHardwareBuffer", "(J)Landroid/hardware/HardwareBuffer;", + (void*) Bitmap_getHardwareBuffer }, + { "nativeComputeColorSpace", "(J)Landroid/graphics/ColorSpace;", (void*)Bitmap_computeColorSpace }, + { "nativeSetColorSpace", "(JJ)V", (void*)Bitmap_setColorSpace }, + { "nativeIsSRGB", "(J)Z", (void*)Bitmap_isSRGB }, + { "nativeIsSRGBLinear", "(J)Z", (void*)Bitmap_isSRGBLinear}, + { "nativeSetImmutable", "(J)V", (void*)Bitmap_setImmutable}, + + // ------------ @CriticalNative ---------------- + { "nativeIsImmutable", "(J)Z", (void*)Bitmap_isImmutable} + +}; + +const char* const kParcelPathName = "android/os/Parcel"; + +int register_android_graphics_Bitmap(JNIEnv* env) +{ + gBitmap_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Bitmap")); + gBitmap_nativePtr = GetFieldIDOrDie(env, gBitmap_class, "mNativePtr", "J"); + gBitmap_constructorMethodID = GetMethodIDOrDie(env, gBitmap_class, "<init>", "(JIIIZ[BLandroid/graphics/NinePatch$InsetStruct;Z)V"); + gBitmap_reinitMethodID = GetMethodIDOrDie(env, gBitmap_class, "reinit", "(IIZ)V"); + +#ifdef __ANDROID__ // Layoutlib does not support graphic buffer or parcel + void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE); + AHardwareBuffer_fromHardwareBuffer = + (AHB_from_HB)dlsym(handle_, "AHardwareBuffer_fromHardwareBuffer"); + LOG_ALWAYS_FATAL_IF(AHardwareBuffer_fromHardwareBuffer == nullptr, + "Failed to find required symbol AHardwareBuffer_fromHardwareBuffer!"); + + AHardwareBuffer_toHardwareBuffer = (AHB_to_HB)dlsym(handle_, "AHardwareBuffer_toHardwareBuffer"); + LOG_ALWAYS_FATAL_IF(AHardwareBuffer_toHardwareBuffer == nullptr, + " Failed to find required symbol AHardwareBuffer_toHardwareBuffer!"); + + gParcelOffsets.clazz = MakeGlobalRefOrDie(env, FindClassOrDie(env, kParcelPathName)); + gParcelOffsets.mNativePtr = GetFieldIDOrDie(env, gParcelOffsets.clazz, "mNativePtr", "J"); +#endif + return android::RegisterMethodsOrDie(env, "android/graphics/Bitmap", gBitmapMethods, + NELEM(gBitmapMethods)); +} diff --git a/libs/hwui/jni/Bitmap.h b/libs/hwui/jni/Bitmap.h new file mode 100644 index 000000000000..73eca3aa8ef8 --- /dev/null +++ b/libs/hwui/jni/Bitmap.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef BITMAP_H_ +#define BITMAP_H_ + +#include <jni.h> +#include <android/bitmap.h> + +class SkBitmap; +struct SkImageInfo; + +namespace android { + +class Bitmap; + +namespace bitmap { + +enum BitmapCreateFlags { + kBitmapCreateFlag_None = 0x0, + kBitmapCreateFlag_Mutable = 0x1, + kBitmapCreateFlag_Premultiplied = 0x2, +}; + +jobject createBitmap(JNIEnv* env, Bitmap* bitmap, + int bitmapCreateFlags, jbyteArray ninePatchChunk = nullptr, + jobject ninePatchInsets = nullptr, int density = -1); + +Bitmap& toBitmap(jlong bitmapHandle); + +/** Reinitialize a bitmap. bitmap must already have its SkAlphaType set in + sync with isPremultiplied +*/ +void reinitBitmap(JNIEnv* env, jobject javaBitmap, const SkImageInfo& info, + bool isPremultiplied); + +} // namespace bitmap + +} // namespace android + +#endif /* BITMAP_H_ */ diff --git a/libs/hwui/jni/BitmapFactory.cpp b/libs/hwui/jni/BitmapFactory.cpp new file mode 100644 index 000000000000..d4e27d812500 --- /dev/null +++ b/libs/hwui/jni/BitmapFactory.cpp @@ -0,0 +1,667 @@ +#undef LOG_TAG +#define LOG_TAG "BitmapFactory" + +#include "BitmapFactory.h" +#include "CreateJavaOutputStreamAdaptor.h" +#include "GraphicsJNI.h" +#include "MimeType.h" +#include "NinePatchPeeker.h" +#include "SkAndroidCodec.h" +#include "SkBRDAllocator.h" +#include "SkFrontBufferedStream.h" +#include "SkMath.h" +#include "SkPixelRef.h" +#include "SkStream.h" +#include "SkUtils.h" +#include "Utils.h" + +#include <HardwareBitmapUploader.h> +#include <nativehelper/JNIHelp.h> +#include <androidfw/Asset.h> +#include <androidfw/ResourceTypes.h> +#include <cutils/compiler.h> +#include <fcntl.h> +#include <memory> +#include <stdio.h> +#include <sys/stat.h> + +jfieldID gOptions_justBoundsFieldID; +jfieldID gOptions_sampleSizeFieldID; +jfieldID gOptions_configFieldID; +jfieldID gOptions_colorSpaceFieldID; +jfieldID gOptions_premultipliedFieldID; +jfieldID gOptions_mutableFieldID; +jfieldID gOptions_ditherFieldID; +jfieldID gOptions_preferQualityOverSpeedFieldID; +jfieldID gOptions_scaledFieldID; +jfieldID gOptions_densityFieldID; +jfieldID gOptions_screenDensityFieldID; +jfieldID gOptions_targetDensityFieldID; +jfieldID gOptions_widthFieldID; +jfieldID gOptions_heightFieldID; +jfieldID gOptions_mimeFieldID; +jfieldID gOptions_outConfigFieldID; +jfieldID gOptions_outColorSpaceFieldID; +jfieldID gOptions_mCancelID; +jfieldID gOptions_bitmapFieldID; + +jfieldID gBitmap_ninePatchInsetsFieldID; + +jclass gBitmapConfig_class; +jmethodID gBitmapConfig_nativeToConfigMethodID; + +using namespace android; + +const char* getMimeType(SkEncodedImageFormat format) { + switch (format) { + case SkEncodedImageFormat::kBMP: + return "image/bmp"; + case SkEncodedImageFormat::kGIF: + return "image/gif"; + case SkEncodedImageFormat::kICO: + return "image/x-ico"; + case SkEncodedImageFormat::kJPEG: + return "image/jpeg"; + case SkEncodedImageFormat::kPNG: + return "image/png"; + case SkEncodedImageFormat::kWEBP: + return "image/webp"; + case SkEncodedImageFormat::kHEIF: + return "image/heif"; + case SkEncodedImageFormat::kWBMP: + return "image/vnd.wap.wbmp"; + case SkEncodedImageFormat::kDNG: + return "image/x-adobe-dng"; + default: + return nullptr; + } +} + +jstring getMimeTypeAsJavaString(JNIEnv* env, SkEncodedImageFormat format) { + jstring jstr = nullptr; + const char* mimeType = getMimeType(format); + if (mimeType) { + // NOTE: Caller should env->ExceptionCheck() for OOM + // (can't check for nullptr as it's a valid return value) + jstr = env->NewStringUTF(mimeType); + } + return jstr; +} + +class ScaleCheckingAllocator : public SkBitmap::HeapAllocator { +public: + ScaleCheckingAllocator(float scale, int size) + : mScale(scale), mSize(size) { + } + + virtual bool allocPixelRef(SkBitmap* bitmap) { + // accounts for scale in final allocation, using eventual size and config + const int bytesPerPixel = SkColorTypeBytesPerPixel(bitmap->colorType()); + const int requestedSize = bytesPerPixel * + int(bitmap->width() * mScale + 0.5f) * + int(bitmap->height() * mScale + 0.5f); + if (requestedSize > mSize) { + ALOGW("bitmap for alloc reuse (%d bytes) can't fit scaled bitmap (%d bytes)", + mSize, requestedSize); + return false; + } + return SkBitmap::HeapAllocator::allocPixelRef(bitmap); + } +private: + const float mScale; + const int mSize; +}; + +class RecyclingPixelAllocator : public SkBitmap::Allocator { +public: + RecyclingPixelAllocator(android::Bitmap* bitmap, unsigned int size) + : mBitmap(bitmap), mSize(size) { + } + + ~RecyclingPixelAllocator() { + } + + virtual bool allocPixelRef(SkBitmap* bitmap) { + const SkImageInfo& info = bitmap->info(); + if (info.colorType() == kUnknown_SkColorType) { + ALOGW("unable to reuse a bitmap as the target has an unknown bitmap configuration"); + return false; + } + + const size_t size = info.computeByteSize(bitmap->rowBytes()); + if (size > SK_MaxS32) { + ALOGW("bitmap is too large"); + return false; + } + + if (size > mSize) { + ALOGW("bitmap marked for reuse (%u bytes) can't fit new bitmap " + "(%zu bytes)", mSize, size); + return false; + } + + mBitmap->reconfigure(info, bitmap->rowBytes()); + bitmap->setPixelRef(sk_ref_sp(mBitmap), 0, 0); + return true; + } + +private: + android::Bitmap* const mBitmap; + const unsigned int mSize; +}; + +// Necessary for decodes when the native decoder cannot scale to appropriately match the sampleSize +// (for example, RAW). If the sampleSize divides evenly into the dimension, we require that the +// scale matches exactly. If sampleSize does not divide evenly, we allow the decoder to choose how +// best to round. +static bool needsFineScale(const int fullSize, const int decodedSize, const int sampleSize) { + if (fullSize % sampleSize == 0 && fullSize / sampleSize != decodedSize) { + return true; + } else if ((fullSize / sampleSize + 1) != decodedSize && + (fullSize / sampleSize) != decodedSize) { + return true; + } + return false; +} + +static bool needsFineScale(const SkISize fullSize, const SkISize decodedSize, + const int sampleSize) { + return needsFineScale(fullSize.width(), decodedSize.width(), sampleSize) || + needsFineScale(fullSize.height(), decodedSize.height(), sampleSize); +} + +static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream, + jobject padding, jobject options, jlong inBitmapHandle, + jlong colorSpaceHandle) { + // Set default values for the options parameters. + int sampleSize = 1; + bool onlyDecodeSize = false; + SkColorType prefColorType = kN32_SkColorType; + bool isHardware = false; + bool isMutable = false; + float scale = 1.0f; + bool requireUnpremultiplied = false; + jobject javaBitmap = NULL; + sk_sp<SkColorSpace> prefColorSpace = GraphicsJNI::getNativeColorSpace(colorSpaceHandle); + + // Update with options supplied by the client. + if (options != NULL) { + sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID); + // Correct a non-positive sampleSize. sampleSize defaults to zero within the + // options object, which is strange. + if (sampleSize <= 0) { + sampleSize = 1; + } + + if (env->GetBooleanField(options, gOptions_justBoundsFieldID)) { + onlyDecodeSize = true; + } + + // initialize these, in case we fail later on + env->SetIntField(options, gOptions_widthFieldID, -1); + env->SetIntField(options, gOptions_heightFieldID, -1); + env->SetObjectField(options, gOptions_mimeFieldID, 0); + env->SetObjectField(options, gOptions_outConfigFieldID, 0); + env->SetObjectField(options, gOptions_outColorSpaceFieldID, 0); + + jobject jconfig = env->GetObjectField(options, gOptions_configFieldID); + prefColorType = GraphicsJNI::getNativeBitmapColorType(env, jconfig); + isHardware = GraphicsJNI::isHardwareConfig(env, jconfig); + isMutable = env->GetBooleanField(options, gOptions_mutableFieldID); + requireUnpremultiplied = !env->GetBooleanField(options, gOptions_premultipliedFieldID); + javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID); + + if (env->GetBooleanField(options, gOptions_scaledFieldID)) { + const int density = env->GetIntField(options, gOptions_densityFieldID); + const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID); + const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID); + if (density != 0 && targetDensity != 0 && density != screenDensity) { + scale = (float) targetDensity / density; + } + } + } + + if (isMutable && isHardware) { + doThrowIAE(env, "Bitmaps with Config.HARDWARE are always immutable"); + return nullObjectReturn("Cannot create mutable hardware bitmap"); + } + + // Create the codec. + NinePatchPeeker peeker; + std::unique_ptr<SkAndroidCodec> codec; + { + SkCodec::Result result; + std::unique_ptr<SkCodec> c = SkCodec::MakeFromStream(std::move(stream), &result, + &peeker); + if (!c) { + SkString msg; + msg.printf("Failed to create image decoder with message '%s'", + SkCodec::ResultToString(result)); + return nullObjectReturn(msg.c_str()); + } + + codec = SkAndroidCodec::MakeFromCodec(std::move(c)); + if (!codec) { + return nullObjectReturn("SkAndroidCodec::MakeFromCodec returned null"); + } + } + + // Do not allow ninepatch decodes to 565. In the past, decodes to 565 + // would dither, and we do not want to pre-dither ninepatches, since we + // know that they will be stretched. We no longer dither 565 decodes, + // but we continue to prevent ninepatches from decoding to 565, in order + // to maintain the old behavior. + if (peeker.mPatch && kRGB_565_SkColorType == prefColorType) { + prefColorType = kN32_SkColorType; + } + + // Determine the output size. + SkISize size = codec->getSampledDimensions(sampleSize); + + int scaledWidth = size.width(); + int scaledHeight = size.height(); + bool willScale = false; + + // Apply a fine scaling step if necessary. + if (needsFineScale(codec->getInfo().dimensions(), size, sampleSize)) { + willScale = true; + scaledWidth = codec->getInfo().width() / sampleSize; + scaledHeight = codec->getInfo().height() / sampleSize; + } + + // Set the decode colorType + SkColorType decodeColorType = codec->computeOutputColorType(prefColorType); + if (decodeColorType == kRGBA_F16_SkColorType && isHardware && + !uirenderer::HardwareBitmapUploader::hasFP16Support()) { + decodeColorType = kN32_SkColorType; + } + + sk_sp<SkColorSpace> decodeColorSpace = codec->computeOutputColorSpace( + decodeColorType, prefColorSpace); + + // Set the options and return if the client only wants the size. + if (options != NULL) { + jstring mimeType = getMimeTypeAsJavaString(env, codec->getEncodedFormat()); + if (env->ExceptionCheck()) { + return nullObjectReturn("OOM in getMimeTypeAsJavaString()"); + } + env->SetIntField(options, gOptions_widthFieldID, scaledWidth); + env->SetIntField(options, gOptions_heightFieldID, scaledHeight); + env->SetObjectField(options, gOptions_mimeFieldID, mimeType); + + jint configID = GraphicsJNI::colorTypeToLegacyBitmapConfig(decodeColorType); + if (isHardware) { + configID = GraphicsJNI::kHardware_LegacyBitmapConfig; + } + jobject config = env->CallStaticObjectMethod(gBitmapConfig_class, + gBitmapConfig_nativeToConfigMethodID, configID); + env->SetObjectField(options, gOptions_outConfigFieldID, config); + + env->SetObjectField(options, gOptions_outColorSpaceFieldID, + GraphicsJNI::getColorSpace(env, decodeColorSpace.get(), decodeColorType)); + + if (onlyDecodeSize) { + return nullptr; + } + } + + // Scale is necessary due to density differences. + if (scale != 1.0f) { + willScale = true; + scaledWidth = static_cast<int>(scaledWidth * scale + 0.5f); + scaledHeight = static_cast<int>(scaledHeight * scale + 0.5f); + } + + android::Bitmap* reuseBitmap = nullptr; + unsigned int existingBufferSize = 0; + if (javaBitmap != nullptr) { + reuseBitmap = &bitmap::toBitmap(inBitmapHandle); + if (reuseBitmap->isImmutable()) { + ALOGW("Unable to reuse an immutable bitmap as an image decoder target."); + javaBitmap = nullptr; + reuseBitmap = nullptr; + } else { + existingBufferSize = reuseBitmap->getAllocationByteCount(); + } + } + + HeapAllocator defaultAllocator; + RecyclingPixelAllocator recyclingAllocator(reuseBitmap, existingBufferSize); + ScaleCheckingAllocator scaleCheckingAllocator(scale, existingBufferSize); + SkBitmap::HeapAllocator heapAllocator; + SkBitmap::Allocator* decodeAllocator; + if (javaBitmap != nullptr && willScale) { + // This will allocate pixels using a HeapAllocator, since there will be an extra + // scaling step that copies these pixels into Java memory. This allocator + // also checks that the recycled javaBitmap is large enough. + decodeAllocator = &scaleCheckingAllocator; + } else if (javaBitmap != nullptr) { + decodeAllocator = &recyclingAllocator; + } else if (willScale || isHardware) { + // This will allocate pixels using a HeapAllocator, + // for scale case: there will be an extra scaling step. + // for hardware case: there will be extra swizzling & upload to gralloc step. + decodeAllocator = &heapAllocator; + } else { + decodeAllocator = &defaultAllocator; + } + + SkAlphaType alphaType = codec->computeOutputAlphaType(requireUnpremultiplied); + + const SkImageInfo decodeInfo = SkImageInfo::Make(size.width(), size.height(), + decodeColorType, alphaType, decodeColorSpace); + + SkImageInfo bitmapInfo = decodeInfo; + if (decodeColorType == kGray_8_SkColorType) { + // The legacy implementation of BitmapFactory used kAlpha8 for + // grayscale images (before kGray8 existed). While the codec + // recognizes kGray8, we need to decode into a kAlpha8 bitmap + // in order to avoid a behavior change. + bitmapInfo = + bitmapInfo.makeColorType(kAlpha_8_SkColorType).makeAlphaType(kPremul_SkAlphaType); + } + SkBitmap decodingBitmap; + if (!decodingBitmap.setInfo(bitmapInfo) || + !decodingBitmap.tryAllocPixels(decodeAllocator)) { + // SkAndroidCodec should recommend a valid SkImageInfo, so setInfo() + // should only only fail if the calculated value for rowBytes is too + // large. + // tryAllocPixels() can fail due to OOM on the Java heap, OOM on the + // native heap, or the recycled javaBitmap being too small to reuse. + return nullptr; + } + + // Use SkAndroidCodec to perform the decode. + SkAndroidCodec::AndroidOptions codecOptions; + codecOptions.fZeroInitialized = decodeAllocator == &defaultAllocator ? + SkCodec::kYes_ZeroInitialized : SkCodec::kNo_ZeroInitialized; + codecOptions.fSampleSize = sampleSize; + SkCodec::Result result = codec->getAndroidPixels(decodeInfo, decodingBitmap.getPixels(), + decodingBitmap.rowBytes(), &codecOptions); + switch (result) { + case SkCodec::kSuccess: + case SkCodec::kIncompleteInput: + break; + default: + return nullObjectReturn("codec->getAndroidPixels() failed."); + } + + // This is weird so let me explain: we could use the scale parameter + // directly, but for historical reasons this is how the corresponding + // Dalvik code has always behaved. We simply recreate the behavior here. + // The result is slightly different from simply using scale because of + // the 0.5f rounding bias applied when computing the target image size + const float scaleX = scaledWidth / float(decodingBitmap.width()); + const float scaleY = scaledHeight / float(decodingBitmap.height()); + + jbyteArray ninePatchChunk = NULL; + if (peeker.mPatch != NULL) { + if (willScale) { + peeker.scale(scaleX, scaleY, scaledWidth, scaledHeight); + } + + size_t ninePatchArraySize = peeker.mPatch->serializedSize(); + ninePatchChunk = env->NewByteArray(ninePatchArraySize); + if (ninePatchChunk == NULL) { + return nullObjectReturn("ninePatchChunk == null"); + } + + jbyte* array = (jbyte*) env->GetPrimitiveArrayCritical(ninePatchChunk, NULL); + if (array == NULL) { + return nullObjectReturn("primitive array == null"); + } + + memcpy(array, peeker.mPatch, peeker.mPatchSize); + env->ReleasePrimitiveArrayCritical(ninePatchChunk, array, 0); + } + + jobject ninePatchInsets = NULL; + if (peeker.mHasInsets) { + ninePatchInsets = peeker.createNinePatchInsets(env, scale); + if (ninePatchInsets == NULL) { + return nullObjectReturn("nine patch insets == null"); + } + if (javaBitmap != NULL) { + env->SetObjectField(javaBitmap, gBitmap_ninePatchInsetsFieldID, ninePatchInsets); + } + } + + SkBitmap outputBitmap; + if (willScale) { + // Set the allocator for the outputBitmap. + SkBitmap::Allocator* outputAllocator; + if (javaBitmap != nullptr) { + outputAllocator = &recyclingAllocator; + } else { + outputAllocator = &defaultAllocator; + } + + SkColorType scaledColorType = decodingBitmap.colorType(); + // FIXME: If the alphaType is kUnpremul and the image has alpha, the + // colors may not be correct, since Skia does not yet support drawing + // to/from unpremultiplied bitmaps. + outputBitmap.setInfo( + bitmapInfo.makeWH(scaledWidth, scaledHeight).makeColorType(scaledColorType)); + if (!outputBitmap.tryAllocPixels(outputAllocator)) { + // This should only fail on OOM. The recyclingAllocator should have + // enough memory since we check this before decoding using the + // scaleCheckingAllocator. + return nullObjectReturn("allocation failed for scaled bitmap"); + } + + SkPaint paint; + // kSrc_Mode instructs us to overwrite the uninitialized pixels in + // outputBitmap. Otherwise we would blend by default, which is not + // what we want. + paint.setBlendMode(SkBlendMode::kSrc); + paint.setFilterQuality(kLow_SkFilterQuality); // bilinear filtering + + SkCanvas canvas(outputBitmap, SkCanvas::ColorBehavior::kLegacy); + canvas.scale(scaleX, scaleY); + canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint); + } else { + outputBitmap.swap(decodingBitmap); + } + + if (padding) { + peeker.getPadding(env, padding); + } + + // If we get here, the outputBitmap should have an installed pixelref. + if (outputBitmap.pixelRef() == NULL) { + return nullObjectReturn("Got null SkPixelRef"); + } + + if (!isMutable && javaBitmap == NULL) { + // promise we will never change our pixels (great for sharing and pictures) + outputBitmap.setImmutable(); + } + + bool isPremultiplied = !requireUnpremultiplied; + if (javaBitmap != nullptr) { + bitmap::reinitBitmap(env, javaBitmap, outputBitmap.info(), isPremultiplied); + outputBitmap.notifyPixelsChanged(); + // If a java bitmap was passed in for reuse, pass it back + return javaBitmap; + } + + int bitmapCreateFlags = 0x0; + if (isMutable) bitmapCreateFlags |= android::bitmap::kBitmapCreateFlag_Mutable; + if (isPremultiplied) bitmapCreateFlags |= android::bitmap::kBitmapCreateFlag_Premultiplied; + + if (isHardware) { + sk_sp<Bitmap> hardwareBitmap = Bitmap::allocateHardwareBitmap(outputBitmap); + if (!hardwareBitmap.get()) { + return nullObjectReturn("Failed to allocate a hardware bitmap"); + } + return bitmap::createBitmap(env, hardwareBitmap.release(), bitmapCreateFlags, + ninePatchChunk, ninePatchInsets, -1); + } + + // now create the java bitmap + return bitmap::createBitmap(env, defaultAllocator.getStorageObjAndReset(), + bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1); +} + +static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage, + jobject padding, jobject options, jlong inBitmapHandle, jlong colorSpaceHandle) { + + jobject bitmap = NULL; + std::unique_ptr<SkStream> stream(CreateJavaInputStreamAdaptor(env, is, storage)); + + if (stream.get()) { + std::unique_ptr<SkStreamRewindable> bufferedStream( + SkFrontBufferedStream::Make(std::move(stream), SkCodec::MinBufferedBytesNeeded())); + SkASSERT(bufferedStream.get() != NULL); + bitmap = doDecode(env, std::move(bufferedStream), padding, options, inBitmapHandle, + colorSpaceHandle); + } + return bitmap; +} + +static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz, jobject fileDescriptor, + jobject padding, jobject bitmapFactoryOptions, jlong inBitmapHandle, jlong colorSpaceHandle) { +#ifndef __ANDROID__ // LayoutLib for Windows does not support F_DUPFD_CLOEXEC + return nullObjectReturn("Not supported on Windows"); +#else + NPE_CHECK_RETURN_ZERO(env, fileDescriptor); + + int descriptor = jniGetFDFromFileDescriptor(env, fileDescriptor); + + struct stat fdStat; + if (fstat(descriptor, &fdStat) == -1) { + doThrowIOE(env, "broken file descriptor"); + return nullObjectReturn("fstat return -1"); + } + + // Restore the descriptor's offset on exiting this function. Even though + // we dup the descriptor, both the original and dup refer to the same open + // file description and changes to the file offset in one impact the other. + AutoFDSeek autoRestore(descriptor); + + // Duplicate the descriptor here to prevent leaking memory. A leak occurs + // if we only close the file descriptor and not the file object it is used to + // create. If we don't explicitly clean up the file (which in turn closes the + // descriptor) the buffers allocated internally by fseek will be leaked. + int dupDescriptor = fcntl(descriptor, F_DUPFD_CLOEXEC, 0); + + FILE* file = fdopen(dupDescriptor, "r"); + if (file == NULL) { + // cleanup the duplicated descriptor since it will not be closed when the + // file is cleaned up (fclose). + close(dupDescriptor); + return nullObjectReturn("Could not open file"); + } + + std::unique_ptr<SkFILEStream> fileStream(new SkFILEStream(file)); + + // If there is no offset for the file descriptor, we use SkFILEStream directly. + if (::lseek(descriptor, 0, SEEK_CUR) == 0) { + assert(isSeekable(dupDescriptor)); + return doDecode(env, std::move(fileStream), padding, bitmapFactoryOptions, + inBitmapHandle, colorSpaceHandle); + } + + // Use a buffered stream. Although an SkFILEStream can be rewound, this + // ensures that SkImageDecoder::Factory never rewinds beyond the + // current position of the file descriptor. + std::unique_ptr<SkStreamRewindable> stream(SkFrontBufferedStream::Make(std::move(fileStream), + SkCodec::MinBufferedBytesNeeded())); + + return doDecode(env, std::move(stream), padding, bitmapFactoryOptions, inBitmapHandle, + colorSpaceHandle); +#endif +} + +static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz, jlong native_asset, + jobject padding, jobject options, jlong inBitmapHandle, jlong colorSpaceHandle) { + + Asset* asset = reinterpret_cast<Asset*>(native_asset); + // since we know we'll be done with the asset when we return, we can + // just use a simple wrapper + return doDecode(env, std::make_unique<AssetStreamAdaptor>(asset), padding, options, + inBitmapHandle, colorSpaceHandle); +} + +static jobject nativeDecodeByteArray(JNIEnv* env, jobject, jbyteArray byteArray, + jint offset, jint length, jobject options, jlong inBitmapHandle, jlong colorSpaceHandle) { + + AutoJavaByteArray ar(env, byteArray); + return doDecode(env, std::make_unique<SkMemoryStream>(ar.ptr() + offset, length, false), + nullptr, options, inBitmapHandle, colorSpaceHandle); +} + +static jboolean nativeIsSeekable(JNIEnv* env, jobject, jobject fileDescriptor) { + jint descriptor = jniGetFDFromFileDescriptor(env, fileDescriptor); + return isSeekable(descriptor) ? JNI_TRUE : JNI_FALSE; +} + +/////////////////////////////////////////////////////////////////////////////// + +static const JNINativeMethod gMethods[] = { + { "nativeDecodeStream", + "(Ljava/io/InputStream;[BLandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;JJ)Landroid/graphics/Bitmap;", + (void*)nativeDecodeStream + }, + + { "nativeDecodeFileDescriptor", + "(Ljava/io/FileDescriptor;Landroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;JJ)Landroid/graphics/Bitmap;", + (void*)nativeDecodeFileDescriptor + }, + + { "nativeDecodeAsset", + "(JLandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;JJ)Landroid/graphics/Bitmap;", + (void*)nativeDecodeAsset + }, + + { "nativeDecodeByteArray", + "([BIILandroid/graphics/BitmapFactory$Options;JJ)Landroid/graphics/Bitmap;", + (void*)nativeDecodeByteArray + }, + + { "nativeIsSeekable", + "(Ljava/io/FileDescriptor;)Z", + (void*)nativeIsSeekable + }, +}; + +int register_android_graphics_BitmapFactory(JNIEnv* env) { + jclass options_class = FindClassOrDie(env, "android/graphics/BitmapFactory$Options"); + gOptions_bitmapFieldID = GetFieldIDOrDie(env, options_class, "inBitmap", + "Landroid/graphics/Bitmap;"); + gOptions_justBoundsFieldID = GetFieldIDOrDie(env, options_class, "inJustDecodeBounds", "Z"); + gOptions_sampleSizeFieldID = GetFieldIDOrDie(env, options_class, "inSampleSize", "I"); + gOptions_configFieldID = GetFieldIDOrDie(env, options_class, "inPreferredConfig", + "Landroid/graphics/Bitmap$Config;"); + gOptions_colorSpaceFieldID = GetFieldIDOrDie(env, options_class, "inPreferredColorSpace", + "Landroid/graphics/ColorSpace;"); + gOptions_premultipliedFieldID = GetFieldIDOrDie(env, options_class, "inPremultiplied", "Z"); + gOptions_mutableFieldID = GetFieldIDOrDie(env, options_class, "inMutable", "Z"); + gOptions_ditherFieldID = GetFieldIDOrDie(env, options_class, "inDither", "Z"); + gOptions_preferQualityOverSpeedFieldID = GetFieldIDOrDie(env, options_class, + "inPreferQualityOverSpeed", "Z"); + gOptions_scaledFieldID = GetFieldIDOrDie(env, options_class, "inScaled", "Z"); + gOptions_densityFieldID = GetFieldIDOrDie(env, options_class, "inDensity", "I"); + gOptions_screenDensityFieldID = GetFieldIDOrDie(env, options_class, "inScreenDensity", "I"); + gOptions_targetDensityFieldID = GetFieldIDOrDie(env, options_class, "inTargetDensity", "I"); + gOptions_widthFieldID = GetFieldIDOrDie(env, options_class, "outWidth", "I"); + gOptions_heightFieldID = GetFieldIDOrDie(env, options_class, "outHeight", "I"); + gOptions_mimeFieldID = GetFieldIDOrDie(env, options_class, "outMimeType", "Ljava/lang/String;"); + gOptions_outConfigFieldID = GetFieldIDOrDie(env, options_class, "outConfig", + "Landroid/graphics/Bitmap$Config;"); + gOptions_outColorSpaceFieldID = GetFieldIDOrDie(env, options_class, "outColorSpace", + "Landroid/graphics/ColorSpace;"); + gOptions_mCancelID = GetFieldIDOrDie(env, options_class, "mCancel", "Z"); + + jclass bitmap_class = FindClassOrDie(env, "android/graphics/Bitmap"); + gBitmap_ninePatchInsetsFieldID = GetFieldIDOrDie(env, bitmap_class, "mNinePatchInsets", + "Landroid/graphics/NinePatch$InsetStruct;"); + + gBitmapConfig_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, + "android/graphics/Bitmap$Config")); + gBitmapConfig_nativeToConfigMethodID = GetStaticMethodIDOrDie(env, gBitmapConfig_class, + "nativeToConfig", "(I)Landroid/graphics/Bitmap$Config;"); + + return android::RegisterMethodsOrDie(env, "android/graphics/BitmapFactory", + gMethods, NELEM(gMethods)); +} diff --git a/libs/hwui/jni/BitmapFactory.h b/libs/hwui/jni/BitmapFactory.h new file mode 100644 index 000000000000..45bffc44967d --- /dev/null +++ b/libs/hwui/jni/BitmapFactory.h @@ -0,0 +1,31 @@ +#ifndef _ANDROID_GRAPHICS_BITMAP_FACTORY_H_ +#define _ANDROID_GRAPHICS_BITMAP_FACTORY_H_ + +#include "GraphicsJNI.h" +#include "SkEncodedImageFormat.h" + +extern jclass gOptions_class; +extern jfieldID gOptions_justBoundsFieldID; +extern jfieldID gOptions_sampleSizeFieldID; +extern jfieldID gOptions_configFieldID; +extern jfieldID gOptions_colorSpaceFieldID; +extern jfieldID gOptions_premultipliedFieldID; +extern jfieldID gOptions_ditherFieldID; +extern jfieldID gOptions_purgeableFieldID; +extern jfieldID gOptions_shareableFieldID; +extern jfieldID gOptions_nativeAllocFieldID; +extern jfieldID gOptions_preferQualityOverSpeedFieldID; +extern jfieldID gOptions_widthFieldID; +extern jfieldID gOptions_heightFieldID; +extern jfieldID gOptions_mimeFieldID; +extern jfieldID gOptions_outConfigFieldID; +extern jfieldID gOptions_outColorSpaceFieldID; +extern jfieldID gOptions_mCancelID; +extern jfieldID gOptions_bitmapFieldID; + +extern jclass gBitmapConfig_class; +extern jmethodID gBitmapConfig_nativeToConfigMethodID; + +jstring getMimeTypeAsJavaString(JNIEnv*, SkEncodedImageFormat); + +#endif // _ANDROID_GRAPHICS_BITMAP_FACTORY_H_ diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp new file mode 100644 index 000000000000..712351382d97 --- /dev/null +++ b/libs/hwui/jni/BitmapRegionDecoder.cpp @@ -0,0 +1,288 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#undef LOG_TAG +#define LOG_TAG "BitmapRegionDecoder" + +#include "BitmapFactory.h" +#include "CreateJavaOutputStreamAdaptor.h" +#include "GraphicsJNI.h" +#include "Utils.h" + +#include "SkBitmap.h" +#include "SkBitmapRegionDecoder.h" +#include "SkCodec.h" +#include "SkData.h" +#include "SkStream.h" + +#include <HardwareBitmapUploader.h> +#include <androidfw/Asset.h> +#include <sys/stat.h> + +#include <memory> + +using namespace android; + +static jobject createBitmapRegionDecoder(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream) { + std::unique_ptr<SkBitmapRegionDecoder> brd( + SkBitmapRegionDecoder::Create(stream.release(), + SkBitmapRegionDecoder::kAndroidCodec_Strategy)); + if (!brd) { + doThrowIOE(env, "Image format not supported"); + return nullObjectReturn("CreateBitmapRegionDecoder returned null"); + } + + return GraphicsJNI::createBitmapRegionDecoder(env, brd.release()); +} + +static jobject nativeNewInstanceFromByteArray(JNIEnv* env, jobject, jbyteArray byteArray, + jint offset, jint length, jboolean isShareable) { + /* If isShareable we could decide to just wrap the java array and + share it, but that means adding a globalref to the java array object + For now we just always copy the array's data if isShareable. + */ + AutoJavaByteArray ar(env, byteArray); + std::unique_ptr<SkMemoryStream> stream(new SkMemoryStream(ar.ptr() + offset, length, true)); + + // the decoder owns the stream. + jobject brd = createBitmapRegionDecoder(env, std::move(stream)); + return brd; +} + +static jobject nativeNewInstanceFromFileDescriptor(JNIEnv* env, jobject clazz, + jobject fileDescriptor, jboolean isShareable) { + NPE_CHECK_RETURN_ZERO(env, fileDescriptor); + + jint descriptor = jniGetFDFromFileDescriptor(env, fileDescriptor); + + struct stat fdStat; + if (fstat(descriptor, &fdStat) == -1) { + doThrowIOE(env, "broken file descriptor"); + return nullObjectReturn("fstat return -1"); + } + + sk_sp<SkData> data(SkData::MakeFromFD(descriptor)); + std::unique_ptr<SkMemoryStream> stream(new SkMemoryStream(std::move(data))); + + // the decoder owns the stream. + jobject brd = createBitmapRegionDecoder(env, std::move(stream)); + return brd; +} + +static jobject nativeNewInstanceFromStream(JNIEnv* env, jobject clazz, + jobject is, // InputStream + jbyteArray storage, // byte[] + jboolean isShareable) { + jobject brd = NULL; + // for now we don't allow shareable with java inputstreams + std::unique_ptr<SkStreamRewindable> stream(CopyJavaInputStream(env, is, storage)); + + if (stream) { + // the decoder owns the stream. + brd = createBitmapRegionDecoder(env, std::move(stream)); + } + return brd; +} + +static jobject nativeNewInstanceFromAsset(JNIEnv* env, jobject clazz, + jlong native_asset, // Asset + jboolean isShareable) { + Asset* asset = reinterpret_cast<Asset*>(native_asset); + std::unique_ptr<SkMemoryStream> stream(CopyAssetToStream(asset)); + if (NULL == stream) { + return NULL; + } + + // the decoder owns the stream. + jobject brd = createBitmapRegionDecoder(env, std::move(stream)); + return brd; +} + +/* + * nine patch not supported + * purgeable not supported + * reportSizeToVM not supported + */ +static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, jint inputX, + jint inputY, jint inputWidth, jint inputHeight, jobject options, jlong inBitmapHandle, + jlong colorSpaceHandle) { + + // Set default options. + int sampleSize = 1; + SkColorType colorType = kN32_SkColorType; + bool requireUnpremul = false; + jobject javaBitmap = nullptr; + bool isHardware = false; + sk_sp<SkColorSpace> colorSpace = GraphicsJNI::getNativeColorSpace(colorSpaceHandle); + // Update the default options with any options supplied by the client. + if (NULL != options) { + sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID); + jobject jconfig = env->GetObjectField(options, gOptions_configFieldID); + colorType = GraphicsJNI::getNativeBitmapColorType(env, jconfig); + isHardware = GraphicsJNI::isHardwareConfig(env, jconfig); + requireUnpremul = !env->GetBooleanField(options, gOptions_premultipliedFieldID); + javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID); + // The Java options of ditherMode and preferQualityOverSpeed are deprecated. We will + // ignore the values of these fields. + + // Initialize these fields to indicate a failure. If the decode succeeds, we + // will update them later on. + env->SetIntField(options, gOptions_widthFieldID, -1); + env->SetIntField(options, gOptions_heightFieldID, -1); + env->SetObjectField(options, gOptions_mimeFieldID, 0); + env->SetObjectField(options, gOptions_outConfigFieldID, 0); + env->SetObjectField(options, gOptions_outColorSpaceFieldID, 0); + } + + // Recycle a bitmap if possible. + android::Bitmap* recycledBitmap = nullptr; + size_t recycledBytes = 0; + if (javaBitmap) { + recycledBitmap = &bitmap::toBitmap(inBitmapHandle); + if (recycledBitmap->isImmutable()) { + ALOGW("Warning: Reusing an immutable bitmap as an image decoder target."); + } + recycledBytes = recycledBitmap->getAllocationByteCount(); + } + + SkBitmapRegionDecoder* brd = reinterpret_cast<SkBitmapRegionDecoder*>(brdHandle); + SkColorType decodeColorType = brd->computeOutputColorType(colorType); + if (decodeColorType == kRGBA_F16_SkColorType && isHardware && + !uirenderer::HardwareBitmapUploader::hasFP16Support()) { + decodeColorType = kN32_SkColorType; + } + + // Set up the pixel allocator + SkBRDAllocator* allocator = nullptr; + RecyclingClippingPixelAllocator recycleAlloc(recycledBitmap, recycledBytes); + HeapAllocator heapAlloc; + if (javaBitmap) { + allocator = &recycleAlloc; + // We are required to match the color type of the recycled bitmap. + decodeColorType = recycledBitmap->info().colorType(); + } else { + allocator = &heapAlloc; + } + + sk_sp<SkColorSpace> decodeColorSpace = brd->computeOutputColorSpace( + decodeColorType, colorSpace); + + // Decode the region. + SkIRect subset = SkIRect::MakeXYWH(inputX, inputY, inputWidth, inputHeight); + SkBitmap bitmap; + if (!brd->decodeRegion(&bitmap, allocator, subset, sampleSize, + decodeColorType, requireUnpremul, decodeColorSpace)) { + return nullObjectReturn("Failed to decode region."); + } + + // If the client provided options, indicate that the decode was successful. + if (NULL != options) { + env->SetIntField(options, gOptions_widthFieldID, bitmap.width()); + env->SetIntField(options, gOptions_heightFieldID, bitmap.height()); + + env->SetObjectField(options, gOptions_mimeFieldID, + getMimeTypeAsJavaString(env, brd->getEncodedFormat())); + if (env->ExceptionCheck()) { + return nullObjectReturn("OOM in encodedFormatToString()"); + } + + jint configID = GraphicsJNI::colorTypeToLegacyBitmapConfig(decodeColorType); + if (isHardware) { + configID = GraphicsJNI::kHardware_LegacyBitmapConfig; + } + jobject config = env->CallStaticObjectMethod(gBitmapConfig_class, + gBitmapConfig_nativeToConfigMethodID, configID); + env->SetObjectField(options, gOptions_outConfigFieldID, config); + + env->SetObjectField(options, gOptions_outColorSpaceFieldID, + GraphicsJNI::getColorSpace(env, decodeColorSpace.get(), decodeColorType)); + } + + // If we may have reused a bitmap, we need to indicate that the pixels have changed. + if (javaBitmap) { + recycleAlloc.copyIfNecessary(); + bitmap::reinitBitmap(env, javaBitmap, recycledBitmap->info(), !requireUnpremul); + return javaBitmap; + } + + int bitmapCreateFlags = 0; + if (!requireUnpremul) { + bitmapCreateFlags |= android::bitmap::kBitmapCreateFlag_Premultiplied; + } + if (isHardware) { + sk_sp<Bitmap> hardwareBitmap = Bitmap::allocateHardwareBitmap(bitmap); + return bitmap::createBitmap(env, hardwareBitmap.release(), bitmapCreateFlags); + } + return android::bitmap::createBitmap(env, heapAlloc.getStorageObjAndReset(), bitmapCreateFlags); +} + +static jint nativeGetHeight(JNIEnv* env, jobject, jlong brdHandle) { + SkBitmapRegionDecoder* brd = + reinterpret_cast<SkBitmapRegionDecoder*>(brdHandle); + return static_cast<jint>(brd->height()); +} + +static jint nativeGetWidth(JNIEnv* env, jobject, jlong brdHandle) { + SkBitmapRegionDecoder* brd = + reinterpret_cast<SkBitmapRegionDecoder*>(brdHandle); + return static_cast<jint>(brd->width()); +} + +static void nativeClean(JNIEnv* env, jobject, jlong brdHandle) { + SkBitmapRegionDecoder* brd = + reinterpret_cast<SkBitmapRegionDecoder*>(brdHandle); + delete brd; +} + +/////////////////////////////////////////////////////////////////////////////// + +static const JNINativeMethod gBitmapRegionDecoderMethods[] = { + { "nativeDecodeRegion", + "(JIIIILandroid/graphics/BitmapFactory$Options;JJ)Landroid/graphics/Bitmap;", + (void*)nativeDecodeRegion}, + + { "nativeGetHeight", "(J)I", (void*)nativeGetHeight}, + + { "nativeGetWidth", "(J)I", (void*)nativeGetWidth}, + + { "nativeClean", "(J)V", (void*)nativeClean}, + + { "nativeNewInstance", + "([BIIZ)Landroid/graphics/BitmapRegionDecoder;", + (void*)nativeNewInstanceFromByteArray + }, + + { "nativeNewInstance", + "(Ljava/io/InputStream;[BZ)Landroid/graphics/BitmapRegionDecoder;", + (void*)nativeNewInstanceFromStream + }, + + { "nativeNewInstance", + "(Ljava/io/FileDescriptor;Z)Landroid/graphics/BitmapRegionDecoder;", + (void*)nativeNewInstanceFromFileDescriptor + }, + + { "nativeNewInstance", + "(JZ)Landroid/graphics/BitmapRegionDecoder;", + (void*)nativeNewInstanceFromAsset + }, +}; + +int register_android_graphics_BitmapRegionDecoder(JNIEnv* env) +{ + return android::RegisterMethodsOrDie(env, "android/graphics/BitmapRegionDecoder", + gBitmapRegionDecoderMethods, NELEM(gBitmapRegionDecoderMethods)); +} diff --git a/libs/hwui/jni/ByteBufferStreamAdaptor.cpp b/libs/hwui/jni/ByteBufferStreamAdaptor.cpp new file mode 100644 index 000000000000..db5f6f6c684f --- /dev/null +++ b/libs/hwui/jni/ByteBufferStreamAdaptor.cpp @@ -0,0 +1,334 @@ +#include "ByteBufferStreamAdaptor.h" +#include "GraphicsJNI.h" +#include "Utils.h" + +#include <SkStream.h> + +using namespace android; + +static jmethodID gByteBuffer_getMethodID; +static jmethodID gByteBuffer_setPositionMethodID; + +/** + * Helper method for accessing the JNI interface pointer. + * + * Image decoding (which this supports) is started on a thread that is already + * attached to the Java VM. But an AnimatedImageDrawable continues decoding on + * the AnimatedImageThread, which is not attached. This will attach if + * necessary. + */ +static JNIEnv* requireEnv(JavaVM* jvm) { + JNIEnv* env; + if (jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { + if (jvm->AttachCurrentThreadAsDaemon(&env, nullptr) != JNI_OK) { + LOG_ALWAYS_FATAL("Failed to AttachCurrentThread!"); + } + } + return env; +} + +class ByteBufferStream : public SkStreamAsset { +private: + ByteBufferStream(JavaVM* jvm, jobject jbyteBuffer, size_t initialPosition, size_t length, + jbyteArray storage) + : mJvm(jvm) + , mByteBuffer(jbyteBuffer) + , mPosition(0) + , mInitialPosition(initialPosition) + , mLength(length) + , mStorage(storage) {} + +public: + static ByteBufferStream* Create(JavaVM* jvm, JNIEnv* env, jobject jbyteBuffer, + size_t position, size_t length) { + // This object outlives its native method call. + jbyteBuffer = env->NewGlobalRef(jbyteBuffer); + if (!jbyteBuffer) { + return nullptr; + } + + jbyteArray storage = env->NewByteArray(kStorageSize); + if (!storage) { + env->DeleteGlobalRef(jbyteBuffer); + return nullptr; + } + + // This object outlives its native method call. + storage = static_cast<jbyteArray>(env->NewGlobalRef(storage)); + if (!storage) { + env->DeleteGlobalRef(jbyteBuffer); + return nullptr; + } + + return new ByteBufferStream(jvm, jbyteBuffer, position, length, storage); + } + + ~ByteBufferStream() override { + auto* env = requireEnv(mJvm); + env->DeleteGlobalRef(mByteBuffer); + env->DeleteGlobalRef(mStorage); + } + + size_t read(void* buffer, size_t size) override { + if (size > mLength - mPosition) { + size = mLength - mPosition; + } + if (!size) { + return 0; + } + + if (!buffer) { + return this->setPosition(mPosition + size) ? size : 0; + } + + auto* env = requireEnv(mJvm); + size_t bytesRead = 0; + do { + const size_t requested = (size > kStorageSize) ? kStorageSize : size; + const jint jrequested = static_cast<jint>(requested); + env->CallObjectMethod(mByteBuffer, gByteBuffer_getMethodID, mStorage, 0, jrequested); + if (env->ExceptionCheck()) { + ALOGE("Error in ByteBufferStream::read - was the ByteBuffer modified externally?"); + env->ExceptionDescribe(); + env->ExceptionClear(); + mPosition = mLength; + return bytesRead; + } + + env->GetByteArrayRegion(mStorage, 0, requested, reinterpret_cast<jbyte*>(buffer)); + if (env->ExceptionCheck()) { + ALOGE("Internal error in ByteBufferStream::read"); + env->ExceptionDescribe(); + env->ExceptionClear(); + mPosition = mLength; + return bytesRead; + } + + mPosition += requested; + buffer = reinterpret_cast<void*>(reinterpret_cast<char*>(buffer) + requested); + bytesRead += requested; + size -= requested; + } while (size); + return bytesRead; + } + + bool isAtEnd() const override { return mLength == mPosition; } + + // SkStreamRewindable overrides + bool rewind() override { return this->setPosition(0); } + + SkStreamAsset* onDuplicate() const override { + // SkStreamRewindable requires overriding this, but it is not called by + // decoders, so does not need a true implementation. A proper + // implementation would require duplicating the ByteBuffer, which has + // its own internal position state. + return nullptr; + } + + // SkStreamSeekable overrides + size_t getPosition() const override { return mPosition; } + + bool seek(size_t position) override { + return this->setPosition(position > mLength ? mLength : position); + } + + bool move(long offset) override { + long newPosition = mPosition + offset; + if (newPosition < 0) { + return this->setPosition(0); + } + return this->seek(static_cast<size_t>(newPosition)); + } + + SkStreamAsset* onFork() const override { + // SkStreamSeekable requires overriding this, but it is not called by + // decoders, so does not need a true implementation. A proper + // implementation would require duplicating the ByteBuffer, which has + // its own internal position state. + return nullptr; + } + + // SkStreamAsset overrides + size_t getLength() const override { return mLength; } + +private: + JavaVM* mJvm; + jobject mByteBuffer; + // Logical position of the SkStream, between 0 and mLength. + size_t mPosition; + // Initial position of mByteBuffer, treated as mPosition 0. + const size_t mInitialPosition; + // Logical length of the SkStream, from mInitialPosition to + // mByteBuffer.limit(). + const size_t mLength; + + // Range has already been checked by the caller. + bool setPosition(size_t newPosition) { + auto* env = requireEnv(mJvm); + env->CallObjectMethod(mByteBuffer, gByteBuffer_setPositionMethodID, + newPosition + mInitialPosition); + if (env->ExceptionCheck()) { + ALOGE("Internal error in ByteBufferStream::setPosition"); + env->ExceptionDescribe(); + env->ExceptionClear(); + mPosition = mLength; + return false; + } + mPosition = newPosition; + return true; + } + + // FIXME: This is an arbitrary storage size, which should be plenty for + // some formats (png, gif, many bmps). But for jpeg, the more we can supply + // in one call the better, and webp really wants all of the data. How to + // best choose the amount of storage used? + static constexpr size_t kStorageSize = 4096; + jbyteArray mStorage; +}; + +class ByteArrayStream : public SkStreamAsset { +private: + ByteArrayStream(JavaVM* jvm, jbyteArray jarray, size_t offset, size_t length) + : mJvm(jvm), mByteArray(jarray), mOffset(offset), mPosition(0), mLength(length) {} + +public: + static ByteArrayStream* Create(JavaVM* jvm, JNIEnv* env, jbyteArray jarray, size_t offset, + size_t length) { + // This object outlives its native method call. + jarray = static_cast<jbyteArray>(env->NewGlobalRef(jarray)); + if (!jarray) { + return nullptr; + } + return new ByteArrayStream(jvm, jarray, offset, length); + } + + ~ByteArrayStream() override { + auto* env = requireEnv(mJvm); + env->DeleteGlobalRef(mByteArray); + } + + size_t read(void* buffer, size_t size) override { + if (size > mLength - mPosition) { + size = mLength - mPosition; + } + if (!size) { + return 0; + } + + auto* env = requireEnv(mJvm); + if (buffer) { + env->GetByteArrayRegion(mByteArray, mPosition + mOffset, size, + reinterpret_cast<jbyte*>(buffer)); + if (env->ExceptionCheck()) { + ALOGE("Internal error in ByteArrayStream::read"); + env->ExceptionDescribe(); + env->ExceptionClear(); + mPosition = mLength; + return 0; + } + } + + mPosition += size; + return size; + } + + bool isAtEnd() const override { return mLength == mPosition; } + + // SkStreamRewindable overrides + bool rewind() override { + mPosition = 0; + return true; + } + SkStreamAsset* onDuplicate() const override { + // SkStreamRewindable requires overriding this, but it is not called by + // decoders, so does not need a true implementation. Note that a proper + // implementation is fairly straightforward + return nullptr; + } + + // SkStreamSeekable overrides + size_t getPosition() const override { return mPosition; } + + bool seek(size_t position) override { + mPosition = (position > mLength) ? mLength : position; + return true; + } + + bool move(long offset) override { + long newPosition = mPosition + offset; + if (newPosition < 0) { + return this->seek(0); + } + return this->seek(static_cast<size_t>(newPosition)); + } + + SkStreamAsset* onFork() const override { + // SkStreamSeekable requires overriding this, but it is not called by + // decoders, so does not need a true implementation. Note that a proper + // implementation is fairly straightforward + return nullptr; + } + + // SkStreamAsset overrides + size_t getLength() const override { return mLength; } + +private: + JavaVM* mJvm; + jbyteArray mByteArray; + // Offset in mByteArray. Only used when communicating with Java. + const size_t mOffset; + // Logical position of the SkStream, between 0 and mLength. + size_t mPosition; + const size_t mLength; +}; + +struct release_proc_context { + JavaVM* jvm; + jobject jbyteBuffer; +}; + +std::unique_ptr<SkStream> CreateByteBufferStreamAdaptor(JNIEnv* env, jobject jbyteBuffer, + size_t position, size_t limit) { + JavaVM* jvm; + LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&jvm) != JNI_OK); + + const size_t length = limit - position; + void* addr = env->GetDirectBufferAddress(jbyteBuffer); + if (addr) { + addr = reinterpret_cast<void*>(reinterpret_cast<char*>(addr) + position); + jbyteBuffer = env->NewGlobalRef(jbyteBuffer); + if (!jbyteBuffer) { + return nullptr; + } + + auto* context = new release_proc_context{jvm, jbyteBuffer}; + auto releaseProc = [](const void*, void* context) { + auto* c = reinterpret_cast<release_proc_context*>(context); + JNIEnv* env = get_env_or_die(c->jvm); + env->DeleteGlobalRef(c->jbyteBuffer); + delete c; + }; + auto data = SkData::MakeWithProc(addr, length, releaseProc, context); + // The new SkMemoryStream will read directly from addr. + return std::unique_ptr<SkStream>(new SkMemoryStream(std::move(data))); + } + + // Non-direct, or direct access is not supported. + return std::unique_ptr<SkStream>(ByteBufferStream::Create(jvm, env, jbyteBuffer, position, + length)); +} + +std::unique_ptr<SkStream> CreateByteArrayStreamAdaptor(JNIEnv* env, jbyteArray array, size_t offset, + size_t length) { + JavaVM* jvm; + LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&jvm) != JNI_OK); + + return std::unique_ptr<SkStream>(ByteArrayStream::Create(jvm, env, array, offset, length)); +} + +int register_android_graphics_ByteBufferStreamAdaptor(JNIEnv* env) { + jclass byteBuffer_class = FindClassOrDie(env, "java/nio/ByteBuffer"); + gByteBuffer_getMethodID = GetMethodIDOrDie(env, byteBuffer_class, "get", "([BII)Ljava/nio/ByteBuffer;"); + gByteBuffer_setPositionMethodID = GetMethodIDOrDie(env, byteBuffer_class, "position", "(I)Ljava/nio/Buffer;"); + return true; +} diff --git a/libs/hwui/jni/ByteBufferStreamAdaptor.h b/libs/hwui/jni/ByteBufferStreamAdaptor.h new file mode 100644 index 000000000000..367a48fad9b9 --- /dev/null +++ b/libs/hwui/jni/ByteBufferStreamAdaptor.h @@ -0,0 +1,37 @@ +#ifndef _ANDROID_GRAPHICS_BYTE_BUFFER_STREAM_ADAPTOR_H_ +#define _ANDROID_GRAPHICS_BYTE_BUFFER_STREAM_ADAPTOR_H_ + +#include <jni.h> +#include <memory> + +class SkStream; + +/** + * Create an adaptor for treating a java.nio.ByteBuffer as an SkStream. + * + * This will special case direct ByteBuffers, but not the case where a byte[] + * can be used directly. For that, use CreateByteArrayStreamAdaptor. + * + * @param jbyteBuffer corresponding to the java ByteBuffer. This method will + * add a global ref. + * @param initialPosition returned by ByteBuffer.position(). Decoding starts + * from here. + * @param limit returned by ByteBuffer.limit(). + * + * Returns null on failure. + */ +std::unique_ptr<SkStream> CreateByteBufferStreamAdaptor(JNIEnv*, jobject jbyteBuffer, + size_t initialPosition, size_t limit); + +/** + * Create an adaptor for treating a Java byte[] as an SkStream. + * + * @param offset into the byte[] of the beginning of the data to use. + * @param length of data to use, starting from offset. + * + * Returns null on failure. + */ +std::unique_ptr<SkStream> CreateByteArrayStreamAdaptor(JNIEnv*, jbyteArray array, size_t offset, + size_t length); + +#endif // _ANDROID_GRAPHICS_BYTE_BUFFER_STREAM_ADAPTOR_H_ diff --git a/libs/hwui/jni/Camera.cpp b/libs/hwui/jni/Camera.cpp new file mode 100644 index 000000000000..a5e1adf26861 --- /dev/null +++ b/libs/hwui/jni/Camera.cpp @@ -0,0 +1,143 @@ +#include "SkCamera.h" + +#include "GraphicsJNI.h" +#include <hwui/Canvas.h> + +static jfieldID gNativeInstanceFieldID; + +static void Camera_constructor(JNIEnv* env, jobject obj) { + Sk3DView* view = new Sk3DView; + env->SetLongField(obj, gNativeInstanceFieldID, reinterpret_cast<jlong>(view)); +} + +static void Camera_destructor(JNIEnv* env, jobject obj) { + jlong viewHandle = env->GetLongField(obj, gNativeInstanceFieldID); + Sk3DView* view = reinterpret_cast<Sk3DView*>(viewHandle); + delete view; +} + +static void Camera_save(JNIEnv* env, jobject obj) { + jlong viewHandle = env->GetLongField(obj, gNativeInstanceFieldID); + Sk3DView* v = reinterpret_cast<Sk3DView*>(viewHandle); + v->save(); +} + +static void Camera_restore(JNIEnv* env, jobject obj) { + jlong viewHandle = env->GetLongField(obj, gNativeInstanceFieldID); + Sk3DView* v = reinterpret_cast<Sk3DView*>(viewHandle); + v->restore(); +} + +static void Camera_translate(JNIEnv* env, jobject obj, + jfloat dx, jfloat dy, jfloat dz) { + jlong viewHandle = env->GetLongField(obj, gNativeInstanceFieldID); + Sk3DView* v = reinterpret_cast<Sk3DView*>(viewHandle); + v->translate(dx, dy, dz); +} + +static void Camera_rotateX(JNIEnv* env, jobject obj, jfloat degrees) { + jlong viewHandle = env->GetLongField(obj, gNativeInstanceFieldID); + Sk3DView* v = reinterpret_cast<Sk3DView*>(viewHandle); + v->rotateX(degrees); +} + +static void Camera_rotateY(JNIEnv* env, jobject obj, jfloat degrees) { + jlong viewHandle = env->GetLongField(obj, gNativeInstanceFieldID); + Sk3DView* v = reinterpret_cast<Sk3DView*>(viewHandle); + v->rotateY(degrees); +} + +static void Camera_rotateZ(JNIEnv* env, jobject obj, jfloat degrees) { + jlong viewHandle = env->GetLongField(obj, gNativeInstanceFieldID); + Sk3DView* v = reinterpret_cast<Sk3DView*>(viewHandle); + v->rotateZ(degrees); +} + +static void Camera_rotate(JNIEnv* env, jobject obj, jfloat x, jfloat y, jfloat z) { + jlong viewHandle = env->GetLongField(obj, gNativeInstanceFieldID); + Sk3DView* v = reinterpret_cast<Sk3DView*>(viewHandle); + v->rotateX(x); + v->rotateY(y); + v->rotateZ(z); +} + +static void Camera_setLocation(JNIEnv* env, jobject obj, jfloat x, jfloat y, jfloat z) { + jlong viewHandle = env->GetLongField(obj, gNativeInstanceFieldID); + Sk3DView* v = reinterpret_cast<Sk3DView*>(viewHandle); + v->setCameraLocation(x, y, z); +} + +static jfloat Camera_getLocationX(JNIEnv* env, jobject obj) { + jlong viewHandle = env->GetLongField(obj, gNativeInstanceFieldID); + Sk3DView* v = reinterpret_cast<Sk3DView*>(viewHandle); + return SkScalarToFloat(v->getCameraLocationX()); +} + +static jfloat Camera_getLocationY(JNIEnv* env, jobject obj) { + jlong viewHandle = env->GetLongField(obj, gNativeInstanceFieldID); + Sk3DView* v = reinterpret_cast<Sk3DView*>(viewHandle); + return SkScalarToFloat(v->getCameraLocationY()); +} + +static jfloat Camera_getLocationZ(JNIEnv* env, jobject obj) { + jlong viewHandle = env->GetLongField(obj, gNativeInstanceFieldID); + Sk3DView* v = reinterpret_cast<Sk3DView*>(viewHandle); + return SkScalarToFloat(v->getCameraLocationZ()); +} + +static void Camera_getMatrix(JNIEnv* env, jobject obj, jlong matrixHandle) { + SkMatrix* native_matrix = reinterpret_cast<SkMatrix*>(matrixHandle); + jlong viewHandle = env->GetLongField(obj, gNativeInstanceFieldID); + Sk3DView* v = reinterpret_cast<Sk3DView*>(viewHandle); + v->getMatrix(native_matrix); +} + +static void Camera_applyToCanvas(JNIEnv* env, jobject obj, jlong canvasHandle) { + android::Canvas* canvas = reinterpret_cast<android::Canvas*>(canvasHandle); + jlong viewHandle = env->GetLongField(obj, gNativeInstanceFieldID); + Sk3DView* v = reinterpret_cast<Sk3DView*>(viewHandle); + SkMatrix matrix; + v->getMatrix(&matrix); + canvas->concat(matrix); +} + +static jfloat Camera_dotWithNormal(JNIEnv* env, jobject obj, + jfloat x, jfloat y, jfloat z) { + jlong viewHandle = env->GetLongField(obj, gNativeInstanceFieldID); + Sk3DView* v = reinterpret_cast<Sk3DView*>(viewHandle); + SkScalar dot = v->dotWithNormal(x, y, z); + return SkScalarToFloat(dot); +} + +// ---------------------------------------------------------------------------- + +/* + * JNI registration. + */ +static const JNINativeMethod gCameraMethods[] = { + /* name, signature, funcPtr */ + + { "nativeConstructor", "()V", (void*)Camera_constructor }, + { "nativeDestructor", "()V", (void*)Camera_destructor }, + { "save", "()V", (void*)Camera_save }, + { "restore", "()V", (void*)Camera_restore }, + { "translate", "(FFF)V", (void*)Camera_translate }, + { "rotateX", "(F)V", (void*)Camera_rotateX }, + { "rotateY", "(F)V", (void*)Camera_rotateY }, + { "rotateZ", "(F)V", (void*)Camera_rotateZ }, + { "rotate", "(FFF)V", (void*)Camera_rotate }, + { "setLocation", "(FFF)V", (void*)Camera_setLocation }, + { "getLocationX", "()F", (void*)Camera_getLocationX }, + { "getLocationY", "()F", (void*)Camera_getLocationY }, + { "getLocationZ", "()F", (void*)Camera_getLocationZ }, + { "nativeGetMatrix", "(J)V", (void*)Camera_getMatrix }, + { "nativeApplyToCanvas", "(J)V", (void*)Camera_applyToCanvas }, + { "dotWithNormal", "(FFF)F", (void*)Camera_dotWithNormal } +}; + +int register_android_graphics_Camera(JNIEnv* env) { + jclass clazz = android::FindClassOrDie(env, "android/graphics/Camera"); + gNativeInstanceFieldID = android::GetFieldIDOrDie(env, clazz, "native_instance", "J"); + return android::RegisterMethodsOrDie(env, "android/graphics/Camera", gCameraMethods, + NELEM(gCameraMethods)); +} diff --git a/libs/hwui/jni/CanvasProperty.cpp b/libs/hwui/jni/CanvasProperty.cpp new file mode 100644 index 000000000000..684ee23b9fca --- /dev/null +++ b/libs/hwui/jni/CanvasProperty.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (C) 20014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "GraphicsJNI.h" + +#include <hwui/Paint.h> +#include <utils/RefBase.h> +#include <CanvasProperty.h> + +namespace android { + +using namespace uirenderer; + +static jlong createFloat(JNIEnv* env, jobject clazz, jfloat initialValue) { + return reinterpret_cast<jlong>(new CanvasPropertyPrimitive(initialValue)); +} + +static jlong createPaint(JNIEnv* env, jobject clazz, jlong paintPtr) { + const Paint* paint = reinterpret_cast<const Paint*>(paintPtr); + return reinterpret_cast<jlong>(new CanvasPropertyPaint(*paint)); +} + +// ---------------------------------------------------------------------------- +// JNI Glue +// ---------------------------------------------------------------------------- + +static const JNINativeMethod gMethods[] = { + { "nCreateFloat", "(F)J", (void*) createFloat }, + { "nCreatePaint", "(J)J", (void*) createPaint }, +}; + +int register_android_graphics_CanvasProperty(JNIEnv* env) { + return RegisterMethodsOrDie(env, "android/graphics/CanvasProperty", gMethods, + NELEM(gMethods)); +} + +}; // namespace android diff --git a/libs/hwui/jni/ColorFilter.cpp b/libs/hwui/jni/ColorFilter.cpp new file mode 100644 index 000000000000..cef21f91f3c1 --- /dev/null +++ b/libs/hwui/jni/ColorFilter.cpp @@ -0,0 +1,89 @@ +/* libs/android_runtime/android/graphics/ColorFilter.cpp +** +** Copyright 2006, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#include "GraphicsJNI.h" + +#include "SkColorFilter.h" +#include "SkColorMatrixFilter.h" + +namespace android { + +using namespace uirenderer; + +class SkColorFilterGlue { +public: + static void SafeUnref(SkColorFilter* filter) { + SkSafeUnref(filter); + } + + static jlong GetNativeFinalizer(JNIEnv*, jobject) { + return static_cast<jlong>(reinterpret_cast<uintptr_t>(&SafeUnref)); + } + + static jlong CreateBlendModeFilter(JNIEnv* env, jobject, jint srcColor, jint modeHandle) { + SkBlendMode mode = static_cast<SkBlendMode>(modeHandle); + return reinterpret_cast<jlong>(SkColorFilters::Blend(srcColor, mode).release()); + } + + static jlong CreateLightingFilter(JNIEnv* env, jobject, jint mul, jint add) { + return reinterpret_cast<jlong>(SkColorMatrixFilter::MakeLightingFilter(mul, add).release()); + } + + static jlong CreateColorMatrixFilter(JNIEnv* env, jobject, jfloatArray jarray) { + float matrix[20]; + env->GetFloatArrayRegion(jarray, 0, 20, matrix); + // java biases the translates by 255, so undo that before calling skia + matrix[ 4] *= (1.0f/255); + matrix[ 9] *= (1.0f/255); + matrix[14] *= (1.0f/255); + matrix[19] *= (1.0f/255); + return reinterpret_cast<jlong>(SkColorFilters::Matrix(matrix).release()); + } +}; + +static const JNINativeMethod colorfilter_methods[] = { + {"nativeGetFinalizer", "()J", (void*) SkColorFilterGlue::GetNativeFinalizer } +}; + +static const JNINativeMethod blendmode_methods[] = { + { "native_CreateBlendModeFilter", "(II)J", (void*) SkColorFilterGlue::CreateBlendModeFilter }, +}; + +static const JNINativeMethod lighting_methods[] = { + { "native_CreateLightingFilter", "(II)J", (void*) SkColorFilterGlue::CreateLightingFilter }, +}; + +static const JNINativeMethod colormatrix_methods[] = { + { "nativeColorMatrixFilter", "([F)J", (void*) SkColorFilterGlue::CreateColorMatrixFilter }, +}; + +int register_android_graphics_ColorFilter(JNIEnv* env) { + android::RegisterMethodsOrDie(env, "android/graphics/ColorFilter", colorfilter_methods, + NELEM(colorfilter_methods)); + android::RegisterMethodsOrDie(env, "android/graphics/PorterDuffColorFilter", blendmode_methods, + NELEM(blendmode_methods)); + android::RegisterMethodsOrDie(env, "android/graphics/BlendModeColorFilter", blendmode_methods, + NELEM(blendmode_methods)); + android::RegisterMethodsOrDie(env, "android/graphics/LightingColorFilter", lighting_methods, + NELEM(lighting_methods)); + android::RegisterMethodsOrDie(env, "android/graphics/ColorMatrixColorFilter", + colormatrix_methods, NELEM(colormatrix_methods)); + + return 0; +} + +} diff --git a/libs/hwui/jni/CreateJavaOutputStreamAdaptor.cpp b/libs/hwui/jni/CreateJavaOutputStreamAdaptor.cpp new file mode 100644 index 000000000000..39483b55992b --- /dev/null +++ b/libs/hwui/jni/CreateJavaOutputStreamAdaptor.cpp @@ -0,0 +1,306 @@ +#include "CreateJavaOutputStreamAdaptor.h" +#include "SkData.h" +#include "SkMalloc.h" +#include "SkRefCnt.h" +#include "SkStream.h" +#include "SkTypes.h" +#include "Utils.h" + +#include <nativehelper/JNIHelp.h> +#include <log/log.h> +#include <memory> + +static jmethodID gInputStream_readMethodID; +static jmethodID gInputStream_skipMethodID; + +/** + * Wrapper for a Java InputStream. + */ +class JavaInputStreamAdaptor : public SkStream { + JavaInputStreamAdaptor(JavaVM* jvm, jobject js, jbyteArray ar, jint capacity, + bool swallowExceptions) + : fJvm(jvm) + , fJavaInputStream(js) + , fJavaByteArray(ar) + , fCapacity(capacity) + , fBytesRead(0) + , fIsAtEnd(false) + , fSwallowExceptions(swallowExceptions) {} + +public: + static JavaInputStreamAdaptor* Create(JNIEnv* env, jobject js, jbyteArray ar, + bool swallowExceptions) { + JavaVM* jvm; + LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&jvm) != JNI_OK); + + js = env->NewGlobalRef(js); + if (!js) { + return nullptr; + } + + ar = (jbyteArray) env->NewGlobalRef(ar); + if (!ar) { + env->DeleteGlobalRef(js); + return nullptr; + } + + jint capacity = env->GetArrayLength(ar); + return new JavaInputStreamAdaptor(jvm, js, ar, capacity, swallowExceptions); + } + + ~JavaInputStreamAdaptor() override { + auto* env = android::get_env_or_die(fJvm); + env->DeleteGlobalRef(fJavaInputStream); + env->DeleteGlobalRef(fJavaByteArray); + } + + size_t read(void* buffer, size_t size) override { + auto* env = android::get_env_or_die(fJvm); + if (!fSwallowExceptions && checkException(env)) { + // Just in case the caller did not clear from a previous exception. + return 0; + } + if (NULL == buffer) { + if (0 == size) { + return 0; + } else { + /* InputStream.skip(n) can return <=0 but still not be at EOF + If we see that value, we need to call read(), which will + block if waiting for more data, or return -1 at EOF + */ + size_t amountSkipped = 0; + do { + size_t amount = this->doSkip(size - amountSkipped, env); + if (0 == amount) { + char tmp; + amount = this->doRead(&tmp, 1, env); + if (0 == amount) { + // if read returned 0, we're at EOF + fIsAtEnd = true; + break; + } + } + amountSkipped += amount; + } while (amountSkipped < size); + return amountSkipped; + } + } + return this->doRead(buffer, size, env); + } + + bool isAtEnd() const override { return fIsAtEnd; } + +private: + size_t doRead(void* buffer, size_t size, JNIEnv* env) { + size_t bytesRead = 0; + // read the bytes + do { + jint requested = 0; + if (size > static_cast<size_t>(fCapacity)) { + requested = fCapacity; + } else { + // This is safe because requested is clamped to (jint) + // fCapacity. + requested = static_cast<jint>(size); + } + + jint n = env->CallIntMethod(fJavaInputStream, + gInputStream_readMethodID, fJavaByteArray, 0, requested); + if (checkException(env)) { + SkDebugf("---- read threw an exception\n"); + return bytesRead; + } + + if (n < 0) { // n == 0 should not be possible, see InputStream read() specifications. + fIsAtEnd = true; + break; // eof + } + + env->GetByteArrayRegion(fJavaByteArray, 0, n, + reinterpret_cast<jbyte*>(buffer)); + if (checkException(env)) { + SkDebugf("---- read:GetByteArrayRegion threw an exception\n"); + return bytesRead; + } + + buffer = (void*)((char*)buffer + n); + bytesRead += n; + size -= n; + fBytesRead += n; + } while (size != 0); + + return bytesRead; + } + + size_t doSkip(size_t size, JNIEnv* env) { + jlong skipped = env->CallLongMethod(fJavaInputStream, + gInputStream_skipMethodID, (jlong)size); + if (checkException(env)) { + SkDebugf("------- skip threw an exception\n"); + return 0; + } + if (skipped < 0) { + skipped = 0; + } + + return (size_t)skipped; + } + + bool checkException(JNIEnv* env) { + if (!env->ExceptionCheck()) { + return false; + } + + env->ExceptionDescribe(); + if (fSwallowExceptions) { + env->ExceptionClear(); + } + + // There is no way to recover from the error, so consider the stream + // to be at the end. + fIsAtEnd = true; + + return true; + } + + JavaVM* fJvm; + jobject fJavaInputStream; + jbyteArray fJavaByteArray; + const jint fCapacity; + size_t fBytesRead; + bool fIsAtEnd; + const bool fSwallowExceptions; +}; + +SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream, jbyteArray storage, + bool swallowExceptions) { + return JavaInputStreamAdaptor::Create(env, stream, storage, swallowExceptions); +} + +static SkMemoryStream* adaptor_to_mem_stream(SkStream* stream) { + SkASSERT(stream != NULL); + size_t bufferSize = 4096; + size_t streamLen = 0; + size_t len; + char* data = (char*)sk_malloc_throw(bufferSize); + + while ((len = stream->read(data + streamLen, + bufferSize - streamLen)) != 0) { + streamLen += len; + if (streamLen == bufferSize) { + bufferSize *= 2; + data = (char*)sk_realloc_throw(data, bufferSize); + } + } + data = (char*)sk_realloc_throw(data, streamLen); + + SkMemoryStream* streamMem = new SkMemoryStream(); + streamMem->setMemoryOwned(data, streamLen); + return streamMem; +} + +SkStreamRewindable* CopyJavaInputStream(JNIEnv* env, jobject stream, + jbyteArray storage) { + std::unique_ptr<SkStream> adaptor(CreateJavaInputStreamAdaptor(env, stream, storage)); + if (NULL == adaptor.get()) { + return NULL; + } + return adaptor_to_mem_stream(adaptor.get()); +} + +/////////////////////////////////////////////////////////////////////////////// + +static jmethodID gOutputStream_writeMethodID; +static jmethodID gOutputStream_flushMethodID; + +class SkJavaOutputStream : public SkWStream { +public: + SkJavaOutputStream(JNIEnv* env, jobject stream, jbyteArray storage) + : fEnv(env), fJavaOutputStream(stream), fJavaByteArray(storage), fBytesWritten(0) { + fCapacity = env->GetArrayLength(storage); + } + + virtual size_t bytesWritten() const { + return fBytesWritten; + } + + virtual bool write(const void* buffer, size_t size) { + JNIEnv* env = fEnv; + jbyteArray storage = fJavaByteArray; + + while (size > 0) { + jint requested = 0; + if (size > static_cast<size_t>(fCapacity)) { + requested = fCapacity; + } else { + // This is safe because requested is clamped to (jint) + // fCapacity. + requested = static_cast<jint>(size); + } + + env->SetByteArrayRegion(storage, 0, requested, + reinterpret_cast<const jbyte*>(buffer)); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + SkDebugf("--- write:SetByteArrayElements threw an exception\n"); + return false; + } + + fEnv->CallVoidMethod(fJavaOutputStream, gOutputStream_writeMethodID, + storage, 0, requested); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + SkDebugf("------- write threw an exception\n"); + return false; + } + + buffer = (void*)((char*)buffer + requested); + size -= requested; + fBytesWritten += requested; + } + return true; + } + + virtual void flush() { + fEnv->CallVoidMethod(fJavaOutputStream, gOutputStream_flushMethodID); + } + +private: + JNIEnv* fEnv; + jobject fJavaOutputStream; // the caller owns this object + jbyteArray fJavaByteArray; // the caller owns this object + jint fCapacity; + size_t fBytesWritten; +}; + +SkWStream* CreateJavaOutputStreamAdaptor(JNIEnv* env, jobject stream, + jbyteArray storage) { + return new SkJavaOutputStream(env, stream, storage); +} + +static jclass findClassCheck(JNIEnv* env, const char classname[]) { + jclass clazz = env->FindClass(classname); + SkASSERT(!env->ExceptionCheck()); + return clazz; +} + +static jmethodID getMethodIDCheck(JNIEnv* env, jclass clazz, + const char methodname[], const char type[]) { + jmethodID id = env->GetMethodID(clazz, methodname, type); + SkASSERT(!env->ExceptionCheck()); + return id; +} + +int register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv* env) { + jclass inputStream_Clazz = findClassCheck(env, "java/io/InputStream"); + gInputStream_readMethodID = getMethodIDCheck(env, inputStream_Clazz, "read", "([BII)I"); + gInputStream_skipMethodID = getMethodIDCheck(env, inputStream_Clazz, "skip", "(J)J"); + + jclass outputStream_Clazz = findClassCheck(env, "java/io/OutputStream"); + gOutputStream_writeMethodID = getMethodIDCheck(env, outputStream_Clazz, "write", "([BII)V"); + gOutputStream_flushMethodID = getMethodIDCheck(env, outputStream_Clazz, "flush", "()V"); + + return 0; +} diff --git a/libs/hwui/jni/CreateJavaOutputStreamAdaptor.h b/libs/hwui/jni/CreateJavaOutputStreamAdaptor.h new file mode 100644 index 000000000000..849418da01a1 --- /dev/null +++ b/libs/hwui/jni/CreateJavaOutputStreamAdaptor.h @@ -0,0 +1,42 @@ +#ifndef _ANDROID_GRAPHICS_CREATE_JAVA_OUTPUT_STREAM_ADAPTOR_H_ +#define _ANDROID_GRAPHICS_CREATE_JAVA_OUTPUT_STREAM_ADAPTOR_H_ + +#include "jni.h" + +class SkMemoryStream; +class SkStream; +class SkStreamRewindable; +class SkWStream; + +/** + * Return an adaptor from a Java InputStream to an SkStream. + * Does not support rewind. + * @param env JNIEnv object. + * @param stream Pointer to Java InputStream. + * @param storage Java byte array for retrieving data from the + * Java InputStream. + * @param swallowExceptions Whether to call ExceptionClear() after + * an Exception is thrown. If false, it is up to the client to + * clear or propagate the exception. + * @return SkStream Simple subclass of SkStream which supports its + * basic methods like reading. Only valid until the calling + * function returns, since the Java InputStream is not managed + * by the SkStream. + */ +SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream, jbyteArray storage, + bool swallowExceptions = true); + +/** + * Copy a Java InputStream. The result will be rewindable. + * @param env JNIEnv object. + * @param stream Pointer to Java InputStream. + * @param storage Java byte array for retrieving data from the + * Java InputStream. + * @return SkStreamRewindable The data in stream will be copied + * to a new SkStreamRewindable. + */ +SkStreamRewindable* CopyJavaInputStream(JNIEnv* env, jobject stream, jbyteArray storage); + +SkWStream* CreateJavaOutputStreamAdaptor(JNIEnv* env, jobject stream, jbyteArray storage); + +#endif // _ANDROID_GRAPHICS_CREATE_JAVA_OUTPUT_STREAM_ADAPTOR_H_ diff --git a/libs/hwui/jni/FontFamily.cpp b/libs/hwui/jni/FontFamily.cpp new file mode 100644 index 000000000000..a2fef1e19328 --- /dev/null +++ b/libs/hwui/jni/FontFamily.cpp @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#undef LOG_TAG +#define LOG_TAG "Minikin" + +#include "SkData.h" +#include "SkFontMgr.h" +#include "SkRefCnt.h" +#include "SkTypeface.h" +#include "GraphicsJNI.h" +#include <nativehelper/ScopedPrimitiveArray.h> +#include <nativehelper/ScopedUtfChars.h> +#include "Utils.h" +#include "FontUtils.h" + +#include <hwui/MinikinSkia.h> +#include <hwui/Typeface.h> +#include <minikin/FontFamily.h> +#include <minikin/LocaleList.h> +#include <ui/FatVector.h> + +#include <memory> + +namespace android { + +struct NativeFamilyBuilder { + NativeFamilyBuilder(uint32_t langId, int variant) + : langId(langId), variant(static_cast<minikin::FamilyVariant>(variant)) {} + uint32_t langId; + minikin::FamilyVariant variant; + std::vector<minikin::Font> fonts; + std::vector<minikin::FontVariation> axes; +}; + +static inline NativeFamilyBuilder* toNativeBuilder(jlong ptr) { + return reinterpret_cast<NativeFamilyBuilder*>(ptr); +} + +static inline FontFamilyWrapper* toFamily(jlong ptr) { + return reinterpret_cast<FontFamilyWrapper*>(ptr); +} + +template<typename Ptr> static inline jlong toJLong(Ptr ptr) { + return reinterpret_cast<jlong>(ptr); +} + +static jlong FontFamily_initBuilder(JNIEnv* env, jobject clazz, jstring langs, jint variant) { + NativeFamilyBuilder* builder; + if (langs != nullptr) { + ScopedUtfChars str(env, langs); + builder = new NativeFamilyBuilder(minikin::registerLocaleList(str.c_str()), variant); + } else { + builder = new NativeFamilyBuilder(minikin::registerLocaleList(""), variant); + } + return toJLong(builder); +} + +static jlong FontFamily_create(CRITICAL_JNI_PARAMS_COMMA jlong builderPtr) { + if (builderPtr == 0) { + return 0; + } + NativeFamilyBuilder* builder = toNativeBuilder(builderPtr); + if (builder->fonts.empty()) { + return 0; + } + std::shared_ptr<minikin::FontFamily> family = std::make_shared<minikin::FontFamily>( + builder->langId, builder->variant, std::move(builder->fonts), + true /* isCustomFallback */); + if (family->getCoverage().length() == 0) { + return 0; + } + return toJLong(new FontFamilyWrapper(std::move(family))); +} + +static void releaseBuilder(jlong builderPtr) { + delete toNativeBuilder(builderPtr); +} + +static jlong FontFamily_getBuilderReleaseFunc(CRITICAL_JNI_PARAMS) { + return toJLong(&releaseBuilder); +} + +static void releaseFamily(jlong familyPtr) { + delete toFamily(familyPtr); +} + +static jlong FontFamily_getFamilyReleaseFunc(CRITICAL_JNI_PARAMS) { + return toJLong(&releaseFamily); +} + +static bool addSkTypeface(NativeFamilyBuilder* builder, sk_sp<SkData>&& data, int ttcIndex, + jint weight, jint italic) { + FatVector<SkFontArguments::Axis, 2> skiaAxes; + for (const auto& axis : builder->axes) { + skiaAxes.emplace_back(SkFontArguments::Axis{axis.axisTag, axis.value}); + } + + const size_t fontSize = data->size(); + const void* fontPtr = data->data(); + std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(std::move(data))); + + SkFontArguments params; + params.setCollectionIndex(ttcIndex); + params.setAxes(skiaAxes.data(), skiaAxes.size()); + + sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault()); + sk_sp<SkTypeface> face(fm->makeFromStream(std::move(fontData), params)); + if (face == NULL) { + ALOGE("addFont failed to create font, invalid request"); + builder->axes.clear(); + return false; + } + std::shared_ptr<minikin::MinikinFont> minikinFont = + std::make_shared<MinikinFontSkia>(std::move(face), fontPtr, fontSize, "", ttcIndex, + builder->axes); + minikin::Font::Builder fontBuilder(minikinFont); + + if (weight != RESOLVE_BY_FONT_TABLE) { + fontBuilder.setWeight(weight); + } + if (italic != RESOLVE_BY_FONT_TABLE) { + fontBuilder.setSlant(static_cast<minikin::FontStyle::Slant>(italic != 0)); + } + builder->fonts.push_back(fontBuilder.build()); + builder->axes.clear(); + return true; +} + +static void release_global_ref(const void* /*data*/, void* context) { + JNIEnv* env = GraphicsJNI::getJNIEnv(); + bool needToAttach = (env == NULL); + if (needToAttach) { + env = GraphicsJNI::attachJNIEnv("release_font_data"); + if (env == nullptr) { + ALOGE("failed to attach to thread to release global ref."); + return; + } + } + + jobject obj = reinterpret_cast<jobject>(context); + env->DeleteGlobalRef(obj); + + if (needToAttach) { + GraphicsJNI::detachJNIEnv(); + } +} + +static jboolean FontFamily_addFont(JNIEnv* env, jobject clazz, jlong builderPtr, jobject bytebuf, + jint ttcIndex, jint weight, jint isItalic) { + NPE_CHECK_RETURN_ZERO(env, bytebuf); + NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr); + const void* fontPtr = env->GetDirectBufferAddress(bytebuf); + if (fontPtr == NULL) { + ALOGE("addFont failed to create font, buffer invalid"); + builder->axes.clear(); + return false; + } + jlong fontSize = env->GetDirectBufferCapacity(bytebuf); + if (fontSize < 0) { + ALOGE("addFont failed to create font, buffer size invalid"); + builder->axes.clear(); + return false; + } + jobject fontRef = MakeGlobalRefOrDie(env, bytebuf); + sk_sp<SkData> data(SkData::MakeWithProc(fontPtr, fontSize, + release_global_ref, reinterpret_cast<void*>(fontRef))); + return addSkTypeface(builder, std::move(data), ttcIndex, weight, isItalic); +} + +static jboolean FontFamily_addFontWeightStyle(JNIEnv* env, jobject clazz, jlong builderPtr, + jobject font, jint ttcIndex, jint weight, jint isItalic) { + NPE_CHECK_RETURN_ZERO(env, font); + NativeFamilyBuilder* builder = toNativeBuilder(builderPtr); + const void* fontPtr = env->GetDirectBufferAddress(font); + if (fontPtr == NULL) { + ALOGE("addFont failed to create font, buffer invalid"); + builder->axes.clear(); + return false; + } + jlong fontSize = env->GetDirectBufferCapacity(font); + if (fontSize < 0) { + ALOGE("addFont failed to create font, buffer size invalid"); + builder->axes.clear(); + return false; + } + jobject fontRef = MakeGlobalRefOrDie(env, font); + sk_sp<SkData> data(SkData::MakeWithProc(fontPtr, fontSize, + release_global_ref, reinterpret_cast<void*>(fontRef))); + return addSkTypeface(builder, std::move(data), ttcIndex, weight, isItalic); +} + +static void FontFamily_addAxisValue(CRITICAL_JNI_PARAMS_COMMA jlong builderPtr, jint tag, jfloat value) { + NativeFamilyBuilder* builder = toNativeBuilder(builderPtr); + builder->axes.push_back({static_cast<minikin::AxisTag>(tag), value}); +} + +/////////////////////////////////////////////////////////////////////////////// + +static const JNINativeMethod gFontFamilyMethods[] = { + { "nInitBuilder", "(Ljava/lang/String;I)J", (void*)FontFamily_initBuilder }, + { "nCreateFamily", "(J)J", (void*)FontFamily_create }, + { "nGetBuilderReleaseFunc", "()J", (void*)FontFamily_getBuilderReleaseFunc }, + { "nGetFamilyReleaseFunc", "()J", (void*)FontFamily_getFamilyReleaseFunc }, + { "nAddFont", "(JLjava/nio/ByteBuffer;III)Z", (void*)FontFamily_addFont }, + { "nAddFontWeightStyle", "(JLjava/nio/ByteBuffer;III)Z", + (void*)FontFamily_addFontWeightStyle }, + { "nAddAxisValue", "(JIF)V", (void*)FontFamily_addAxisValue }, +}; + +int register_android_graphics_FontFamily(JNIEnv* env) +{ + int err = RegisterMethodsOrDie(env, "android/graphics/FontFamily", gFontFamilyMethods, + NELEM(gFontFamilyMethods)); + + init_FontUtils(env); + return err; +} + +} diff --git a/libs/hwui/jni/FontUtils.cpp b/libs/hwui/jni/FontUtils.cpp new file mode 100644 index 000000000000..654c5fdf6528 --- /dev/null +++ b/libs/hwui/jni/FontUtils.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "FontUtils.h" + +#include "graphics_jni_helpers.h" + +namespace android { +namespace { + +static struct { + jmethodID mGet; + jmethodID mSize; +} gListClassInfo; + +static struct { + jfieldID mTag; + jfieldID mStyleValue; +} gAxisClassInfo; + +} // namespace + +jint ListHelper::size() const { + return mEnv->CallIntMethod(mList, gListClassInfo.mSize); +} + +jobject ListHelper::get(jint index) const { + return mEnv->CallObjectMethod(mList, gListClassInfo.mGet, index); +} + +jint AxisHelper::getTag() const { + return mEnv->GetIntField(mAxis, gAxisClassInfo.mTag); +} + +jfloat AxisHelper::getStyleValue() const { + return mEnv->GetFloatField(mAxis, gAxisClassInfo.mStyleValue); +} + +void init_FontUtils(JNIEnv* env) { + jclass listClass = FindClassOrDie(env, "java/util/List"); + gListClassInfo.mGet = GetMethodIDOrDie(env, listClass, "get", "(I)Ljava/lang/Object;"); + gListClassInfo.mSize = GetMethodIDOrDie(env, listClass, "size", "()I"); + + jclass axisClass = FindClassOrDie(env, "android/graphics/fonts/FontVariationAxis"); + gAxisClassInfo.mTag = GetFieldIDOrDie(env, axisClass, "mTag", "I"); + gAxisClassInfo.mStyleValue = GetFieldIDOrDie(env, axisClass, "mStyleValue", "F"); +} + +} // namespace android diff --git a/libs/hwui/jni/FontUtils.h b/libs/hwui/jni/FontUtils.h new file mode 100644 index 000000000000..b36b4e60e33a --- /dev/null +++ b/libs/hwui/jni/FontUtils.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _ANDROID_GRAPHICS_FONT_UTILS_H_ +#define _ANDROID_GRAPHICS_FONT_UTILS_H_ + +#include <jni.h> +#include <memory> + +#include <minikin/Font.h> + +namespace minikin { +class FontFamily; +} // namespace minikin + +namespace android { + +struct FontFamilyWrapper { + explicit FontFamilyWrapper(std::shared_ptr<minikin::FontFamily>&& family) : family(family) {} + std::shared_ptr<minikin::FontFamily> family; +}; + +struct FontWrapper { + FontWrapper(minikin::Font&& font) : font(std::move(font)) {} + minikin::Font font; +}; + +// Utility wrapper for java.util.List +class ListHelper { +public: + ListHelper(JNIEnv* env, jobject list) : mEnv(env), mList(list) {} + + jint size() const; + jobject get(jint index) const; + +private: + JNIEnv* mEnv; + jobject mList; +}; + +// Utility wrapper for android.graphics.FontConfig$Axis +class AxisHelper { +public: + AxisHelper(JNIEnv* env, jobject axis) : mEnv(env), mAxis(axis) {} + + jint getTag() const; + jfloat getStyleValue() const; + +private: + JNIEnv* mEnv; + jobject mAxis; +}; + +void init_FontUtils(JNIEnv* env); + +}; // namespace android + +#endif // _ANDROID_GRAPHICS_FONT_UTILS_H_ diff --git a/libs/hwui/jni/GIFMovie.cpp b/libs/hwui/jni/GIFMovie.cpp new file mode 100644 index 000000000000..f84a4bd09073 --- /dev/null +++ b/libs/hwui/jni/GIFMovie.cpp @@ -0,0 +1,447 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#include "Movie.h" +#include "SkColor.h" +#include "SkColorPriv.h" +#include "SkStream.h" +#include "SkTemplates.h" +#include "SkUtils.h" + +#include "gif_lib.h" + +#if GIFLIB_MAJOR < 5 || (GIFLIB_MAJOR == 5 && GIFLIB_MINOR == 0) +#define DGifCloseFile(a, b) DGifCloseFile(a) +#endif + +class GIFMovie : public Movie { +public: + explicit GIFMovie(SkStream* stream); + virtual ~GIFMovie(); + +protected: + virtual bool onGetInfo(Info*); + virtual bool onSetTime(SkMSec); + virtual bool onGetBitmap(SkBitmap*); + +private: + GifFileType* fGIF; + int fCurrIndex; + int fLastDrawIndex; + SkBitmap fBackup; + SkColor fPaintingColor; +}; + +static int Decode(GifFileType* fileType, GifByteType* out, int size) { + SkStream* stream = (SkStream*) fileType->UserData; + return (int) stream->read(out, size); +} + +GIFMovie::GIFMovie(SkStream* stream) +{ +#if GIFLIB_MAJOR < 5 + fGIF = DGifOpen( stream, Decode ); +#else + fGIF = DGifOpen( stream, Decode, nullptr ); +#endif + if (nullptr == fGIF) + return; + + if (DGifSlurp(fGIF) != GIF_OK) + { + DGifCloseFile(fGIF, nullptr); + fGIF = nullptr; + } + fCurrIndex = -1; + fLastDrawIndex = -1; + fPaintingColor = SkPackARGB32(0, 0, 0, 0); +} + +GIFMovie::~GIFMovie() +{ + if (fGIF) + DGifCloseFile(fGIF, nullptr); +} + +static SkMSec savedimage_duration(const SavedImage* image) +{ + for (int j = 0; j < image->ExtensionBlockCount; j++) + { + if (image->ExtensionBlocks[j].Function == GRAPHICS_EXT_FUNC_CODE) + { + SkASSERT(image->ExtensionBlocks[j].ByteCount >= 4); + const uint8_t* b = (const uint8_t*)image->ExtensionBlocks[j].Bytes; + return ((b[2] << 8) | b[1]) * 10; + } + } + return 0; +} + +bool GIFMovie::onGetInfo(Info* info) +{ + if (nullptr == fGIF) + return false; + + SkMSec dur = 0; + for (int i = 0; i < fGIF->ImageCount; i++) + dur += savedimage_duration(&fGIF->SavedImages[i]); + + info->fDuration = dur; + info->fWidth = fGIF->SWidth; + info->fHeight = fGIF->SHeight; + info->fIsOpaque = false; // how to compute? + return true; +} + +bool GIFMovie::onSetTime(SkMSec time) +{ + if (nullptr == fGIF) + return false; + + SkMSec dur = 0; + for (int i = 0; i < fGIF->ImageCount; i++) + { + dur += savedimage_duration(&fGIF->SavedImages[i]); + if (dur >= time) + { + fCurrIndex = i; + return fLastDrawIndex != fCurrIndex; + } + } + fCurrIndex = fGIF->ImageCount - 1; + return true; +} + +static void copyLine(uint32_t* dst, const unsigned char* src, const ColorMapObject* cmap, + int transparent, int width) +{ + for (; width > 0; width--, src++, dst++) { + if (*src != transparent && *src < cmap->ColorCount) { + const GifColorType& col = cmap->Colors[*src]; + *dst = SkPackARGB32(0xFF, col.Red, col.Green, col.Blue); + } + } +} + +#if GIFLIB_MAJOR < 5 +static void copyInterlaceGroup(SkBitmap* bm, const unsigned char*& src, + const ColorMapObject* cmap, int transparent, int copyWidth, + int copyHeight, const GifImageDesc& imageDesc, int rowStep, + int startRow) +{ + int row; + // every 'rowStep'th row, starting with row 'startRow' + for (row = startRow; row < copyHeight; row += rowStep) { + uint32_t* dst = bm->getAddr32(imageDesc.Left, imageDesc.Top + row); + copyLine(dst, src, cmap, transparent, copyWidth); + src += imageDesc.Width; + } + + // pad for rest height + src += imageDesc.Width * ((imageDesc.Height - row + rowStep - 1) / rowStep); +} + +static void blitInterlace(SkBitmap* bm, const SavedImage* frame, const ColorMapObject* cmap, + int transparent) +{ + int width = bm->width(); + int height = bm->height(); + GifWord copyWidth = frame->ImageDesc.Width; + if (frame->ImageDesc.Left + copyWidth > width) { + copyWidth = width - frame->ImageDesc.Left; + } + + GifWord copyHeight = frame->ImageDesc.Height; + if (frame->ImageDesc.Top + copyHeight > height) { + copyHeight = height - frame->ImageDesc.Top; + } + + // deinterlace + const unsigned char* src = (unsigned char*)frame->RasterBits; + + // group 1 - every 8th row, starting with row 0 + copyInterlaceGroup(bm, src, cmap, transparent, copyWidth, copyHeight, frame->ImageDesc, 8, 0); + + // group 2 - every 8th row, starting with row 4 + copyInterlaceGroup(bm, src, cmap, transparent, copyWidth, copyHeight, frame->ImageDesc, 8, 4); + + // group 3 - every 4th row, starting with row 2 + copyInterlaceGroup(bm, src, cmap, transparent, copyWidth, copyHeight, frame->ImageDesc, 4, 2); + + copyInterlaceGroup(bm, src, cmap, transparent, copyWidth, copyHeight, frame->ImageDesc, 2, 1); +} +#endif + +static void blitNormal(SkBitmap* bm, const SavedImage* frame, const ColorMapObject* cmap, + int transparent) +{ + int width = bm->width(); + int height = bm->height(); + const unsigned char* src = (unsigned char*)frame->RasterBits; + uint32_t* dst = bm->getAddr32(frame->ImageDesc.Left, frame->ImageDesc.Top); + GifWord copyWidth = frame->ImageDesc.Width; + if (frame->ImageDesc.Left + copyWidth > width) { + copyWidth = width - frame->ImageDesc.Left; + } + + GifWord copyHeight = frame->ImageDesc.Height; + if (frame->ImageDesc.Top + copyHeight > height) { + copyHeight = height - frame->ImageDesc.Top; + } + + for (; copyHeight > 0; copyHeight--) { + copyLine(dst, src, cmap, transparent, copyWidth); + src += frame->ImageDesc.Width; + dst += width; + } +} + +static void fillRect(SkBitmap* bm, GifWord left, GifWord top, GifWord width, GifWord height, + uint32_t col) +{ + int bmWidth = bm->width(); + int bmHeight = bm->height(); + uint32_t* dst = bm->getAddr32(left, top); + GifWord copyWidth = width; + if (left + copyWidth > bmWidth) { + copyWidth = bmWidth - left; + } + + GifWord copyHeight = height; + if (top + copyHeight > bmHeight) { + copyHeight = bmHeight - top; + } + + for (; copyHeight > 0; copyHeight--) { + sk_memset32(dst, col, copyWidth); + dst += bmWidth; + } +} + +static void drawFrame(SkBitmap* bm, const SavedImage* frame, const ColorMapObject* cmap) +{ + int transparent = -1; + + for (int i = 0; i < frame->ExtensionBlockCount; ++i) { + ExtensionBlock* eb = frame->ExtensionBlocks + i; + if (eb->Function == GRAPHICS_EXT_FUNC_CODE && + eb->ByteCount == 4) { + bool has_transparency = ((eb->Bytes[0] & 1) == 1); + if (has_transparency) { + transparent = (unsigned char)eb->Bytes[3]; + } + } + } + + if (frame->ImageDesc.ColorMap != nullptr) { + // use local color table + cmap = frame->ImageDesc.ColorMap; + } + + if (cmap == nullptr || cmap->ColorCount != (1 << cmap->BitsPerPixel)) { + SkDEBUGFAIL("bad colortable setup"); + return; + } + +#if GIFLIB_MAJOR < 5 + // before GIFLIB 5, de-interlacing wasn't done by library at load time + if (frame->ImageDesc.Interlace) { + blitInterlace(bm, frame, cmap, transparent); + return; + } +#endif + + blitNormal(bm, frame, cmap, transparent); +} + +static bool checkIfWillBeCleared(const SavedImage* frame) +{ + for (int i = 0; i < frame->ExtensionBlockCount; ++i) { + ExtensionBlock* eb = frame->ExtensionBlocks + i; + if (eb->Function == GRAPHICS_EXT_FUNC_CODE && + eb->ByteCount == 4) { + // check disposal method + int disposal = ((eb->Bytes[0] >> 2) & 7); + if (disposal == 2 || disposal == 3) { + return true; + } + } + } + return false; +} + +static void getTransparencyAndDisposalMethod(const SavedImage* frame, bool* trans, int* disposal) +{ + *trans = false; + *disposal = 0; + for (int i = 0; i < frame->ExtensionBlockCount; ++i) { + ExtensionBlock* eb = frame->ExtensionBlocks + i; + if (eb->Function == GRAPHICS_EXT_FUNC_CODE && + eb->ByteCount == 4) { + *trans = ((eb->Bytes[0] & 1) == 1); + *disposal = ((eb->Bytes[0] >> 2) & 7); + } + } +} + +// return true if area of 'target' is completely covers area of 'covered' +static bool checkIfCover(const SavedImage* target, const SavedImage* covered) +{ + if (target->ImageDesc.Left <= covered->ImageDesc.Left + && covered->ImageDesc.Left + covered->ImageDesc.Width <= + target->ImageDesc.Left + target->ImageDesc.Width + && target->ImageDesc.Top <= covered->ImageDesc.Top + && covered->ImageDesc.Top + covered->ImageDesc.Height <= + target->ImageDesc.Top + target->ImageDesc.Height) { + return true; + } + return false; +} + +static void disposeFrameIfNeeded(SkBitmap* bm, const SavedImage* cur, const SavedImage* next, + SkBitmap* backup, SkColor color) +{ + // We can skip disposal process if next frame is not transparent + // and completely covers current area + bool curTrans; + int curDisposal; + getTransparencyAndDisposalMethod(cur, &curTrans, &curDisposal); + bool nextTrans; + int nextDisposal; + getTransparencyAndDisposalMethod(next, &nextTrans, &nextDisposal); + if ((curDisposal == 2 || curDisposal == 3) + && (nextTrans || !checkIfCover(next, cur))) { + switch (curDisposal) { + // restore to background color + // -> 'background' means background under this image. + case 2: + fillRect(bm, cur->ImageDesc.Left, cur->ImageDesc.Top, + cur->ImageDesc.Width, cur->ImageDesc.Height, + color); + break; + + // restore to previous + case 3: + bm->swap(*backup); + break; + } + } + + // Save current image if next frame's disposal method == 3 + if (nextDisposal == 3) { + const uint32_t* src = bm->getAddr32(0, 0); + uint32_t* dst = backup->getAddr32(0, 0); + int cnt = bm->width() * bm->height(); + memcpy(dst, src, cnt*sizeof(uint32_t)); + } +} + +bool GIFMovie::onGetBitmap(SkBitmap* bm) +{ + const GifFileType* gif = fGIF; + if (nullptr == gif) + return false; + + if (gif->ImageCount < 1) { + return false; + } + + const int width = gif->SWidth; + const int height = gif->SHeight; + if (width <= 0 || height <= 0) { + return false; + } + + // no need to draw + if (fLastDrawIndex >= 0 && fLastDrawIndex == fCurrIndex) { + return true; + } + + int startIndex = fLastDrawIndex + 1; + if (fLastDrawIndex < 0 || !bm->readyToDraw()) { + // first time + + startIndex = 0; + + // create bitmap + if (!bm->tryAllocN32Pixels(width, height)) { + return false; + } + // create bitmap for backup + if (!fBackup.tryAllocN32Pixels(width, height)) { + return false; + } + } else if (startIndex > fCurrIndex) { + // rewind to 1st frame for repeat + startIndex = 0; + } + + int lastIndex = fCurrIndex; + if (lastIndex < 0) { + // first time + lastIndex = 0; + } else if (lastIndex > fGIF->ImageCount - 1) { + // this block must not be reached. + lastIndex = fGIF->ImageCount - 1; + } + + SkColor bgColor = SkPackARGB32(0, 0, 0, 0); + if (gif->SColorMap != nullptr && gif->SBackGroundColor < gif->SColorMap->ColorCount) { + const GifColorType& col = gif->SColorMap->Colors[gif->SBackGroundColor]; + bgColor = SkColorSetARGB(0xFF, col.Red, col.Green, col.Blue); + } + + // draw each frames - not intelligent way + for (int i = startIndex; i <= lastIndex; i++) { + const SavedImage* cur = &fGIF->SavedImages[i]; + if (i == 0) { + bool trans; + int disposal; + getTransparencyAndDisposalMethod(cur, &trans, &disposal); + if (!trans && gif->SColorMap != nullptr) { + fPaintingColor = bgColor; + } else { + fPaintingColor = SkColorSetARGB(0, 0, 0, 0); + } + + bm->eraseColor(fPaintingColor); + fBackup.eraseColor(fPaintingColor); + } else { + // Dispose previous frame before move to next frame. + const SavedImage* prev = &fGIF->SavedImages[i-1]; + disposeFrameIfNeeded(bm, prev, cur, &fBackup, fPaintingColor); + } + + // Draw frame + // We can skip this process if this index is not last and disposal + // method == 2 or method == 3 + if (i == lastIndex || !checkIfWillBeCleared(cur)) { + drawFrame(bm, cur, gif->SColorMap); + } + } + + // save index + fLastDrawIndex = lastIndex; + return true; +} + +/////////////////////////////////////////////////////////////////////////////// + +Movie* Movie::DecodeStream(SkStreamRewindable* stream) { + char buf[GIF_STAMP_LEN]; + if (stream->read(buf, GIF_STAMP_LEN) == GIF_STAMP_LEN) { + if (memcmp(GIF_STAMP, buf, GIF_STAMP_LEN) == 0 || + memcmp(GIF87_STAMP, buf, GIF_STAMP_LEN) == 0 || + memcmp(GIF89_STAMP, buf, GIF_STAMP_LEN) == 0) { + // must rewind here, since our construct wants to re-read the data + stream->rewind(); + return new GIFMovie(stream); + } + } + return nullptr; +} diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp new file mode 100644 index 000000000000..f76ecb4c9c8a --- /dev/null +++ b/libs/hwui/jni/Graphics.cpp @@ -0,0 +1,768 @@ +#undef LOG_TAG +#define LOG_TAG "GraphicsJNI" + +#include <assert.h> +#include <unistd.h> + +#include "jni.h" +#include <nativehelper/JNIHelp.h> +#include "GraphicsJNI.h" + +#include "SkCanvas.h" +#include "SkMath.h" +#include "SkRegion.h" +#include <cutils/ashmem.h> +#include <hwui/Canvas.h> + +using namespace android; + +/*static*/ JavaVM* GraphicsJNI::mJavaVM = nullptr; + +void GraphicsJNI::setJavaVM(JavaVM* javaVM) { + mJavaVM = javaVM; +} + +/** return a pointer to the JNIEnv for this thread */ +JNIEnv* GraphicsJNI::getJNIEnv() { + assert(mJavaVM != nullptr); + JNIEnv* env; + if (mJavaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { + return nullptr; + } + return env; +} + +/** create a JNIEnv* for this thread or assert if one already exists */ +JNIEnv* GraphicsJNI::attachJNIEnv(const char* envName) { + assert(getJNIEnv() == nullptr); + JNIEnv* env = nullptr; + JavaVMAttachArgs args = { JNI_VERSION_1_4, envName, NULL }; + int result = mJavaVM->AttachCurrentThread(&env, (void*) &args); + if (result != JNI_OK) { + ALOGE("thread attach failed: %#x", result); + } + return env; +} + +/** detach the current thread from the JavaVM */ +void GraphicsJNI::detachJNIEnv() { + assert(mJavaVM != nullptr); + mJavaVM->DetachCurrentThread(); +} + +void doThrowNPE(JNIEnv* env) { + jniThrowNullPointerException(env, NULL); +} + +void doThrowAIOOBE(JNIEnv* env) { + jniThrowException(env, "java/lang/ArrayIndexOutOfBoundsException", NULL); +} + +void doThrowRE(JNIEnv* env, const char* msg) { + jniThrowRuntimeException(env, msg); +} + +void doThrowIAE(JNIEnv* env, const char* msg) { + jniThrowException(env, "java/lang/IllegalArgumentException", msg); +} + +void doThrowISE(JNIEnv* env, const char* msg) { + jniThrowException(env, "java/lang/IllegalStateException", msg); +} + +void doThrowOOME(JNIEnv* env, const char* msg) { + jniThrowException(env, "java/lang/OutOfMemoryError", msg); +} + +void doThrowIOE(JNIEnv* env, const char* msg) { + jniThrowException(env, "java/io/IOException", msg); +} + +bool GraphicsJNI::hasException(JNIEnv *env) { + if (env->ExceptionCheck() != 0) { + ALOGE("*** Uncaught exception returned from Java call!\n"); + env->ExceptionDescribe(); + return true; + } + return false; +} + +/////////////////////////////////////////////////////////////////////////////// + +AutoJavaFloatArray::AutoJavaFloatArray(JNIEnv* env, jfloatArray array, + int minLength, JNIAccess access) +: fEnv(env), fArray(array), fPtr(NULL), fLen(0) { + ALOG_ASSERT(env); + if (array) { + fLen = env->GetArrayLength(array); + if (fLen < minLength) { + LOG_ALWAYS_FATAL("bad length"); + } + fPtr = env->GetFloatArrayElements(array, NULL); + } + fReleaseMode = (access == kRO_JNIAccess) ? JNI_ABORT : 0; +} + +AutoJavaFloatArray::~AutoJavaFloatArray() { + if (fPtr) { + fEnv->ReleaseFloatArrayElements(fArray, fPtr, fReleaseMode); + } +} + +AutoJavaIntArray::AutoJavaIntArray(JNIEnv* env, jintArray array, + int minLength) +: fEnv(env), fArray(array), fPtr(NULL), fLen(0) { + ALOG_ASSERT(env); + if (array) { + fLen = env->GetArrayLength(array); + if (fLen < minLength) { + LOG_ALWAYS_FATAL("bad length"); + } + fPtr = env->GetIntArrayElements(array, NULL); + } +} + +AutoJavaIntArray::~AutoJavaIntArray() { + if (fPtr) { + fEnv->ReleaseIntArrayElements(fArray, fPtr, 0); + } +} + +AutoJavaShortArray::AutoJavaShortArray(JNIEnv* env, jshortArray array, + int minLength, JNIAccess access) +: fEnv(env), fArray(array), fPtr(NULL), fLen(0) { + ALOG_ASSERT(env); + if (array) { + fLen = env->GetArrayLength(array); + if (fLen < minLength) { + LOG_ALWAYS_FATAL("bad length"); + } + fPtr = env->GetShortArrayElements(array, NULL); + } + fReleaseMode = (access == kRO_JNIAccess) ? JNI_ABORT : 0; +} + +AutoJavaShortArray::~AutoJavaShortArray() { + if (fPtr) { + fEnv->ReleaseShortArrayElements(fArray, fPtr, fReleaseMode); + } +} + +AutoJavaByteArray::AutoJavaByteArray(JNIEnv* env, jbyteArray array, + int minLength) +: fEnv(env), fArray(array), fPtr(NULL), fLen(0) { + ALOG_ASSERT(env); + if (array) { + fLen = env->GetArrayLength(array); + if (fLen < minLength) { + LOG_ALWAYS_FATAL("bad length"); + } + fPtr = env->GetByteArrayElements(array, NULL); + } +} + +AutoJavaByteArray::~AutoJavaByteArray() { + if (fPtr) { + fEnv->ReleaseByteArrayElements(fArray, fPtr, 0); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +static jclass gRect_class; +static jfieldID gRect_leftFieldID; +static jfieldID gRect_topFieldID; +static jfieldID gRect_rightFieldID; +static jfieldID gRect_bottomFieldID; + +static jclass gRectF_class; +static jfieldID gRectF_leftFieldID; +static jfieldID gRectF_topFieldID; +static jfieldID gRectF_rightFieldID; +static jfieldID gRectF_bottomFieldID; + +static jclass gPoint_class; +static jfieldID gPoint_xFieldID; +static jfieldID gPoint_yFieldID; + +static jclass gPointF_class; +static jfieldID gPointF_xFieldID; +static jfieldID gPointF_yFieldID; + +static jclass gBitmapConfig_class; +static jfieldID gBitmapConfig_nativeInstanceID; +static jmethodID gBitmapConfig_nativeToConfigMethodID; + +static jclass gBitmapRegionDecoder_class; +static jmethodID gBitmapRegionDecoder_constructorMethodID; + +static jclass gCanvas_class; +static jfieldID gCanvas_nativeInstanceID; + +static jclass gPicture_class; +static jfieldID gPicture_nativeInstanceID; + +static jclass gRegion_class; +static jfieldID gRegion_nativeInstanceID; +static jmethodID gRegion_constructorMethodID; + +static jclass gByte_class; +static jobject gVMRuntime; +static jclass gVMRuntime_class; +static jmethodID gVMRuntime_newNonMovableArray; +static jmethodID gVMRuntime_addressOf; + +static jclass gColorSpace_class; +static jmethodID gColorSpace_getMethodID; +static jmethodID gColorSpace_matchMethodID; + +static jclass gColorSpaceRGB_class; +static jmethodID gColorSpaceRGB_constructorMethodID; + +static jclass gColorSpace_Named_class; +static jfieldID gColorSpace_Named_sRGBFieldID; +static jfieldID gColorSpace_Named_ExtendedSRGBFieldID; +static jfieldID gColorSpace_Named_LinearSRGBFieldID; +static jfieldID gColorSpace_Named_LinearExtendedSRGBFieldID; + +static jclass gTransferParameters_class; +static jmethodID gTransferParameters_constructorMethodID; + +/////////////////////////////////////////////////////////////////////////////// + +void GraphicsJNI::get_jrect(JNIEnv* env, jobject obj, int* L, int* T, int* R, int* B) +{ + ALOG_ASSERT(env->IsInstanceOf(obj, gRect_class)); + + *L = env->GetIntField(obj, gRect_leftFieldID); + *T = env->GetIntField(obj, gRect_topFieldID); + *R = env->GetIntField(obj, gRect_rightFieldID); + *B = env->GetIntField(obj, gRect_bottomFieldID); +} + +void GraphicsJNI::set_jrect(JNIEnv* env, jobject obj, int L, int T, int R, int B) +{ + ALOG_ASSERT(env->IsInstanceOf(obj, gRect_class)); + + env->SetIntField(obj, gRect_leftFieldID, L); + env->SetIntField(obj, gRect_topFieldID, T); + env->SetIntField(obj, gRect_rightFieldID, R); + env->SetIntField(obj, gRect_bottomFieldID, B); +} + +SkIRect* GraphicsJNI::jrect_to_irect(JNIEnv* env, jobject obj, SkIRect* ir) +{ + ALOG_ASSERT(env->IsInstanceOf(obj, gRect_class)); + + ir->setLTRB(env->GetIntField(obj, gRect_leftFieldID), + env->GetIntField(obj, gRect_topFieldID), + env->GetIntField(obj, gRect_rightFieldID), + env->GetIntField(obj, gRect_bottomFieldID)); + return ir; +} + +void GraphicsJNI::irect_to_jrect(const SkIRect& ir, JNIEnv* env, jobject obj) +{ + ALOG_ASSERT(env->IsInstanceOf(obj, gRect_class)); + + env->SetIntField(obj, gRect_leftFieldID, ir.fLeft); + env->SetIntField(obj, gRect_topFieldID, ir.fTop); + env->SetIntField(obj, gRect_rightFieldID, ir.fRight); + env->SetIntField(obj, gRect_bottomFieldID, ir.fBottom); +} + +SkRect* GraphicsJNI::jrectf_to_rect(JNIEnv* env, jobject obj, SkRect* r) +{ + ALOG_ASSERT(env->IsInstanceOf(obj, gRectF_class)); + + r->setLTRB(env->GetFloatField(obj, gRectF_leftFieldID), + env->GetFloatField(obj, gRectF_topFieldID), + env->GetFloatField(obj, gRectF_rightFieldID), + env->GetFloatField(obj, gRectF_bottomFieldID)); + return r; +} + +SkRect* GraphicsJNI::jrect_to_rect(JNIEnv* env, jobject obj, SkRect* r) +{ + ALOG_ASSERT(env->IsInstanceOf(obj, gRect_class)); + + r->setLTRB(SkIntToScalar(env->GetIntField(obj, gRect_leftFieldID)), + SkIntToScalar(env->GetIntField(obj, gRect_topFieldID)), + SkIntToScalar(env->GetIntField(obj, gRect_rightFieldID)), + SkIntToScalar(env->GetIntField(obj, gRect_bottomFieldID))); + return r; +} + +void GraphicsJNI::rect_to_jrectf(const SkRect& r, JNIEnv* env, jobject obj) +{ + ALOG_ASSERT(env->IsInstanceOf(obj, gRectF_class)); + + env->SetFloatField(obj, gRectF_leftFieldID, SkScalarToFloat(r.fLeft)); + env->SetFloatField(obj, gRectF_topFieldID, SkScalarToFloat(r.fTop)); + env->SetFloatField(obj, gRectF_rightFieldID, SkScalarToFloat(r.fRight)); + env->SetFloatField(obj, gRectF_bottomFieldID, SkScalarToFloat(r.fBottom)); +} + +SkIPoint* GraphicsJNI::jpoint_to_ipoint(JNIEnv* env, jobject obj, SkIPoint* point) +{ + ALOG_ASSERT(env->IsInstanceOf(obj, gPoint_class)); + + point->set(env->GetIntField(obj, gPoint_xFieldID), + env->GetIntField(obj, gPoint_yFieldID)); + return point; +} + +void GraphicsJNI::ipoint_to_jpoint(const SkIPoint& ir, JNIEnv* env, jobject obj) +{ + ALOG_ASSERT(env->IsInstanceOf(obj, gPoint_class)); + + env->SetIntField(obj, gPoint_xFieldID, ir.fX); + env->SetIntField(obj, gPoint_yFieldID, ir.fY); +} + +SkPoint* GraphicsJNI::jpointf_to_point(JNIEnv* env, jobject obj, SkPoint* point) +{ + ALOG_ASSERT(env->IsInstanceOf(obj, gPointF_class)); + + point->set(env->GetIntField(obj, gPointF_xFieldID), + env->GetIntField(obj, gPointF_yFieldID)); + return point; +} + +void GraphicsJNI::point_to_jpointf(const SkPoint& r, JNIEnv* env, jobject obj) +{ + ALOG_ASSERT(env->IsInstanceOf(obj, gPointF_class)); + + env->SetFloatField(obj, gPointF_xFieldID, SkScalarToFloat(r.fX)); + env->SetFloatField(obj, gPointF_yFieldID, SkScalarToFloat(r.fY)); +} + +// See enum values in GraphicsJNI.h +jint GraphicsJNI::colorTypeToLegacyBitmapConfig(SkColorType colorType) { + switch (colorType) { + case kRGBA_F16_SkColorType: + return kRGBA_16F_LegacyBitmapConfig; + case kN32_SkColorType: + return kARGB_8888_LegacyBitmapConfig; + case kARGB_4444_SkColorType: + return kARGB_4444_LegacyBitmapConfig; + case kRGB_565_SkColorType: + return kRGB_565_LegacyBitmapConfig; + case kAlpha_8_SkColorType: + return kA8_LegacyBitmapConfig; + case kUnknown_SkColorType: + default: + break; + } + return kNo_LegacyBitmapConfig; +} + +SkColorType GraphicsJNI::legacyBitmapConfigToColorType(jint legacyConfig) { + const uint8_t gConfig2ColorType[] = { + kUnknown_SkColorType, + kAlpha_8_SkColorType, + kUnknown_SkColorType, // Previously kIndex_8_SkColorType, + kRGB_565_SkColorType, + kARGB_4444_SkColorType, + kN32_SkColorType, + kRGBA_F16_SkColorType, + kN32_SkColorType + }; + + if (legacyConfig < 0 || legacyConfig > kLastEnum_LegacyBitmapConfig) { + legacyConfig = kNo_LegacyBitmapConfig; + } + return static_cast<SkColorType>(gConfig2ColorType[legacyConfig]); +} + +AndroidBitmapFormat GraphicsJNI::getFormatFromConfig(JNIEnv* env, jobject jconfig) { + ALOG_ASSERT(env); + if (NULL == jconfig) { + return ANDROID_BITMAP_FORMAT_NONE; + } + ALOG_ASSERT(env->IsInstanceOf(jconfig, gBitmapConfig_class)); + jint javaConfigId = env->GetIntField(jconfig, gBitmapConfig_nativeInstanceID); + + const AndroidBitmapFormat config2BitmapFormat[] = { + ANDROID_BITMAP_FORMAT_NONE, + ANDROID_BITMAP_FORMAT_A_8, + ANDROID_BITMAP_FORMAT_NONE, // Previously Config.Index_8 + ANDROID_BITMAP_FORMAT_RGB_565, + ANDROID_BITMAP_FORMAT_RGBA_4444, + ANDROID_BITMAP_FORMAT_RGBA_8888, + ANDROID_BITMAP_FORMAT_RGBA_F16, + ANDROID_BITMAP_FORMAT_NONE // Congfig.HARDWARE + }; + return config2BitmapFormat[javaConfigId]; +} + +jobject GraphicsJNI::getConfigFromFormat(JNIEnv* env, AndroidBitmapFormat format) { + ALOG_ASSERT(env); + jint configId = kNo_LegacyBitmapConfig; + switch (format) { + case ANDROID_BITMAP_FORMAT_A_8: + configId = kA8_LegacyBitmapConfig; + break; + case ANDROID_BITMAP_FORMAT_RGB_565: + configId = kRGB_565_LegacyBitmapConfig; + break; + case ANDROID_BITMAP_FORMAT_RGBA_4444: + configId = kARGB_4444_LegacyBitmapConfig; + break; + case ANDROID_BITMAP_FORMAT_RGBA_8888: + configId = kARGB_8888_LegacyBitmapConfig; + break; + case ANDROID_BITMAP_FORMAT_RGBA_F16: + configId = kRGBA_16F_LegacyBitmapConfig; + break; + default: + break; + } + + return env->CallStaticObjectMethod(gBitmapConfig_class, + gBitmapConfig_nativeToConfigMethodID, configId); +} + +SkColorType GraphicsJNI::getNativeBitmapColorType(JNIEnv* env, jobject jconfig) { + ALOG_ASSERT(env); + if (NULL == jconfig) { + return kUnknown_SkColorType; + } + ALOG_ASSERT(env->IsInstanceOf(jconfig, gBitmapConfig_class)); + int c = env->GetIntField(jconfig, gBitmapConfig_nativeInstanceID); + return legacyBitmapConfigToColorType(c); +} + +bool GraphicsJNI::isHardwareConfig(JNIEnv* env, jobject jconfig) { + ALOG_ASSERT(env); + if (NULL == jconfig) { + return false; + } + int c = env->GetIntField(jconfig, gBitmapConfig_nativeInstanceID); + return c == kHardware_LegacyBitmapConfig; +} + +jint GraphicsJNI::hardwareLegacyBitmapConfig() { + return kHardware_LegacyBitmapConfig; +} + +android::Canvas* GraphicsJNI::getNativeCanvas(JNIEnv* env, jobject canvas) { + ALOG_ASSERT(env); + ALOG_ASSERT(canvas); + ALOG_ASSERT(env->IsInstanceOf(canvas, gCanvas_class)); + jlong canvasHandle = env->GetLongField(canvas, gCanvas_nativeInstanceID); + if (!canvasHandle) { + return NULL; + } + return reinterpret_cast<android::Canvas*>(canvasHandle); +} + +SkRegion* GraphicsJNI::getNativeRegion(JNIEnv* env, jobject region) +{ + ALOG_ASSERT(env); + ALOG_ASSERT(region); + ALOG_ASSERT(env->IsInstanceOf(region, gRegion_class)); + jlong regionHandle = env->GetLongField(region, gRegion_nativeInstanceID); + SkRegion* r = reinterpret_cast<SkRegion*>(regionHandle); + ALOG_ASSERT(r); + return r; +} + +/////////////////////////////////////////////////////////////////////////////////////////// + +jobject GraphicsJNI::createBitmapRegionDecoder(JNIEnv* env, SkBitmapRegionDecoder* bitmap) +{ + ALOG_ASSERT(bitmap != NULL); + + jobject obj = env->NewObject(gBitmapRegionDecoder_class, + gBitmapRegionDecoder_constructorMethodID, + reinterpret_cast<jlong>(bitmap)); + hasException(env); // For the side effect of logging. + return obj; +} + +jobject GraphicsJNI::createRegion(JNIEnv* env, SkRegion* region) +{ + ALOG_ASSERT(region != NULL); + jobject obj = env->NewObject(gRegion_class, gRegion_constructorMethodID, + reinterpret_cast<jlong>(region), 0); + hasException(env); // For the side effect of logging. + return obj; +} + +/////////////////////////////////////////////////////////////////////////////// + +jobject GraphicsJNI::getColorSpace(JNIEnv* env, SkColorSpace* decodeColorSpace, + SkColorType decodeColorType) { + if (!decodeColorSpace || decodeColorType == kAlpha_8_SkColorType) { + return nullptr; + } + + // Special checks for the common sRGB cases and their extended variants. + jobject namedCS = nullptr; + sk_sp<SkColorSpace> srgbLinear = SkColorSpace::MakeSRGBLinear(); + if (decodeColorType == kRGBA_F16_SkColorType) { + // An F16 Bitmap will always report that it is EXTENDED if + // it matches a ColorSpace that has an EXTENDED variant. + if (decodeColorSpace->isSRGB()) { + namedCS = env->GetStaticObjectField(gColorSpace_Named_class, + gColorSpace_Named_ExtendedSRGBFieldID); + } else if (decodeColorSpace == srgbLinear.get()) { + namedCS = env->GetStaticObjectField(gColorSpace_Named_class, + gColorSpace_Named_LinearExtendedSRGBFieldID); + } + } else if (decodeColorSpace->isSRGB()) { + namedCS = env->GetStaticObjectField(gColorSpace_Named_class, + gColorSpace_Named_sRGBFieldID); + } else if (decodeColorSpace == srgbLinear.get()) { + namedCS = env->GetStaticObjectField(gColorSpace_Named_class, + gColorSpace_Named_LinearSRGBFieldID); + } + + if (namedCS) { + return env->CallStaticObjectMethod(gColorSpace_class, gColorSpace_getMethodID, namedCS); + } + + // Try to match against known RGB color spaces using the CIE XYZ D50 + // conversion matrix and numerical transfer function parameters + skcms_Matrix3x3 xyzMatrix; + LOG_ALWAYS_FATAL_IF(!decodeColorSpace->toXYZD50(&xyzMatrix)); + + skcms_TransferFunction transferParams; + // We can only handle numerical transfer functions at the moment + LOG_ALWAYS_FATAL_IF(!decodeColorSpace->isNumericalTransferFn(&transferParams)); + + jobject params = env->NewObject(gTransferParameters_class, + gTransferParameters_constructorMethodID, + transferParams.a, transferParams.b, transferParams.c, + transferParams.d, transferParams.e, transferParams.f, + transferParams.g); + + jfloatArray xyzArray = env->NewFloatArray(9); + jfloat xyz[9] = { + xyzMatrix.vals[0][0], + xyzMatrix.vals[1][0], + xyzMatrix.vals[2][0], + xyzMatrix.vals[0][1], + xyzMatrix.vals[1][1], + xyzMatrix.vals[2][1], + xyzMatrix.vals[0][2], + xyzMatrix.vals[1][2], + xyzMatrix.vals[2][2] + }; + env->SetFloatArrayRegion(xyzArray, 0, 9, xyz); + + jobject colorSpace = env->CallStaticObjectMethod(gColorSpace_class, + gColorSpace_matchMethodID, xyzArray, params); + + if (colorSpace == nullptr) { + // We couldn't find an exact match, let's create a new color space + // instance with the 3x3 conversion matrix and transfer function + colorSpace = env->NewObject(gColorSpaceRGB_class, + gColorSpaceRGB_constructorMethodID, + env->NewStringUTF("Unknown"), xyzArray, params); + } + + env->DeleteLocalRef(xyzArray); + return colorSpace; +} + +/////////////////////////////////////////////////////////////////////////////// +bool HeapAllocator::allocPixelRef(SkBitmap* bitmap) { + mStorage = android::Bitmap::allocateHeapBitmap(bitmap); + return !!mStorage; +} + +//////////////////////////////////////////////////////////////////////////////// + +RecyclingClippingPixelAllocator::RecyclingClippingPixelAllocator( + android::Bitmap* recycledBitmap, size_t recycledBytes) + : mRecycledBitmap(recycledBitmap) + , mRecycledBytes(recycledBytes) + , mSkiaBitmap(nullptr) + , mNeedsCopy(false) +{} + +RecyclingClippingPixelAllocator::~RecyclingClippingPixelAllocator() {} + +bool RecyclingClippingPixelAllocator::allocPixelRef(SkBitmap* bitmap) { + // Ensure that the caller did not pass in a NULL bitmap to the constructor or this + // function. + LOG_ALWAYS_FATAL_IF(!mRecycledBitmap); + LOG_ALWAYS_FATAL_IF(!bitmap); + mSkiaBitmap = bitmap; + + // This behaves differently than the RecyclingPixelAllocator. For backwards + // compatibility, the original color type of the recycled bitmap must be maintained. + if (mRecycledBitmap->info().colorType() != bitmap->colorType()) { + return false; + } + + // The Skia bitmap specifies the width and height needed by the decoder. + // mRecycledBitmap specifies the width and height of the bitmap that we + // want to reuse. Neither can be changed. We will try to find a way + // to reuse the memory. + const int maxWidth = std::max(bitmap->width(), mRecycledBitmap->info().width()); + const int maxHeight = std::max(bitmap->height(), mRecycledBitmap->info().height()); + const SkImageInfo maxInfo = bitmap->info().makeWH(maxWidth, maxHeight); + const size_t rowBytes = maxInfo.minRowBytes(); + const size_t bytesNeeded = maxInfo.computeByteSize(rowBytes); + if (bytesNeeded <= mRecycledBytes) { + // Here we take advantage of reconfigure() to reset the rowBytes + // of mRecycledBitmap. It is very important that we pass in + // mRecycledBitmap->info() for the SkImageInfo. According to the + // specification for BitmapRegionDecoder, we are not allowed to change + // the SkImageInfo. + // We can (must) preserve the color space since it doesn't affect the + // storage needs + mRecycledBitmap->reconfigure( + mRecycledBitmap->info().makeColorSpace(bitmap->refColorSpace()), + rowBytes); + + // Give the bitmap the same pixelRef as mRecycledBitmap. + // skbug.com/4538: We also need to make sure that the rowBytes on the pixel ref + // match the rowBytes on the bitmap. + bitmap->setInfo(bitmap->info(), rowBytes); + bitmap->setPixelRef(sk_ref_sp(mRecycledBitmap), 0, 0); + + // Make sure that the recycled bitmap has the correct alpha type. + mRecycledBitmap->setAlphaType(bitmap->alphaType()); + + bitmap->notifyPixelsChanged(); + mNeedsCopy = false; + + // TODO: If the dimensions of the SkBitmap are smaller than those of + // mRecycledBitmap, should we zero the memory in mRecycledBitmap? + return true; + } + + // In the event that mRecycledBitmap is not large enough, allocate new memory + // on the heap. + SkBitmap::HeapAllocator heapAllocator; + + // We will need to copy from heap memory to mRecycledBitmap's memory after the + // decode is complete. + mNeedsCopy = true; + + return heapAllocator.allocPixelRef(bitmap); +} + +void RecyclingClippingPixelAllocator::copyIfNecessary() { + if (mNeedsCopy) { + mRecycledBitmap->ref(); + SkPixelRef* recycledPixels = mRecycledBitmap; + void* dst = recycledPixels->pixels(); + const size_t dstRowBytes = mRecycledBitmap->rowBytes(); + const size_t bytesToCopy = std::min(mRecycledBitmap->info().minRowBytes(), + mSkiaBitmap->info().minRowBytes()); + const int rowsToCopy = std::min(mRecycledBitmap->info().height(), + mSkiaBitmap->info().height()); + for (int y = 0; y < rowsToCopy; y++) { + memcpy(dst, mSkiaBitmap->getAddr(0, y), bytesToCopy); + dst = SkTAddOffset<void>(dst, dstRowBytes); + } + recycledPixels->notifyPixelsChanged(); + recycledPixels->unref(); + } + mRecycledBitmap = nullptr; + mSkiaBitmap = nullptr; +} + +//////////////////////////////////////////////////////////////////////////////// + +AshmemPixelAllocator::AshmemPixelAllocator(JNIEnv *env) { + LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&mJavaVM) != JNI_OK, + "env->GetJavaVM failed"); +} + +bool AshmemPixelAllocator::allocPixelRef(SkBitmap* bitmap) { + mStorage = android::Bitmap::allocateAshmemBitmap(bitmap); + return !!mStorage; +} + +//////////////////////////////////////////////////////////////////////////////// + +int register_android_graphics_Graphics(JNIEnv* env) +{ + jmethodID m; + jclass c; + + gRect_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Rect")); + gRect_leftFieldID = GetFieldIDOrDie(env, gRect_class, "left", "I"); + gRect_topFieldID = GetFieldIDOrDie(env, gRect_class, "top", "I"); + gRect_rightFieldID = GetFieldIDOrDie(env, gRect_class, "right", "I"); + gRect_bottomFieldID = GetFieldIDOrDie(env, gRect_class, "bottom", "I"); + + gRectF_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/RectF")); + gRectF_leftFieldID = GetFieldIDOrDie(env, gRectF_class, "left", "F"); + gRectF_topFieldID = GetFieldIDOrDie(env, gRectF_class, "top", "F"); + gRectF_rightFieldID = GetFieldIDOrDie(env, gRectF_class, "right", "F"); + gRectF_bottomFieldID = GetFieldIDOrDie(env, gRectF_class, "bottom", "F"); + + gPoint_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Point")); + gPoint_xFieldID = GetFieldIDOrDie(env, gPoint_class, "x", "I"); + gPoint_yFieldID = GetFieldIDOrDie(env, gPoint_class, "y", "I"); + + gPointF_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/PointF")); + gPointF_xFieldID = GetFieldIDOrDie(env, gPointF_class, "x", "F"); + gPointF_yFieldID = GetFieldIDOrDie(env, gPointF_class, "y", "F"); + + gBitmapRegionDecoder_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/BitmapRegionDecoder")); + gBitmapRegionDecoder_constructorMethodID = GetMethodIDOrDie(env, gBitmapRegionDecoder_class, "<init>", "(J)V"); + + gBitmapConfig_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Bitmap$Config")); + gBitmapConfig_nativeInstanceID = GetFieldIDOrDie(env, gBitmapConfig_class, "nativeInt", "I"); + gBitmapConfig_nativeToConfigMethodID = GetStaticMethodIDOrDie(env, gBitmapConfig_class, + "nativeToConfig", + "(I)Landroid/graphics/Bitmap$Config;"); + + gCanvas_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Canvas")); + gCanvas_nativeInstanceID = GetFieldIDOrDie(env, gCanvas_class, "mNativeCanvasWrapper", "J"); + + gPicture_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Picture")); + gPicture_nativeInstanceID = GetFieldIDOrDie(env, gPicture_class, "mNativePicture", "J"); + + gRegion_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Region")); + gRegion_nativeInstanceID = GetFieldIDOrDie(env, gRegion_class, "mNativeRegion", "J"); + gRegion_constructorMethodID = GetMethodIDOrDie(env, gRegion_class, "<init>", "(JI)V"); + + c = env->FindClass("java/lang/Byte"); + gByte_class = (jclass) env->NewGlobalRef( + env->GetStaticObjectField(c, env->GetStaticFieldID(c, "TYPE", "Ljava/lang/Class;"))); + + gVMRuntime_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "dalvik/system/VMRuntime")); + m = env->GetStaticMethodID(gVMRuntime_class, "getRuntime", "()Ldalvik/system/VMRuntime;"); + gVMRuntime = env->NewGlobalRef(env->CallStaticObjectMethod(gVMRuntime_class, m)); + gVMRuntime_newNonMovableArray = GetMethodIDOrDie(env, gVMRuntime_class, "newNonMovableArray", + "(Ljava/lang/Class;I)Ljava/lang/Object;"); + gVMRuntime_addressOf = GetMethodIDOrDie(env, gVMRuntime_class, "addressOf", "(Ljava/lang/Object;)J"); + + gColorSpace_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ColorSpace")); + gColorSpace_getMethodID = GetStaticMethodIDOrDie(env, gColorSpace_class, + "get", "(Landroid/graphics/ColorSpace$Named;)Landroid/graphics/ColorSpace;"); + gColorSpace_matchMethodID = GetStaticMethodIDOrDie(env, gColorSpace_class, "match", + "([FLandroid/graphics/ColorSpace$Rgb$TransferParameters;)Landroid/graphics/ColorSpace;"); + + gColorSpaceRGB_class = MakeGlobalRefOrDie(env, + FindClassOrDie(env, "android/graphics/ColorSpace$Rgb")); + gColorSpaceRGB_constructorMethodID = GetMethodIDOrDie(env, gColorSpaceRGB_class, + "<init>", "(Ljava/lang/String;[FLandroid/graphics/ColorSpace$Rgb$TransferParameters;)V"); + + gColorSpace_Named_class = MakeGlobalRefOrDie(env, + FindClassOrDie(env, "android/graphics/ColorSpace$Named")); + gColorSpace_Named_sRGBFieldID = GetStaticFieldIDOrDie(env, + gColorSpace_Named_class, "SRGB", "Landroid/graphics/ColorSpace$Named;"); + gColorSpace_Named_ExtendedSRGBFieldID = GetStaticFieldIDOrDie(env, + gColorSpace_Named_class, "EXTENDED_SRGB", "Landroid/graphics/ColorSpace$Named;"); + gColorSpace_Named_LinearSRGBFieldID = GetStaticFieldIDOrDie(env, + gColorSpace_Named_class, "LINEAR_SRGB", "Landroid/graphics/ColorSpace$Named;"); + gColorSpace_Named_LinearExtendedSRGBFieldID = GetStaticFieldIDOrDie(env, + gColorSpace_Named_class, "LINEAR_EXTENDED_SRGB", "Landroid/graphics/ColorSpace$Named;"); + + gTransferParameters_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, + "android/graphics/ColorSpace$Rgb$TransferParameters")); + gTransferParameters_constructorMethodID = GetMethodIDOrDie(env, gTransferParameters_class, + "<init>", "(DDDDDDD)V"); + + return 0; +} diff --git a/libs/hwui/jni/GraphicsJNI.h b/libs/hwui/jni/GraphicsJNI.h new file mode 100644 index 000000000000..b58a740a4c27 --- /dev/null +++ b/libs/hwui/jni/GraphicsJNI.h @@ -0,0 +1,335 @@ +#ifndef _ANDROID_GRAPHICS_GRAPHICS_JNI_H_ +#define _ANDROID_GRAPHICS_GRAPHICS_JNI_H_ + +#include <cutils/compiler.h> + +#include "Bitmap.h" +#include "SkBitmap.h" +#include "SkBRDAllocator.h" +#include "SkCodec.h" +#include "SkPixelRef.h" +#include "SkMallocPixelRef.h" +#include "SkPoint.h" +#include "SkRect.h" +#include "SkColorSpace.h" +#include <hwui/Canvas.h> +#include <hwui/Bitmap.h> + +#include "graphics_jni_helpers.h" + +class SkBitmapRegionDecoder; +class SkCanvas; + +namespace android { +class Paint; +struct Typeface; +} + +class GraphicsJNI { +public: + // This enum must keep these int values, to match the int values + // in the java Bitmap.Config enum. + enum LegacyBitmapConfig { + kNo_LegacyBitmapConfig = 0, + kA8_LegacyBitmapConfig = 1, + kIndex8_LegacyBitmapConfig = 2, + kRGB_565_LegacyBitmapConfig = 3, + kARGB_4444_LegacyBitmapConfig = 4, + kARGB_8888_LegacyBitmapConfig = 5, + kRGBA_16F_LegacyBitmapConfig = 6, + kHardware_LegacyBitmapConfig = 7, + + kLastEnum_LegacyBitmapConfig = kHardware_LegacyBitmapConfig + }; + + static void setJavaVM(JavaVM* javaVM); + + /** returns a pointer to the JavaVM provided when we initialized the module */ + static JavaVM* getJavaVM() { return mJavaVM; } + + /** return a pointer to the JNIEnv for this thread */ + static JNIEnv* getJNIEnv(); + + /** create a JNIEnv* for this thread or assert if one already exists */ + static JNIEnv* attachJNIEnv(const char* envName); + + /** detach the current thread from the JavaVM */ + static void detachJNIEnv(); + + // returns true if an exception is set (and dumps it out to the Log) + static bool hasException(JNIEnv*); + + static void get_jrect(JNIEnv*, jobject jrect, int* L, int* T, int* R, int* B); + static void set_jrect(JNIEnv*, jobject jrect, int L, int T, int R, int B); + + static SkIRect* jrect_to_irect(JNIEnv*, jobject jrect, SkIRect*); + static void irect_to_jrect(const SkIRect&, JNIEnv*, jobject jrect); + + static SkRect* jrectf_to_rect(JNIEnv*, jobject jrectf, SkRect*); + static SkRect* jrect_to_rect(JNIEnv*, jobject jrect, SkRect*); + static void rect_to_jrectf(const SkRect&, JNIEnv*, jobject jrectf); + + static void set_jpoint(JNIEnv*, jobject jrect, int x, int y); + + static SkIPoint* jpoint_to_ipoint(JNIEnv*, jobject jpoint, SkIPoint* point); + static void ipoint_to_jpoint(const SkIPoint& point, JNIEnv*, jobject jpoint); + + static SkPoint* jpointf_to_point(JNIEnv*, jobject jpointf, SkPoint* point); + static void point_to_jpointf(const SkPoint& point, JNIEnv*, jobject jpointf); + + ANDROID_API static android::Canvas* getNativeCanvas(JNIEnv*, jobject canvas); + static android::Bitmap* getNativeBitmap(JNIEnv*, jobject bitmap); + static SkImageInfo getBitmapInfo(JNIEnv*, jobject bitmap, uint32_t* outRowBytes, + bool* isHardware); + static SkRegion* getNativeRegion(JNIEnv*, jobject region); + + /* + * LegacyBitmapConfig is the old enum in Skia that matched the enum int values + * in Bitmap.Config. Skia no longer supports this config, but has replaced it + * with SkColorType. These routines convert between the two. + */ + static SkColorType legacyBitmapConfigToColorType(jint legacyConfig); + static jint colorTypeToLegacyBitmapConfig(SkColorType colorType); + + /** Return the corresponding native colorType from the java Config enum, + or kUnknown_SkColorType if the java object is null. + */ + static SkColorType getNativeBitmapColorType(JNIEnv*, jobject jconfig); + static AndroidBitmapFormat getFormatFromConfig(JNIEnv* env, jobject jconfig); + static jobject getConfigFromFormat(JNIEnv* env, AndroidBitmapFormat format); + + static bool isHardwareConfig(JNIEnv* env, jobject jconfig); + static jint hardwareLegacyBitmapConfig(); + + static jobject createRegion(JNIEnv* env, SkRegion* region); + + static jobject createBitmapRegionDecoder(JNIEnv* env, SkBitmapRegionDecoder* bitmap); + + /** + * Given a bitmap we natively allocate a memory block to store the contents + * of that bitmap. The memory is then attached to the bitmap via an + * SkPixelRef, which ensures that upon deletion the appropriate caches + * are notified. + */ + static bool allocatePixels(JNIEnv* env, SkBitmap* bitmap); + + /** Copy the colors in colors[] to the bitmap, convert to the correct + format along the way. + Whether to use premultiplied pixels is determined by dstBitmap's alphaType. + */ + static bool SetPixels(JNIEnv* env, jintArray colors, int srcOffset, + int srcStride, int x, int y, int width, int height, + SkBitmap* dstBitmap); + + /** + * Convert the native SkColorSpace retrieved from ColorSpace.Rgb.getNativeInstance(). + * + * This will never throw an Exception. If the ColorSpace is one that Skia cannot + * use, ColorSpace.Rgb.getNativeInstance() would have thrown an Exception. It may, + * however, be nullptr, which may be acceptable. + */ + static sk_sp<SkColorSpace> getNativeColorSpace(jlong colorSpaceHandle); + + /** + * Return the android.graphics.ColorSpace Java object that corresponds to decodeColorSpace + * and decodeColorType. + * + * This may create a new object if none of the Named ColorSpaces match. + */ + static jobject getColorSpace(JNIEnv* env, SkColorSpace* decodeColorSpace, + SkColorType decodeColorType); + + /** + * Convert from a Java @ColorLong to an SkColor4f that Skia can use directly. + * + * This ignores the encoded ColorSpace, besides checking to see if it is sRGB, + * which is encoded differently. The color space should be passed down separately + * via ColorSpace#getNativeInstance(), and converted with getNativeColorSpace(), + * above. + */ + static SkColor4f convertColorLong(jlong color); + +private: + /* JNI JavaVM pointer */ + static JavaVM* mJavaVM; +}; + +class HeapAllocator : public SkBRDAllocator { +public: + HeapAllocator() { }; + ~HeapAllocator() { }; + + virtual bool allocPixelRef(SkBitmap* bitmap) override; + + /** + * Fetches the backing allocation object. Must be called! + */ + android::Bitmap* getStorageObjAndReset() { + return mStorage.release(); + }; + + SkCodec::ZeroInitialized zeroInit() const override { return SkCodec::kYes_ZeroInitialized; } +private: + sk_sp<android::Bitmap> mStorage; +}; + +/** + * Allocator to handle reusing bitmaps for BitmapRegionDecoder. + * + * The BitmapRegionDecoder documentation states that, if it is + * provided, the recycled bitmap will always be reused, clipping + * the decoded output to fit in the recycled bitmap if necessary. + * This allocator implements that behavior. + * + * Skia's SkBitmapRegionDecoder expects the memory that + * is allocated to be large enough to decode the entire region + * that is requested. It will decode directly into the memory + * that is provided. + * + * FIXME: BUG:25465958 + * If the recycled bitmap is not large enough for the decode + * requested, meaning that a clip is required, we will allocate + * enough memory for Skia to perform the decode, and then copy + * from the decoded output into the recycled bitmap. + * + * If the recycled bitmap is large enough for the decode requested, + * we will provide that memory for Skia to decode directly into. + * + * This allocator should only be used for a single allocation. + * After we reuse the recycledBitmap once, it is dangerous to + * reuse it again, given that it still may be in use from our + * first allocation. + */ +class RecyclingClippingPixelAllocator : public SkBRDAllocator { +public: + + RecyclingClippingPixelAllocator(android::Bitmap* recycledBitmap, + size_t recycledBytes); + + ~RecyclingClippingPixelAllocator(); + + virtual bool allocPixelRef(SkBitmap* bitmap) override; + + /** + * Must be called! + * + * In the event that the recycled bitmap is not large enough for + * the allocation requested, we will allocate memory on the heap + * instead. As a final step, once we are done using this memory, + * we will copy the contents of the heap memory into the recycled + * bitmap's memory, clipping as necessary. + */ + void copyIfNecessary(); + + /** + * Indicates that this allocator does not allocate zero initialized + * memory. + */ + SkCodec::ZeroInitialized zeroInit() const override { return SkCodec::kNo_ZeroInitialized; } + +private: + android::Bitmap* mRecycledBitmap; + const size_t mRecycledBytes; + SkBitmap* mSkiaBitmap; + bool mNeedsCopy; +}; + +class AshmemPixelAllocator : public SkBitmap::Allocator { +public: + explicit AshmemPixelAllocator(JNIEnv* env); + ~AshmemPixelAllocator() { }; + virtual bool allocPixelRef(SkBitmap* bitmap); + android::Bitmap* getStorageObjAndReset() { + return mStorage.release(); + }; + +private: + JavaVM* mJavaVM; + sk_sp<android::Bitmap> mStorage; +}; + + +enum JNIAccess { + kRO_JNIAccess, + kRW_JNIAccess +}; + +class AutoJavaFloatArray { +public: + AutoJavaFloatArray(JNIEnv* env, jfloatArray array, + int minLength = 0, JNIAccess = kRW_JNIAccess); + ~AutoJavaFloatArray(); + + float* ptr() const { return fPtr; } + int length() const { return fLen; } + +private: + JNIEnv* fEnv; + jfloatArray fArray; + float* fPtr; + int fLen; + int fReleaseMode; +}; + +class AutoJavaIntArray { +public: + AutoJavaIntArray(JNIEnv* env, jintArray array, int minLength = 0); + ~AutoJavaIntArray(); + + jint* ptr() const { return fPtr; } + int length() const { return fLen; } + +private: + JNIEnv* fEnv; + jintArray fArray; + jint* fPtr; + int fLen; +}; + +class AutoJavaShortArray { +public: + AutoJavaShortArray(JNIEnv* env, jshortArray array, + int minLength = 0, JNIAccess = kRW_JNIAccess); + ~AutoJavaShortArray(); + + jshort* ptr() const { return fPtr; } + int length() const { return fLen; } + +private: + JNIEnv* fEnv; + jshortArray fArray; + jshort* fPtr; + int fLen; + int fReleaseMode; +}; + +class AutoJavaByteArray { +public: + AutoJavaByteArray(JNIEnv* env, jbyteArray array, int minLength = 0); + ~AutoJavaByteArray(); + + jbyte* ptr() const { return fPtr; } + int length() const { return fLen; } + +private: + JNIEnv* fEnv; + jbyteArray fArray; + jbyte* fPtr; + int fLen; +}; + +void doThrowNPE(JNIEnv* env); +void doThrowAIOOBE(JNIEnv* env); // Array Index Out Of Bounds Exception +void doThrowIAE(JNIEnv* env, const char* msg = NULL); // Illegal Argument +void doThrowRE(JNIEnv* env, const char* msg = NULL); // Runtime +void doThrowISE(JNIEnv* env, const char* msg = NULL); // Illegal State +void doThrowOOME(JNIEnv* env, const char* msg = NULL); // Out of memory +void doThrowIOE(JNIEnv* env, const char* msg = NULL); // IO Exception + +#define NPE_CHECK_RETURN_ZERO(env, object) \ + do { if (NULL == (object)) { doThrowNPE(env); return 0; } } while (0) + +#define NPE_CHECK_RETURN_VOID(env, object) \ + do { if (NULL == (object)) { doThrowNPE(env); return; } } while (0) + +#endif // _ANDROID_GRAPHICS_GRAPHICS_JNI_H_ diff --git a/libs/hwui/jni/GraphicsStatsService.cpp b/libs/hwui/jni/GraphicsStatsService.cpp new file mode 100644 index 000000000000..6076552fc094 --- /dev/null +++ b/libs/hwui/jni/GraphicsStatsService.cpp @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#undef LOG_TAG +#define LOG_TAG "GraphicsStatsService" + +#include <JankTracker.h> +#include <log/log.h> +#include <nativehelper/ScopedPrimitiveArray.h> +#include <nativehelper/ScopedUtfChars.h> +#include <service/GraphicsStatsService.h> +#include <stats_event.h> +#include <stats_pull_atom_callback.h> +#include <statslog.h> + +#include "android/graphics/jni_runtime.h" +#include "GraphicsJNI.h" + +namespace android { + +using namespace android::uirenderer; + +static jint getAshmemSize(JNIEnv*, jobject) { + return sizeof(ProfileData); +} + +static jlong createDump(JNIEnv*, jobject, jint fd, jboolean isProto) { + GraphicsStatsService::Dump* dump = + GraphicsStatsService::createDump(fd, + isProto ? GraphicsStatsService::DumpType::Protobuf + : GraphicsStatsService::DumpType::Text); + return reinterpret_cast<jlong>(dump); +} + +static void addToDump(JNIEnv* env, jobject, jlong dumpPtr, jstring jpath, jstring jpackage, + jlong versionCode, jlong startTime, jlong endTime, jbyteArray jdata) { + std::string path; + const ProfileData* data = nullptr; + LOG_ALWAYS_FATAL_IF(jdata == nullptr && jpath == nullptr, "Path and data can't both be null"); + ScopedByteArrayRO buffer{env}; + if (jdata != nullptr) { + buffer.reset(jdata); + LOG_ALWAYS_FATAL_IF(buffer.size() != sizeof(ProfileData), + "Buffer size %zu doesn't match expected %zu!", buffer.size(), + sizeof(ProfileData)); + data = reinterpret_cast<const ProfileData*>(buffer.get()); + } + if (jpath != nullptr) { + ScopedUtfChars pathChars(env, jpath); + LOG_ALWAYS_FATAL_IF(pathChars.size() <= 0 || !pathChars.c_str(), + "Failed to get path chars"); + path.assign(pathChars.c_str(), pathChars.size()); + } + ScopedUtfChars packageChars(env, jpackage); + LOG_ALWAYS_FATAL_IF(packageChars.size() <= 0 || !packageChars.c_str(), + "Failed to get path chars"); + GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr); + LOG_ALWAYS_FATAL_IF(!dump, "null passed for dump pointer"); + + const std::string package(packageChars.c_str(), packageChars.size()); + GraphicsStatsService::addToDump(dump, path, package, versionCode, startTime, endTime, data); +} + +static void addFileToDump(JNIEnv* env, jobject, jlong dumpPtr, jstring jpath) { + ScopedUtfChars pathChars(env, jpath); + LOG_ALWAYS_FATAL_IF(pathChars.size() <= 0 || !pathChars.c_str(), "Failed to get path chars"); + const std::string path(pathChars.c_str(), pathChars.size()); + GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr); + GraphicsStatsService::addToDump(dump, path); +} + +static void finishDump(JNIEnv*, jobject, jlong dumpPtr) { + GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr); + GraphicsStatsService::finishDump(dump); +} + +static void finishDumpInMemory(JNIEnv* env, jobject, jlong dumpPtr, jlong pulledData, + jboolean lastFullDay) { + GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr); + AStatsEventList* data = reinterpret_cast<AStatsEventList*>(pulledData); + GraphicsStatsService::finishDumpInMemory(dump, data, lastFullDay == JNI_TRUE); +} + +static void saveBuffer(JNIEnv* env, jobject clazz, jstring jpath, jstring jpackage, + jlong versionCode, jlong startTime, jlong endTime, jbyteArray jdata) { + ScopedByteArrayRO buffer(env, jdata); + LOG_ALWAYS_FATAL_IF(buffer.size() != sizeof(ProfileData), + "Buffer size %zu doesn't match expected %zu!", buffer.size(), + sizeof(ProfileData)); + ScopedUtfChars pathChars(env, jpath); + LOG_ALWAYS_FATAL_IF(pathChars.size() <= 0 || !pathChars.c_str(), "Failed to get path chars"); + ScopedUtfChars packageChars(env, jpackage); + LOG_ALWAYS_FATAL_IF(packageChars.size() <= 0 || !packageChars.c_str(), + "Failed to get path chars"); + + const std::string path(pathChars.c_str(), pathChars.size()); + const std::string package(packageChars.c_str(), packageChars.size()); + const ProfileData* data = reinterpret_cast<const ProfileData*>(buffer.get()); + GraphicsStatsService::saveBuffer(path, package, versionCode, startTime, endTime, data); +} + +static jobject gGraphicsStatsServiceObject = nullptr; +static jmethodID gGraphicsStatsService_pullGraphicsStatsMethodID; + +static JNIEnv* getJNIEnv() { + JavaVM* vm = GraphicsJNI::getJavaVM(); + JNIEnv* env = nullptr; + if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { + if (vm->AttachCurrentThreadAsDaemon(&env, nullptr) != JNI_OK) { + LOG_ALWAYS_FATAL("Failed to AttachCurrentThread!"); + } + } + return env; +} + +// graphicsStatsPullCallback is invoked by statsd service to pull GRAPHICS_STATS atom. +static AStatsManager_PullAtomCallbackReturn graphicsStatsPullCallback(int32_t atom_tag, + AStatsEventList* data, + void* cookie) { + JNIEnv* env = getJNIEnv(); + if (!env) { + return false; + } + if (gGraphicsStatsServiceObject == nullptr) { + ALOGE("Failed to get graphicsstats service"); + return AStatsManager_PULL_SKIP; + } + + for (bool lastFullDay : {true, false}) { + env->CallVoidMethod(gGraphicsStatsServiceObject, + gGraphicsStatsService_pullGraphicsStatsMethodID, + (jboolean)(lastFullDay ? JNI_TRUE : JNI_FALSE), + reinterpret_cast<jlong>(data)); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + ALOGE("Failed to invoke graphicsstats service"); + return AStatsManager_PULL_SKIP; + } + } + return AStatsManager_PULL_SUCCESS; +} + +// Register a puller for GRAPHICS_STATS atom with the statsd service. +static void nativeInit(JNIEnv* env, jobject javaObject) { + gGraphicsStatsServiceObject = env->NewGlobalRef(javaObject); + AStatsManager_PullAtomMetadata* metadata = AStatsManager_PullAtomMetadata_obtain(); + AStatsManager_PullAtomMetadata_setCoolDownNs(metadata, 10 * 1000000); // 10 milliseconds + AStatsManager_PullAtomMetadata_setTimeoutNs(metadata, 2 * NS_PER_SEC); // 2 seconds + + AStatsManager_registerPullAtomCallback(android::util::GRAPHICS_STATS, + &graphicsStatsPullCallback, metadata, nullptr); + + AStatsManager_PullAtomMetadata_release(metadata); +} + +static void nativeDestructor(JNIEnv* env, jobject javaObject) { + AStatsManager_unregisterPullAtomCallback(android::util::GRAPHICS_STATS); + env->DeleteGlobalRef(gGraphicsStatsServiceObject); + gGraphicsStatsServiceObject = nullptr; +} + +} // namespace android +using namespace android; + +static const JNINativeMethod sMethods[] = + {{"nGetAshmemSize", "()I", (void*)getAshmemSize}, + {"nCreateDump", "(IZ)J", (void*)createDump}, + {"nAddToDump", "(JLjava/lang/String;Ljava/lang/String;JJJ[B)V", (void*)addToDump}, + {"nAddToDump", "(JLjava/lang/String;)V", (void*)addFileToDump}, + {"nFinishDump", "(J)V", (void*)finishDump}, + {"nFinishDumpInMemory", "(JJZ)V", (void*)finishDumpInMemory}, + {"nSaveBuffer", "(Ljava/lang/String;Ljava/lang/String;JJJ[B)V", (void*)saveBuffer}, + {"nativeInit", "()V", (void*)nativeInit}, + {"nativeDestructor", "()V", (void*)nativeDestructor}}; + +int register_android_graphics_GraphicsStatsService(JNIEnv* env) { + jclass graphicsStatsService_class = + FindClassOrDie(env, "android/graphics/GraphicsStatsService"); + gGraphicsStatsService_pullGraphicsStatsMethodID = + GetMethodIDOrDie(env, graphicsStatsService_class, "pullGraphicsStats", "(ZJ)V"); + return jniRegisterNativeMethods(env, "android/graphics/GraphicsStatsService", sMethods, + NELEM(sMethods)); +} diff --git a/libs/hwui/jni/ImageDecoder.cpp b/libs/hwui/jni/ImageDecoder.cpp new file mode 100644 index 000000000000..b6b378539bd0 --- /dev/null +++ b/libs/hwui/jni/ImageDecoder.cpp @@ -0,0 +1,529 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Bitmap.h" +#include "BitmapFactory.h" +#include "ByteBufferStreamAdaptor.h" +#include "CreateJavaOutputStreamAdaptor.h" +#include "GraphicsJNI.h" +#include "ImageDecoder.h" +#include "NinePatchPeeker.h" +#include "Utils.h" + +#include <hwui/Bitmap.h> +#include <hwui/ImageDecoder.h> +#include <HardwareBitmapUploader.h> + +#include <SkAndroidCodec.h> +#include <SkEncodedImageFormat.h> +#include <SkFrontBufferedStream.h> +#include <SkStream.h> + +#include <androidfw/Asset.h> +#include <fcntl.h> +#include <sys/stat.h> + +using namespace android; + +static jclass gImageDecoder_class; +static jclass gSize_class; +static jclass gDecodeException_class; +static jclass gCanvas_class; +static jmethodID gImageDecoder_constructorMethodID; +static jmethodID gImageDecoder_postProcessMethodID; +static jmethodID gSize_constructorMethodID; +static jmethodID gDecodeException_constructorMethodID; +static jmethodID gCallback_onPartialImageMethodID; +static jmethodID gCanvas_constructorMethodID; +static jmethodID gCanvas_releaseMethodID; + +// These need to stay in sync with ImageDecoder.java's Allocator constants. +enum Allocator { + kDefault_Allocator = 0, + kSoftware_Allocator = 1, + kSharedMemory_Allocator = 2, + kHardware_Allocator = 3, +}; + +// These need to stay in sync with ImageDecoder.java's Error constants. +enum Error { + kSourceException = 1, + kSourceIncomplete = 2, + kSourceMalformedData = 3, +}; + +// These need to stay in sync with PixelFormat.java's Format constants. +enum PixelFormat { + kUnknown = 0, + kTranslucent = -3, + kOpaque = -1, +}; + +// Clear and return any pending exception for handling other than throwing directly. +static jthrowable get_and_clear_exception(JNIEnv* env) { + jthrowable jexception = env->ExceptionOccurred(); + if (jexception) { + env->ExceptionClear(); + } + return jexception; +} + +// Throw a new ImageDecoder.DecodeException. Returns null for convenience. +static jobject throw_exception(JNIEnv* env, Error error, const char* msg, + jthrowable cause, jobject source) { + jstring jstr = nullptr; + if (msg) { + jstr = env->NewStringUTF(msg); + if (!jstr) { + // Out of memory. + return nullptr; + } + } + jthrowable exception = (jthrowable) env->NewObject(gDecodeException_class, + gDecodeException_constructorMethodID, error, jstr, cause, source); + // Only throw if not out of memory. + if (exception) { + env->Throw(exception); + } + return nullptr; +} + +static jobject native_create(JNIEnv* env, std::unique_ptr<SkStream> stream, + jobject source, jboolean preferAnimation) { + if (!stream.get()) { + return throw_exception(env, kSourceMalformedData, "Failed to create a stream", + nullptr, source); + } + sk_sp<NinePatchPeeker> peeker(new NinePatchPeeker); + SkCodec::Result result; + auto codec = SkCodec::MakeFromStream( + std::move(stream), &result, peeker.get(), + preferAnimation ? SkCodec::SelectionPolicy::kPreferAnimation + : SkCodec::SelectionPolicy::kPreferStillImage); + if (jthrowable jexception = get_and_clear_exception(env)) { + return throw_exception(env, kSourceException, "", jexception, source); + } + if (!codec) { + switch (result) { + case SkCodec::kIncompleteInput: + return throw_exception(env, kSourceIncomplete, "", nullptr, source); + default: + SkString msg; + msg.printf("Failed to create image decoder with message '%s'", + SkCodec::ResultToString(result)); + return throw_exception(env, kSourceMalformedData, msg.c_str(), + nullptr, source); + + } + } + + const bool animated = codec->getFrameCount() > 1; + if (jthrowable jexception = get_and_clear_exception(env)) { + return throw_exception(env, kSourceException, "", jexception, source); + } + + auto androidCodec = SkAndroidCodec::MakeFromCodec(std::move(codec), + SkAndroidCodec::ExifOrientationBehavior::kRespect); + if (!androidCodec.get()) { + return throw_exception(env, kSourceMalformedData, "", nullptr, source); + } + + const auto& info = androidCodec->getInfo(); + const int width = info.width(); + const int height = info.height(); + const bool isNinePatch = peeker->mPatch != nullptr; + ImageDecoder* decoder = new ImageDecoder(std::move(androidCodec), std::move(peeker)); + return env->NewObject(gImageDecoder_class, gImageDecoder_constructorMethodID, + reinterpret_cast<jlong>(decoder), width, height, + animated, isNinePatch); +} + +static jobject ImageDecoder_nCreateFd(JNIEnv* env, jobject /*clazz*/, + jobject fileDescriptor, jboolean preferAnimation, jobject source) { +#ifndef __ANDROID__ // LayoutLib for Windows does not support F_DUPFD_CLOEXEC + return throw_exception(env, kSourceException, "Only supported on Android", nullptr, source); +#else + int descriptor = jniGetFDFromFileDescriptor(env, fileDescriptor); + + struct stat fdStat; + if (fstat(descriptor, &fdStat) == -1) { + return throw_exception(env, kSourceMalformedData, + "broken file descriptor; fstat returned -1", nullptr, source); + } + + int dupDescriptor = fcntl(descriptor, F_DUPFD_CLOEXEC, 0); + FILE* file = fdopen(dupDescriptor, "r"); + if (file == NULL) { + close(dupDescriptor); + return throw_exception(env, kSourceMalformedData, "Could not open file", + nullptr, source); + } + + std::unique_ptr<SkFILEStream> fileStream(new SkFILEStream(file)); + return native_create(env, std::move(fileStream), source, preferAnimation); +#endif +} + +static jobject ImageDecoder_nCreateInputStream(JNIEnv* env, jobject /*clazz*/, + jobject is, jbyteArray storage, jboolean preferAnimation, jobject source) { + std::unique_ptr<SkStream> stream(CreateJavaInputStreamAdaptor(env, is, storage, false)); + + if (!stream.get()) { + return throw_exception(env, kSourceMalformedData, "Failed to create a stream", + nullptr, source); + } + + std::unique_ptr<SkStream> bufferedStream( + SkFrontBufferedStream::Make(std::move(stream), + SkCodec::MinBufferedBytesNeeded())); + return native_create(env, std::move(bufferedStream), source, preferAnimation); +} + +static jobject ImageDecoder_nCreateAsset(JNIEnv* env, jobject /*clazz*/, + jlong assetPtr, jboolean preferAnimation, jobject source) { + Asset* asset = reinterpret_cast<Asset*>(assetPtr); + std::unique_ptr<SkStream> stream(new AssetStreamAdaptor(asset)); + return native_create(env, std::move(stream), source, preferAnimation); +} + +static jobject ImageDecoder_nCreateByteBuffer(JNIEnv* env, jobject /*clazz*/, + jobject jbyteBuffer, jint initialPosition, jint limit, + jboolean preferAnimation, jobject source) { + std::unique_ptr<SkStream> stream = CreateByteBufferStreamAdaptor(env, jbyteBuffer, + initialPosition, limit); + if (!stream) { + return throw_exception(env, kSourceMalformedData, "Failed to read ByteBuffer", + nullptr, source); + } + return native_create(env, std::move(stream), source, preferAnimation); +} + +static jobject ImageDecoder_nCreateByteArray(JNIEnv* env, jobject /*clazz*/, + jbyteArray byteArray, jint offset, jint length, + jboolean preferAnimation, jobject source) { + std::unique_ptr<SkStream> stream(CreateByteArrayStreamAdaptor(env, byteArray, offset, length)); + return native_create(env, std::move(stream), source, preferAnimation); +} + +jint postProcessAndRelease(JNIEnv* env, jobject jimageDecoder, std::unique_ptr<Canvas> canvas) { + jobject jcanvas = env->NewObject(gCanvas_class, gCanvas_constructorMethodID, + reinterpret_cast<jlong>(canvas.get())); + if (!jcanvas) { + doThrowOOME(env, "Failed to create Java Canvas for PostProcess!"); + return kUnknown; + } + + // jcanvas now owns canvas. + canvas.release(); + + return env->CallIntMethod(jimageDecoder, gImageDecoder_postProcessMethodID, jcanvas); +} + +static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, + jobject jdecoder, jboolean jpostProcess, + jint targetWidth, jint targetHeight, jobject jsubset, + jboolean requireMutable, jint allocator, + jboolean requireUnpremul, jboolean preferRamOverQuality, + jboolean asAlphaMask, jlong colorSpaceHandle, + jboolean extended) { + auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr); + if (!decoder->setTargetSize(targetWidth, targetHeight)) { + doThrowISE(env, "Could not scale to target size!"); + return nullptr; + } + if (requireUnpremul && !decoder->setUnpremultipliedRequired(true)) { + doThrowISE(env, "Cannot scale unpremultiplied pixels!"); + return nullptr; + } + + SkColorType colorType = kN32_SkColorType; + if (asAlphaMask && decoder->gray()) { + // We have to trick Skia to decode this to a single channel. + colorType = kGray_8_SkColorType; + } else if (preferRamOverQuality) { + // FIXME: The post-process might add alpha, which would make a 565 + // result incorrect. If we call the postProcess before now and record + // to a picture, we can know whether alpha was added, and if not, we + // can still use 565. + if (decoder->opaque() && !jpostProcess) { + // If the final result will be hardware, decoding to 565 and then + // uploading to the gpu as 8888 will not save memory. This still + // may save us from using F16, but do not go down to 565. + if (allocator != kHardware_Allocator && + (allocator != kDefault_Allocator || requireMutable)) { + colorType = kRGB_565_SkColorType; + } + } + // Otherwise, stick with N32 + } else if (extended) { + colorType = kRGBA_F16_SkColorType; + } else { + colorType = decoder->mCodec->computeOutputColorType(colorType); + } + + const bool isHardware = !requireMutable + && (allocator == kDefault_Allocator || + allocator == kHardware_Allocator) + && colorType != kGray_8_SkColorType; + + if (colorType == kRGBA_F16_SkColorType && isHardware && + !uirenderer::HardwareBitmapUploader::hasFP16Support()) { + colorType = kN32_SkColorType; + } + + if (!decoder->setOutColorType(colorType)) { + doThrowISE(env, "Failed to set out color type!"); + return nullptr; + } + + { + sk_sp<SkColorSpace> colorSpace = GraphicsJNI::getNativeColorSpace(colorSpaceHandle); + colorSpace = decoder->mCodec->computeOutputColorSpace(colorType, colorSpace); + decoder->setOutColorSpace(std::move(colorSpace)); + } + + if (jsubset) { + SkIRect subset; + GraphicsJNI::jrect_to_irect(env, jsubset, &subset); + if (!decoder->setCropRect(&subset)) { + doThrowISE(env, "Invalid crop rect!"); + return nullptr; + } + } + + SkImageInfo bitmapInfo = decoder->getOutputInfo(); + if (decoder->opaque()) { + bitmapInfo = bitmapInfo.makeAlphaType(kOpaque_SkAlphaType); + } + if (asAlphaMask && colorType == kGray_8_SkColorType) { + bitmapInfo = bitmapInfo.makeColorType(kAlpha_8_SkColorType); + } + + SkBitmap bm; + if (!bm.setInfo(bitmapInfo)) { + doThrowIOE(env, "Failed to setInfo properly"); + return nullptr; + } + + sk_sp<Bitmap> nativeBitmap; + if (allocator == kSharedMemory_Allocator) { + nativeBitmap = Bitmap::allocateAshmemBitmap(&bm); + } else { + nativeBitmap = Bitmap::allocateHeapBitmap(&bm); + } + if (!nativeBitmap) { + SkString msg; + msg.printf("OOM allocating Bitmap with dimensions %i x %i", + bitmapInfo.width(), bitmapInfo.height()); + doThrowOOME(env, msg.c_str()); + return nullptr; + } + + SkCodec::Result result = decoder->decode(bm.getPixels(), bm.rowBytes()); + jthrowable jexception = get_and_clear_exception(env); + int onPartialImageError = jexception ? kSourceException + : 0; // No error. + switch (result) { + case SkCodec::kSuccess: + // Ignore the exception, since the decode was successful anyway. + jexception = nullptr; + onPartialImageError = 0; + break; + case SkCodec::kIncompleteInput: + if (!jexception) { + onPartialImageError = kSourceIncomplete; + } + break; + case SkCodec::kErrorInInput: + if (!jexception) { + onPartialImageError = kSourceMalformedData; + } + break; + default: + SkString msg; + msg.printf("getPixels failed with error %s", SkCodec::ResultToString(result)); + doThrowIOE(env, msg.c_str()); + return nullptr; + } + + if (onPartialImageError) { + env->CallVoidMethod(jdecoder, gCallback_onPartialImageMethodID, onPartialImageError, + jexception); + if (env->ExceptionCheck()) { + return nullptr; + } + } + + jbyteArray ninePatchChunk = nullptr; + jobject ninePatchInsets = nullptr; + + // Ignore ninepatch when post-processing. + if (!jpostProcess) { + // FIXME: Share more code with BitmapFactory.cpp. + auto* peeker = reinterpret_cast<NinePatchPeeker*>(decoder->mPeeker.get()); + if (peeker->mPatch != nullptr) { + size_t ninePatchArraySize = peeker->mPatch->serializedSize(); + ninePatchChunk = env->NewByteArray(ninePatchArraySize); + if (ninePatchChunk == nullptr) { + doThrowOOME(env, "Failed to allocate nine patch chunk."); + return nullptr; + } + + env->SetByteArrayRegion(ninePatchChunk, 0, peeker->mPatchSize, + reinterpret_cast<jbyte*>(peeker->mPatch)); + } + + if (peeker->mHasInsets) { + ninePatchInsets = peeker->createNinePatchInsets(env, 1.0f); + if (ninePatchInsets == nullptr) { + doThrowOOME(env, "Failed to allocate nine patch insets."); + return nullptr; + } + } + } + + if (jpostProcess) { + std::unique_ptr<Canvas> canvas(Canvas::create_canvas(bm)); + + jint pixelFormat = postProcessAndRelease(env, jdecoder, std::move(canvas)); + if (env->ExceptionCheck()) { + return nullptr; + } + + SkAlphaType newAlphaType = bm.alphaType(); + switch (pixelFormat) { + case kUnknown: + break; + case kTranslucent: + newAlphaType = kPremul_SkAlphaType; + break; + case kOpaque: + newAlphaType = kOpaque_SkAlphaType; + break; + default: + SkString msg; + msg.printf("invalid return from postProcess: %i", pixelFormat); + doThrowIAE(env, msg.c_str()); + return nullptr; + } + + if (newAlphaType != bm.alphaType()) { + if (!bm.setAlphaType(newAlphaType)) { + SkString msg; + msg.printf("incompatible return from postProcess: %i", pixelFormat); + doThrowIAE(env, msg.c_str()); + return nullptr; + } + nativeBitmap->setAlphaType(newAlphaType); + } + } + + int bitmapCreateFlags = 0x0; + if (!requireUnpremul) { + // Even if the image is opaque, setting this flag means that + // if alpha is added (e.g. by PostProcess), it will be marked as + // premultiplied. + bitmapCreateFlags |= bitmap::kBitmapCreateFlag_Premultiplied; + } + + if (requireMutable) { + bitmapCreateFlags |= bitmap::kBitmapCreateFlag_Mutable; + } else { + if (isHardware) { + sk_sp<Bitmap> hwBitmap = Bitmap::allocateHardwareBitmap(bm); + if (hwBitmap) { + hwBitmap->setImmutable(); + return bitmap::createBitmap(env, hwBitmap.release(), bitmapCreateFlags, + ninePatchChunk, ninePatchInsets); + } + if (allocator == kHardware_Allocator) { + doThrowOOME(env, "failed to allocate hardware Bitmap!"); + return nullptr; + } + // If we failed to create a hardware bitmap, go ahead and create a + // software one. + } + + nativeBitmap->setImmutable(); + } + return bitmap::createBitmap(env, nativeBitmap.release(), bitmapCreateFlags, ninePatchChunk, + ninePatchInsets); +} + +static jobject ImageDecoder_nGetSampledSize(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, + jint sampleSize) { + auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr); + SkISize size = decoder->mCodec->getSampledDimensions(sampleSize); + return env->NewObject(gSize_class, gSize_constructorMethodID, size.width(), size.height()); +} + +static void ImageDecoder_nGetPadding(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, + jobject outPadding) { + auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr); + reinterpret_cast<NinePatchPeeker*>(decoder->mPeeker.get())->getPadding(env, outPadding); +} + +static void ImageDecoder_nClose(JNIEnv* /*env*/, jobject /*clazz*/, jlong nativePtr) { + delete reinterpret_cast<ImageDecoder*>(nativePtr); +} + +static jstring ImageDecoder_nGetMimeType(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { + auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr); + return getMimeTypeAsJavaString(env, decoder->mCodec->getEncodedFormat()); +} + +static jobject ImageDecoder_nGetColorSpace(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { + auto* codec = reinterpret_cast<ImageDecoder*>(nativePtr)->mCodec.get(); + auto colorType = codec->computeOutputColorType(kN32_SkColorType); + sk_sp<SkColorSpace> colorSpace = codec->computeOutputColorSpace(colorType); + return GraphicsJNI::getColorSpace(env, colorSpace.get(), colorType); +} + +static const JNINativeMethod gImageDecoderMethods[] = { + { "nCreate", "(JZLandroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateAsset }, + { "nCreate", "(Ljava/nio/ByteBuffer;IIZLandroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateByteBuffer }, + { "nCreate", "([BIIZLandroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateByteArray }, + { "nCreate", "(Ljava/io/InputStream;[BZLandroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateInputStream }, + { "nCreate", "(Ljava/io/FileDescriptor;ZLandroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateFd }, + { "nDecodeBitmap", "(JLandroid/graphics/ImageDecoder;ZIILandroid/graphics/Rect;ZIZZZJZ)Landroid/graphics/Bitmap;", + (void*) ImageDecoder_nDecodeBitmap }, + { "nGetSampledSize","(JI)Landroid/util/Size;", (void*) ImageDecoder_nGetSampledSize }, + { "nGetPadding", "(JLandroid/graphics/Rect;)V", (void*) ImageDecoder_nGetPadding }, + { "nClose", "(J)V", (void*) ImageDecoder_nClose}, + { "nGetMimeType", "(J)Ljava/lang/String;", (void*) ImageDecoder_nGetMimeType }, + { "nGetColorSpace", "(J)Landroid/graphics/ColorSpace;", (void*) ImageDecoder_nGetColorSpace }, +}; + +int register_android_graphics_ImageDecoder(JNIEnv* env) { + gImageDecoder_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ImageDecoder")); + gImageDecoder_constructorMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "<init>", "(JIIZZ)V"); + gImageDecoder_postProcessMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "postProcessAndRelease", "(Landroid/graphics/Canvas;)I"); + + gSize_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/util/Size")); + gSize_constructorMethodID = GetMethodIDOrDie(env, gSize_class, "<init>", "(II)V"); + + gDecodeException_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ImageDecoder$DecodeException")); + gDecodeException_constructorMethodID = GetMethodIDOrDie(env, gDecodeException_class, "<init>", "(ILjava/lang/String;Ljava/lang/Throwable;Landroid/graphics/ImageDecoder$Source;)V"); + + gCallback_onPartialImageMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "onPartialImage", "(ILjava/lang/Throwable;)V"); + + gCanvas_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Canvas")); + gCanvas_constructorMethodID = GetMethodIDOrDie(env, gCanvas_class, "<init>", "(J)V"); + gCanvas_releaseMethodID = GetMethodIDOrDie(env, gCanvas_class, "release", "()V"); + + return android::RegisterMethodsOrDie(env, "android/graphics/ImageDecoder", gImageDecoderMethods, + NELEM(gImageDecoderMethods)); +} diff --git a/libs/hwui/jni/ImageDecoder.h b/libs/hwui/jni/ImageDecoder.h new file mode 100644 index 000000000000..8a7fa79503ba --- /dev/null +++ b/libs/hwui/jni/ImageDecoder.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <hwui/Canvas.h> + +#include <jni.h> + +// Creates a Java Canvas object from canvas, calls jimageDecoder's PostProcess on it, and then +// releases the Canvas. +// Caller needs to check for exceptions. +jint postProcessAndRelease(JNIEnv* env, jobject jimageDecoder, + std::unique_ptr<android::Canvas> canvas); diff --git a/libs/hwui/jni/Interpolator.cpp b/libs/hwui/jni/Interpolator.cpp new file mode 100644 index 000000000000..146d634a297c --- /dev/null +++ b/libs/hwui/jni/Interpolator.cpp @@ -0,0 +1,84 @@ +#include "GraphicsJNI.h" +#include "SkInterpolator.h" + +static jlong Interpolator_constructor(JNIEnv* env, jobject clazz, jint valueCount, jint frameCount) +{ + return reinterpret_cast<jlong>(new SkInterpolator(valueCount, frameCount)); +} + +static void Interpolator_destructor(JNIEnv* env, jobject clazz, jlong interpHandle) +{ + SkInterpolator* interp = reinterpret_cast<SkInterpolator*>(interpHandle); + delete interp; +} + +static void Interpolator_reset(JNIEnv* env, jobject clazz, jlong interpHandle, jint valueCount, jint frameCount) +{ + SkInterpolator* interp = reinterpret_cast<SkInterpolator*>(interpHandle); + interp->reset(valueCount, frameCount); +} + +static void Interpolator_setKeyFrame(JNIEnv* env, jobject clazz, jlong interpHandle, jint index, jint msec, jfloatArray valueArray, jfloatArray blendArray) +{ + SkInterpolator* interp = reinterpret_cast<SkInterpolator*>(interpHandle); + + AutoJavaFloatArray autoValues(env, valueArray); + AutoJavaFloatArray autoBlend(env, blendArray, 4); +#ifdef SK_SCALAR_IS_FLOAT + SkScalar* scalars = autoValues.ptr(); + SkScalar* blend = autoBlend.ptr(); +#else + #error Need to convert float array to SkScalar array before calling the following function. +#endif + + interp->setKeyFrame(index, msec, scalars, blend); +} + +static void Interpolator_setRepeatMirror(JNIEnv* env, jobject clazz, jlong interpHandle, jfloat repeatCount, jboolean mirror) +{ + SkInterpolator* interp = reinterpret_cast<SkInterpolator*>(interpHandle); + if (repeatCount > 32000) + repeatCount = 32000; + + interp->setRepeatCount(repeatCount); + interp->setMirror(mirror != 0); +} + +static jint Interpolator_timeToValues(JNIEnv* env, jobject clazz, jlong interpHandle, jint msec, jfloatArray valueArray) +{ + SkInterpolator* interp = reinterpret_cast<SkInterpolator*>(interpHandle); + SkInterpolatorBase::Result result; + + float* values = valueArray ? env->GetFloatArrayElements(valueArray, NULL) : NULL; + result = interp->timeToValues(msec, (SkScalar*)values); + + if (valueArray) { + int n = env->GetArrayLength(valueArray); + for (int i = 0; i < n; i++) { + values[i] = SkScalarToFloat(*(SkScalar*)&values[i]); + } + env->ReleaseFloatArrayElements(valueArray, values, 0); + } + + return static_cast<jint>(result); +} + +// ---------------------------------------------------------------------------- + +/* + * JNI registration. + */ +static const JNINativeMethod gInterpolatorMethods[] = { + { "nativeConstructor", "(II)J", (void*)Interpolator_constructor }, + { "nativeDestructor", "(J)V", (void*)Interpolator_destructor }, + { "nativeReset", "(JII)V", (void*)Interpolator_reset }, + { "nativeSetKeyFrame", "(JII[F[F)V", (void*)Interpolator_setKeyFrame }, + { "nativeSetRepeatMirror", "(JFZ)V", (void*)Interpolator_setRepeatMirror }, + { "nativeTimeToValues", "(JI[F)I", (void*)Interpolator_timeToValues } +}; + +int register_android_graphics_Interpolator(JNIEnv* env) +{ + return android::RegisterMethodsOrDie(env, "android/graphics/Interpolator", + gInterpolatorMethods, NELEM(gInterpolatorMethods)); +} diff --git a/libs/hwui/jni/MaskFilter.cpp b/libs/hwui/jni/MaskFilter.cpp new file mode 100644 index 000000000000..5383032e0f77 --- /dev/null +++ b/libs/hwui/jni/MaskFilter.cpp @@ -0,0 +1,90 @@ +#include "GraphicsJNI.h" +#include "SkMaskFilter.h" +#include "SkBlurMask.h" +#include "SkBlurMaskFilter.h" +#include "SkTableMaskFilter.h" + +static void ThrowIAE_IfNull(JNIEnv* env, void* ptr) { + if (NULL == ptr) { + doThrowIAE(env); + } +} + +class SkMaskFilterGlue { +public: + static void destructor(JNIEnv* env, jobject, jlong filterHandle) { + SkMaskFilter* filter = reinterpret_cast<SkMaskFilter *>(filterHandle); + SkSafeUnref(filter); + } + + static jlong createBlur(JNIEnv* env, jobject, jfloat radius, jint blurStyle) { + SkScalar sigma = SkBlurMask::ConvertRadiusToSigma(radius); + SkMaskFilter* filter = SkMaskFilter::MakeBlur((SkBlurStyle)blurStyle, sigma).release(); + ThrowIAE_IfNull(env, filter); + return reinterpret_cast<jlong>(filter); + } + + static jlong createEmboss(JNIEnv* env, jobject, jfloatArray dirArray, jfloat ambient, jfloat specular, jfloat radius) { + SkScalar direction[3]; + + AutoJavaFloatArray autoDir(env, dirArray, 3); + float* values = autoDir.ptr(); + for (int i = 0; i < 3; i++) { + direction[i] = values[i]; + } + + SkScalar sigma = SkBlurMask::ConvertRadiusToSigma(radius); + SkMaskFilter* filter = SkBlurMaskFilter::MakeEmboss(sigma, + direction, ambient, specular).release(); + ThrowIAE_IfNull(env, filter); + return reinterpret_cast<jlong>(filter); + } + + static jlong createTable(JNIEnv* env, jobject, jbyteArray jtable) { + AutoJavaByteArray autoTable(env, jtable, 256); + SkMaskFilter* filter = SkTableMaskFilter::Create((const uint8_t*)autoTable.ptr()); + return reinterpret_cast<jlong>(filter); + } + + static jlong createClipTable(JNIEnv* env, jobject, jint min, jint max) { + SkMaskFilter* filter = SkTableMaskFilter::CreateClip(min, max); + return reinterpret_cast<jlong>(filter); + } + + static jlong createGammaTable(JNIEnv* env, jobject, jfloat gamma) { + SkMaskFilter* filter = SkTableMaskFilter::CreateGamma(gamma); + return reinterpret_cast<jlong>(filter); + } +}; + +static const JNINativeMethod gMaskFilterMethods[] = { + { "nativeDestructor", "(J)V", (void*)SkMaskFilterGlue::destructor } +}; + +static const JNINativeMethod gBlurMaskFilterMethods[] = { + { "nativeConstructor", "(FI)J", (void*)SkMaskFilterGlue::createBlur } +}; + +static const JNINativeMethod gEmbossMaskFilterMethods[] = { + { "nativeConstructor", "([FFFF)J", (void*)SkMaskFilterGlue::createEmboss } +}; + +static const JNINativeMethod gTableMaskFilterMethods[] = { + { "nativeNewTable", "([B)J", (void*)SkMaskFilterGlue::createTable }, + { "nativeNewClip", "(II)J", (void*)SkMaskFilterGlue::createClipTable }, + { "nativeNewGamma", "(F)J", (void*)SkMaskFilterGlue::createGammaTable } +}; + +int register_android_graphics_MaskFilter(JNIEnv* env) +{ + android::RegisterMethodsOrDie(env, "android/graphics/MaskFilter", gMaskFilterMethods, + NELEM(gMaskFilterMethods)); + android::RegisterMethodsOrDie(env, "android/graphics/BlurMaskFilter", gBlurMaskFilterMethods, + NELEM(gBlurMaskFilterMethods)); + android::RegisterMethodsOrDie(env, "android/graphics/EmbossMaskFilter", + gEmbossMaskFilterMethods, NELEM(gEmbossMaskFilterMethods)); + android::RegisterMethodsOrDie(env, "android/graphics/TableMaskFilter", gTableMaskFilterMethods, + NELEM(gTableMaskFilterMethods)); + + return 0; +} diff --git a/libs/hwui/jni/MimeType.h b/libs/hwui/jni/MimeType.h new file mode 100644 index 000000000000..fdd510cfeb79 --- /dev/null +++ b/libs/hwui/jni/MimeType.h @@ -0,0 +1,22 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <cutils/compiler.h> +#include "SkEncodedImageFormat.h" + +ANDROID_API const char* getMimeType(SkEncodedImageFormat); diff --git a/libs/hwui/jni/Movie.cpp b/libs/hwui/jni/Movie.cpp new file mode 100644 index 000000000000..ede0ca8cda5b --- /dev/null +++ b/libs/hwui/jni/Movie.cpp @@ -0,0 +1,164 @@ +#include "CreateJavaOutputStreamAdaptor.h" +#include "GraphicsJNI.h" +#include <nativehelper/ScopedLocalRef.h> +#include "SkFrontBufferedStream.h" +#include "Movie.h" +#include "SkStream.h" +#include "SkUtils.h" +#include "Utils.h" + +#include <androidfw/Asset.h> +#include <androidfw/ResourceTypes.h> +#include <hwui/Canvas.h> +#include <hwui/Paint.h> +#include <netinet/in.h> + +static jclass gMovie_class; +static jmethodID gMovie_constructorMethodID; +static jfieldID gMovie_nativeInstanceID; + +jobject create_jmovie(JNIEnv* env, Movie* moov) { + if (NULL == moov) { + return NULL; + } + return env->NewObject(gMovie_class, gMovie_constructorMethodID, + static_cast<jlong>(reinterpret_cast<uintptr_t>(moov))); +} + +static Movie* J2Movie(JNIEnv* env, jobject movie) { + SkASSERT(env); + SkASSERT(movie); + SkASSERT(env->IsInstanceOf(movie, gMovie_class)); + Movie* m = (Movie*)env->GetLongField(movie, gMovie_nativeInstanceID); + SkASSERT(m); + return m; +} + +/////////////////////////////////////////////////////////////////////////////// + +static jint movie_width(JNIEnv* env, jobject movie) { + NPE_CHECK_RETURN_ZERO(env, movie); + return static_cast<jint>(J2Movie(env, movie)->width()); +} + +static jint movie_height(JNIEnv* env, jobject movie) { + NPE_CHECK_RETURN_ZERO(env, movie); + return static_cast<jint>(J2Movie(env, movie)->height()); +} + +static jboolean movie_isOpaque(JNIEnv* env, jobject movie) { + NPE_CHECK_RETURN_ZERO(env, movie); + return J2Movie(env, movie)->isOpaque() ? JNI_TRUE : JNI_FALSE; +} + +static jint movie_duration(JNIEnv* env, jobject movie) { + NPE_CHECK_RETURN_ZERO(env, movie); + return static_cast<jint>(J2Movie(env, movie)->duration()); +} + +static jboolean movie_setTime(JNIEnv* env, jobject movie, jint ms) { + NPE_CHECK_RETURN_ZERO(env, movie); + return J2Movie(env, movie)->setTime(ms) ? JNI_TRUE : JNI_FALSE; +} + +static void movie_draw(JNIEnv* env, jobject movie, jlong canvasHandle, + jfloat fx, jfloat fy, jlong paintHandle) { + NPE_CHECK_RETURN_VOID(env, movie); + + android::Canvas* c = reinterpret_cast<android::Canvas*>(canvasHandle); + const android::Paint* p = reinterpret_cast<android::Paint*>(paintHandle); + + // Canvas should never be NULL. However paint is an optional parameter and + // therefore may be NULL. + SkASSERT(c != NULL); + + Movie* m = J2Movie(env, movie); + const SkBitmap& b = m->bitmap(); + sk_sp<android::Bitmap> wrapper = android::Bitmap::createFrom(b.info(), *b.pixelRef()); + c->drawBitmap(*wrapper, fx, fy, p); +} + +static jobject movie_decodeAsset(JNIEnv* env, jobject clazz, jlong native_asset) { + android::Asset* asset = reinterpret_cast<android::Asset*>(native_asset); + if (asset == NULL) return NULL; + android::AssetStreamAdaptor stream(asset); + Movie* moov = Movie::DecodeStream(&stream); + return create_jmovie(env, moov); +} + +static jobject movie_decodeStream(JNIEnv* env, jobject clazz, jobject istream) { + + NPE_CHECK_RETURN_ZERO(env, istream); + + jbyteArray byteArray = env->NewByteArray(16*1024); + ScopedLocalRef<jbyteArray> scoper(env, byteArray); + SkStream* strm = CreateJavaInputStreamAdaptor(env, istream, byteArray); + if (NULL == strm) { + return 0; + } + + // Need to buffer enough input to be able to rewind as much as might be read by a decoder + // trying to determine the stream's format. The only decoder for movies is GIF, which + // will only read 6. + // FIXME: Get this number from SkImageDecoder + // bufferedStream takes ownership of strm + std::unique_ptr<SkStreamRewindable> bufferedStream(SkFrontBufferedStream::Make( + std::unique_ptr<SkStream>(strm), 6)); + SkASSERT(bufferedStream.get() != NULL); + + Movie* moov = Movie::DecodeStream(bufferedStream.get()); + return create_jmovie(env, moov); +} + +static jobject movie_decodeByteArray(JNIEnv* env, jobject clazz, + jbyteArray byteArray, + jint offset, jint length) { + + NPE_CHECK_RETURN_ZERO(env, byteArray); + + int totalLength = env->GetArrayLength(byteArray); + if ((offset | length) < 0 || offset + length > totalLength) { + doThrowAIOOBE(env); + return 0; + } + + AutoJavaByteArray ar(env, byteArray); + Movie* moov = Movie::DecodeMemory(ar.ptr() + offset, length); + return create_jmovie(env, moov); +} + +static void movie_destructor(JNIEnv* env, jobject, jlong movieHandle) { + Movie* movie = (Movie*) movieHandle; + delete movie; +} + +////////////////////////////////////////////////////////////////////////////////////////////// + +static const JNINativeMethod gMethods[] = { + { "width", "()I", (void*)movie_width }, + { "height", "()I", (void*)movie_height }, + { "isOpaque", "()Z", (void*)movie_isOpaque }, + { "duration", "()I", (void*)movie_duration }, + { "setTime", "(I)Z", (void*)movie_setTime }, + { "nDraw", "(JFFJ)V", + (void*)movie_draw }, + { "nativeDecodeAsset", "(J)Landroid/graphics/Movie;", + (void*)movie_decodeAsset }, + { "nativeDecodeStream", "(Ljava/io/InputStream;)Landroid/graphics/Movie;", + (void*)movie_decodeStream }, + { "nativeDestructor","(J)V", (void*)movie_destructor }, + { "decodeByteArray", "([BII)Landroid/graphics/Movie;", + (void*)movie_decodeByteArray }, +}; + +int register_android_graphics_Movie(JNIEnv* env) +{ + gMovie_class = android::FindClassOrDie(env, "android/graphics/Movie"); + gMovie_class = android::MakeGlobalRefOrDie(env, gMovie_class); + + gMovie_constructorMethodID = android::GetMethodIDOrDie(env, gMovie_class, "<init>", "(J)V"); + + gMovie_nativeInstanceID = android::GetFieldIDOrDie(env, gMovie_class, "mNativeMovie", "J"); + + return android::RegisterMethodsOrDie(env, "android/graphics/Movie", gMethods, NELEM(gMethods)); +} diff --git a/libs/hwui/jni/Movie.h b/libs/hwui/jni/Movie.h new file mode 100644 index 000000000000..736890d5215e --- /dev/null +++ b/libs/hwui/jni/Movie.h @@ -0,0 +1,79 @@ + +/* + * Copyright 2008 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef Movie_DEFINED +#define Movie_DEFINED + +#include "SkBitmap.h" +#include "SkCanvas.h" +#include "SkRefCnt.h" + +class SkStreamRewindable; + +class Movie : public SkRefCnt { +public: + /** Try to create a movie from the stream. If the stream format is not + supported, return NULL. + */ + static Movie* DecodeStream(SkStreamRewindable*); + /** Try to create a movie from the specified file path. If the file is not + found, or the format is not supported, return NULL. If a movie is + returned, the stream may be retained by the movie (via ref()) until + the movie is finished with it (by calling unref()). + */ + static Movie* DecodeFile(const char path[]); + /** Try to create a movie from the specified memory. + If the format is not supported, return NULL. If a movie is returned, + the data will have been read or copied, and so the caller may free + it. + */ + static Movie* DecodeMemory(const void* data, size_t length); + + SkMSec duration(); + int width(); + int height(); + int isOpaque(); + + /** Specify the time code (between 0...duration) to sample a bitmap + from the movie. Returns true if this time code generated a different + bitmap/frame from the previous state (i.e. true means you need to + redraw). + */ + bool setTime(SkMSec); + + // return the right bitmap for the current time code + const SkBitmap& bitmap(); + +protected: + struct Info { + SkMSec fDuration; + int fWidth; + int fHeight; + bool fIsOpaque; + }; + + virtual bool onGetInfo(Info*) = 0; + virtual bool onSetTime(SkMSec) = 0; + virtual bool onGetBitmap(SkBitmap*) = 0; + + // visible for subclasses + Movie(); + +private: + Info fInfo; + SkMSec fCurrTime; + SkBitmap fBitmap; + bool fNeedBitmap; + + void ensureInfo(); + + typedef SkRefCnt INHERITED; +}; + +#endif diff --git a/libs/hwui/jni/MovieImpl.cpp b/libs/hwui/jni/MovieImpl.cpp new file mode 100644 index 000000000000..ae9e04e617b0 --- /dev/null +++ b/libs/hwui/jni/MovieImpl.cpp @@ -0,0 +1,94 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "Movie.h" +#include "SkCanvas.h" +#include "SkPaint.h" + +// We should never see this in normal operation since our time values are +// 0-based. So we use it as a sentinal. +#define UNINITIALIZED_MSEC ((SkMSec)-1) + +Movie::Movie() +{ + fInfo.fDuration = UNINITIALIZED_MSEC; // uninitialized + fCurrTime = UNINITIALIZED_MSEC; // uninitialized + fNeedBitmap = true; +} + +void Movie::ensureInfo() +{ + if (fInfo.fDuration == UNINITIALIZED_MSEC && !this->onGetInfo(&fInfo)) + memset(&fInfo, 0, sizeof(fInfo)); // failure +} + +SkMSec Movie::duration() +{ + this->ensureInfo(); + return fInfo.fDuration; +} + +int Movie::width() +{ + this->ensureInfo(); + return fInfo.fWidth; +} + +int Movie::height() +{ + this->ensureInfo(); + return fInfo.fHeight; +} + +int Movie::isOpaque() +{ + this->ensureInfo(); + return fInfo.fIsOpaque; +} + +bool Movie::setTime(SkMSec time) +{ + SkMSec dur = this->duration(); + if (time > dur) + time = dur; + + bool changed = false; + if (time != fCurrTime) + { + fCurrTime = time; + changed = this->onSetTime(time); + fNeedBitmap |= changed; + } + return changed; +} + +const SkBitmap& Movie::bitmap() +{ + if (fCurrTime == UNINITIALIZED_MSEC) // uninitialized + this->setTime(0); + + if (fNeedBitmap) + { + if (!this->onGetBitmap(&fBitmap)) // failure + fBitmap.reset(); + fNeedBitmap = false; + } + return fBitmap; +} + +//////////////////////////////////////////////////////////////////// + +#include "SkStream.h" + +Movie* Movie::DecodeMemory(const void* data, size_t length) { + SkMemoryStream stream(data, length, false); + return Movie::DecodeStream(&stream); +} + +Movie* Movie::DecodeFile(const char path[]) { + std::unique_ptr<SkStreamRewindable> stream = SkStream::MakeFromFile(path); + return stream ? Movie::DecodeStream(stream.get()) : nullptr; +} diff --git a/libs/hwui/jni/NinePatch.cpp b/libs/hwui/jni/NinePatch.cpp new file mode 100644 index 000000000000..6942017d5f27 --- /dev/null +++ b/libs/hwui/jni/NinePatch.cpp @@ -0,0 +1,168 @@ +/* +** +** Copyright 2006, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#undef LOG_TAG +#define LOG_TAG "9patch" +#define LOG_NDEBUG 1 + +#include <androidfw/ResourceTypes.h> +#include <hwui/Canvas.h> +#include <hwui/Paint.h> +#include <utils/Log.h> + +#include "SkCanvas.h" +#include "SkLatticeIter.h" +#include "SkRegion.h" +#include "GraphicsJNI.h" +#include "NinePatchPeeker.h" +#include "NinePatchUtils.h" + +jclass gInsetStruct_class; +jmethodID gInsetStruct_constructorMethodID; + +using namespace android; + +/** + * IMPORTANT NOTE: 9patch chunks can be manipuated either as an array of bytes + * or as a Res_png_9patch instance. It is important to note that the size of the + * array required to hold a 9patch chunk is greater than sizeof(Res_png_9patch). + * The code below manipulates chunks as Res_png_9patch* types to draw and as + * int8_t* to allocate and free the backing storage. + */ + +class SkNinePatchGlue { +public: + static jboolean isNinePatchChunk(JNIEnv* env, jobject, jbyteArray obj) { + if (NULL == obj) { + return JNI_FALSE; + } + if (env->GetArrayLength(obj) < (int)sizeof(Res_png_9patch)) { + return JNI_FALSE; + } + const jbyte* array = env->GetByteArrayElements(obj, 0); + if (array != NULL) { + const Res_png_9patch* chunk = reinterpret_cast<const Res_png_9patch*>(array); + int8_t wasDeserialized = chunk->wasDeserialized; + env->ReleaseByteArrayElements(obj, const_cast<jbyte*>(array), JNI_ABORT); + return (wasDeserialized != -1) ? JNI_TRUE : JNI_FALSE; + } + return JNI_FALSE; + } + + static jlong validateNinePatchChunk(JNIEnv* env, jobject, jbyteArray obj) { + size_t chunkSize = env->GetArrayLength(obj); + if (chunkSize < (int) (sizeof(Res_png_9patch))) { + jniThrowRuntimeException(env, "Array too small for chunk."); + return NULL; + } + + int8_t* storage = new int8_t[chunkSize]; + // This call copies the content of the jbyteArray + env->GetByteArrayRegion(obj, 0, chunkSize, reinterpret_cast<jbyte*>(storage)); + // Deserialize in place, return the array we just allocated + return reinterpret_cast<jlong>(Res_png_9patch::deserialize(storage)); + } + + static void finalize(JNIEnv* env, jobject, jlong patchHandle) { + int8_t* patch = reinterpret_cast<int8_t*>(patchHandle); + delete[] patch; + } + + static jlong getTransparentRegion(JNIEnv* env, jobject, jlong bitmapPtr, + jlong chunkHandle, jobject dstRect) { + Res_png_9patch* chunk = reinterpret_cast<Res_png_9patch*>(chunkHandle); + SkASSERT(chunk); + + SkBitmap bitmap; + bitmap::toBitmap(bitmapPtr).getSkBitmap(&bitmap); + SkRect dst; + GraphicsJNI::jrect_to_rect(env, dstRect, &dst); + + SkCanvas::Lattice lattice; + SkIRect src = SkIRect::MakeWH(bitmap.width(), bitmap.height()); + lattice.fBounds = &src; + NinePatchUtils::SetLatticeDivs(&lattice, *chunk, bitmap.width(), bitmap.height()); + lattice.fRectTypes = nullptr; + lattice.fColors = nullptr; + + SkRegion* region = nullptr; + if (SkLatticeIter::Valid(bitmap.width(), bitmap.height(), lattice)) { + SkLatticeIter iter(lattice, dst); + if (iter.numRectsToDraw() == chunk->numColors) { + SkRect dummy; + SkRect iterDst; + int index = 0; + while (iter.next(&dummy, &iterDst)) { + if (0 == chunk->getColors()[index++] && !iterDst.isEmpty()) { + if (!region) { + region = new SkRegion(); + } + + region->op(iterDst.round(), SkRegion::kUnion_Op); + } + } + } + } + + return reinterpret_cast<jlong>(region); + } + +}; + +jobject NinePatchPeeker::createNinePatchInsets(JNIEnv* env, float scale) const { + if (!mHasInsets) { + return nullptr; + } + + return env->NewObject(gInsetStruct_class, gInsetStruct_constructorMethodID, + mOpticalInsets[0], mOpticalInsets[1], + mOpticalInsets[2], mOpticalInsets[3], + mOutlineInsets[0], mOutlineInsets[1], + mOutlineInsets[2], mOutlineInsets[3], + mOutlineRadius, mOutlineAlpha, scale); +} + +void NinePatchPeeker::getPadding(JNIEnv* env, jobject outPadding) const { + if (mPatch) { + GraphicsJNI::set_jrect(env, outPadding, + mPatch->paddingLeft, mPatch->paddingTop, + mPatch->paddingRight, mPatch->paddingBottom); + + } else { + GraphicsJNI::set_jrect(env, outPadding, -1, -1, -1, -1); + } +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static const JNINativeMethod gNinePatchMethods[] = { + { "isNinePatchChunk", "([B)Z", (void*) SkNinePatchGlue::isNinePatchChunk }, + { "validateNinePatchChunk", "([B)J", + (void*) SkNinePatchGlue::validateNinePatchChunk }, + { "nativeFinalize", "(J)V", (void*) SkNinePatchGlue::finalize }, + { "nativeGetTransparentRegion", "(JJLandroid/graphics/Rect;)J", + (void*) SkNinePatchGlue::getTransparentRegion } +}; + +int register_android_graphics_NinePatch(JNIEnv* env) { + gInsetStruct_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, + "android/graphics/NinePatch$InsetStruct")); + gInsetStruct_constructorMethodID = GetMethodIDOrDie(env, gInsetStruct_class, "<init>", + "(IIIIIIIIFIF)V"); + return android::RegisterMethodsOrDie(env, + "android/graphics/NinePatch", gNinePatchMethods, NELEM(gNinePatchMethods)); +} diff --git a/libs/hwui/jni/NinePatchPeeker.cpp b/libs/hwui/jni/NinePatchPeeker.cpp new file mode 100644 index 000000000000..9171fc687276 --- /dev/null +++ b/libs/hwui/jni/NinePatchPeeker.cpp @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "NinePatchPeeker.h" + +#include <SkBitmap.h> +#include <cutils/compiler.h> + +using namespace android; + +bool NinePatchPeeker::readChunk(const char tag[], const void* data, size_t length) { + if (!strcmp("npTc", tag) && length >= sizeof(Res_png_9patch)) { + Res_png_9patch* patch = (Res_png_9patch*) data; + size_t patchSize = patch->serializedSize(); + if (length != patchSize) { + return false; + } + // You have to copy the data because it is owned by the png reader + Res_png_9patch* patchNew = (Res_png_9patch*) malloc(patchSize); + memcpy(patchNew, patch, patchSize); + Res_png_9patch::deserialize(patchNew); + patchNew->fileToDevice(); + free(mPatch); + mPatch = patchNew; + mPatchSize = patchSize; + } else if (!strcmp("npLb", tag) && length == sizeof(int32_t) * 4) { + mHasInsets = true; + memcpy(&mOpticalInsets, data, sizeof(int32_t) * 4); + } else if (!strcmp("npOl", tag) && length == 24) { // 4 int32_ts, 1 float, 1 int32_t sized byte + mHasInsets = true; + memcpy(&mOutlineInsets, data, sizeof(int32_t) * 4); + mOutlineRadius = ((const float*)data)[4]; + mOutlineAlpha = ((const int32_t*)data)[5] & 0xff; + } + return true; // keep on decoding +} + +static void scaleDivRange(int32_t* divs, int count, float scale, int maxValue) { + for (int i = 0; i < count; i++) { + divs[i] = int32_t(divs[i] * scale + 0.5f); + if (i > 0 && divs[i] == divs[i - 1]) { + divs[i]++; // avoid collisions + } + } + + if (CC_UNLIKELY(divs[count - 1] > maxValue)) { + // if the collision avoidance above put some divs outside the bounds of the bitmap, + // slide outer stretchable divs inward to stay within bounds + int highestAvailable = maxValue; + for (int i = count - 1; i >= 0; i--) { + divs[i] = highestAvailable; + if (i > 0 && divs[i] <= divs[i-1]) { + // keep shifting + highestAvailable = divs[i] - 1; + } else { + break; + } + } + } +} + +void NinePatchPeeker::scale(float scaleX, float scaleY, int scaledWidth, int scaledHeight) { + if (!mPatch) { + return; + } + + // The max value for the divRange is one pixel less than the actual max to ensure that the size + // of the last div is not zero. A div of size 0 is considered invalid input and will not render. + if (!SkScalarNearlyEqual(scaleX, 1.0f)) { + mPatch->paddingLeft = int(mPatch->paddingLeft * scaleX + 0.5f); + mPatch->paddingRight = int(mPatch->paddingRight * scaleX + 0.5f); + scaleDivRange(mPatch->getXDivs(), mPatch->numXDivs, scaleX, scaledWidth - 1); + } + + if (!SkScalarNearlyEqual(scaleY, 1.0f)) { + mPatch->paddingTop = int(mPatch->paddingTop * scaleY + 0.5f); + mPatch->paddingBottom = int(mPatch->paddingBottom * scaleY + 0.5f); + scaleDivRange(mPatch->getYDivs(), mPatch->numYDivs, scaleY, scaledHeight - 1); + } +} diff --git a/libs/hwui/jni/NinePatchPeeker.h b/libs/hwui/jni/NinePatchPeeker.h new file mode 100644 index 000000000000..e4e58dda4783 --- /dev/null +++ b/libs/hwui/jni/NinePatchPeeker.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _ANDROID_GRAPHICS_NINE_PATCH_PEEKER_H_ +#define _ANDROID_GRAPHICS_NINE_PATCH_PEEKER_H_ + +#include "SkPngChunkReader.h" +#include <androidfw/ResourceTypes.h> + +#include <jni.h> + +using namespace android; + +class NinePatchPeeker : public SkPngChunkReader { +public: + NinePatchPeeker() + : mPatch(NULL) + , mPatchSize(0) + , mHasInsets(false) + , mOutlineRadius(0) + , mOutlineAlpha(0) { + memset(mOpticalInsets, 0, 4 * sizeof(int32_t)); + memset(mOutlineInsets, 0, 4 * sizeof(int32_t)); + } + + ~NinePatchPeeker() { + free(mPatch); + } + + bool readChunk(const char tag[], const void* data, size_t length) override; + + jobject createNinePatchInsets(JNIEnv*, float scale) const; + void getPadding(JNIEnv*, jobject outPadding) const; + void scale(float scaleX, float scaleY, int scaledWidth, int scaledHeight); + + Res_png_9patch* mPatch; + size_t mPatchSize; + bool mHasInsets; +private: + int32_t mOpticalInsets[4]; + int32_t mOutlineInsets[4]; + float mOutlineRadius; + uint8_t mOutlineAlpha; +}; + +#endif // _ANDROID_GRAPHICS_NINE_PATCH_PEEKER_H_ diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp new file mode 100644 index 000000000000..df8635a8fe5a --- /dev/null +++ b/libs/hwui/jni/Paint.cpp @@ -0,0 +1,1159 @@ +/* libs/android_runtime/android/graphics/Paint.cpp +** +** Copyright 2006, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#undef LOG_TAG +#define LOG_TAG "Paint" + +#include <utils/Log.h> + +#include "GraphicsJNI.h" +#include <nativehelper/ScopedStringChars.h> +#include <nativehelper/ScopedUtfChars.h> +#include <nativehelper/ScopedPrimitiveArray.h> + +#include "SkBlurDrawLooper.h" +#include "SkColorFilter.h" +#include "SkFont.h" +#include "SkFontMetrics.h" +#include "SkFontTypes.h" +#include "SkMaskFilter.h" +#include "SkPath.h" +#include "SkPathEffect.h" +#include "SkShader.h" +#include "SkBlendMode.h" +#include "unicode/uloc.h" +#include "unicode/ushape.h" +#include "utils/Blur.h" + +#include <hwui/MinikinSkia.h> +#include <hwui/MinikinUtils.h> +#include <hwui/Paint.h> +#include <hwui/Typeface.h> +#include <minikin/GraphemeBreak.h> +#include <minikin/LocaleList.h> +#include <minikin/Measurement.h> +#include <minikin/MinikinPaint.h> +#include <unicode/utf16.h> + +#include <cassert> +#include <cstring> +#include <memory> +#include <vector> + +namespace android { + +struct JMetricsID { + jfieldID top; + jfieldID ascent; + jfieldID descent; + jfieldID bottom; + jfieldID leading; +}; + +static jclass gFontMetrics_class; +static JMetricsID gFontMetrics_fieldID; + +static jclass gFontMetricsInt_class; +static JMetricsID gFontMetricsInt_fieldID; + +static void getPosTextPath(const SkFont& font, const uint16_t glyphs[], int count, + const SkPoint pos[], SkPath* dst) { + dst->reset(); + struct Rec { + SkPath* fDst; + const SkPoint* fPos; + } rec = { dst, pos }; + font.getPaths(glyphs, count, [](const SkPath* src, const SkMatrix& mx, void* ctx) { + Rec* rec = (Rec*)ctx; + if (src) { + SkMatrix tmp(mx); + tmp.postTranslate(rec->fPos->fX, rec->fPos->fY); + rec->fDst->addPath(*src, tmp); + } + rec->fPos += 1; + }, &rec); +} + +namespace PaintGlue { + enum MoveOpt { + AFTER, AT_OR_AFTER, BEFORE, AT_OR_BEFORE, AT + }; + + static void deletePaint(Paint* paint) { + delete paint; + } + + static jlong getNativeFinalizer(JNIEnv*, jobject) { + return static_cast<jlong>(reinterpret_cast<uintptr_t>(&deletePaint)); + } + + static jlong init(JNIEnv* env, jobject) { + return reinterpret_cast<jlong>(new Paint); + } + + static jlong initWithPaint(JNIEnv* env, jobject clazz, jlong paintHandle) { + Paint* paint = reinterpret_cast<Paint*>(paintHandle); + Paint* obj = new Paint(*paint); + return reinterpret_cast<jlong>(obj); + } + + static int breakText(JNIEnv* env, const Paint& paint, const Typeface* typeface, + const jchar text[], int count, float maxWidth, jint bidiFlags, jfloatArray jmeasured, + const bool forwardScan) { + size_t measuredCount = 0; + float measured = 0; + + std::unique_ptr<float[]> advancesArray(new float[count]); + MinikinUtils::measureText(&paint, static_cast<minikin::Bidi>(bidiFlags), typeface, text, + 0, count, count, advancesArray.get()); + + for (int i = 0; i < count; i++) { + // traverse in the given direction + int index = forwardScan ? i : (count - i - 1); + float width = advancesArray[index]; + if (measured + width > maxWidth) { + break; + } + // properly handle clusters when scanning backwards + if (forwardScan || width != 0.0f) { + measuredCount = i + 1; + } + measured += width; + } + + if (jmeasured && env->GetArrayLength(jmeasured) > 0) { + AutoJavaFloatArray autoMeasured(env, jmeasured, 1); + jfloat* array = autoMeasured.ptr(); + array[0] = measured; + } + return measuredCount; + } + + static jint breakTextC(JNIEnv* env, jobject clazz, jlong paintHandle, jcharArray jtext, + jint index, jint count, jfloat maxWidth, jint bidiFlags, jfloatArray jmeasuredWidth) { + NPE_CHECK_RETURN_ZERO(env, jtext); + + Paint* paint = reinterpret_cast<Paint*>(paintHandle); + const Typeface* typeface = paint->getAndroidTypeface(); + + bool forwardTextDirection; + if (count < 0) { + forwardTextDirection = false; + count = -count; + } + else { + forwardTextDirection = true; + } + + if ((index < 0) || (index + count > env->GetArrayLength(jtext))) { + doThrowAIOOBE(env); + return 0; + } + + const jchar* text = env->GetCharArrayElements(jtext, nullptr); + count = breakText(env, *paint, typeface, text + index, count, maxWidth, + bidiFlags, jmeasuredWidth, forwardTextDirection); + env->ReleaseCharArrayElements(jtext, const_cast<jchar*>(text), + JNI_ABORT); + return count; + } + + static jint breakTextS(JNIEnv* env, jobject clazz, jlong paintHandle, jstring jtext, + jboolean forwards, jfloat maxWidth, jint bidiFlags, jfloatArray jmeasuredWidth) { + NPE_CHECK_RETURN_ZERO(env, jtext); + + Paint* paint = reinterpret_cast<Paint*>(paintHandle); + const Typeface* typeface = paint->getAndroidTypeface(); + + int count = env->GetStringLength(jtext); + const jchar* text = env->GetStringChars(jtext, nullptr); + count = breakText(env, *paint, typeface, text, count, maxWidth, bidiFlags, jmeasuredWidth, forwards); + env->ReleaseStringChars(jtext, text); + return count; + } + + static jfloat doTextAdvances(JNIEnv *env, Paint *paint, const Typeface* typeface, + const jchar *text, jint start, jint count, jint contextCount, jint bidiFlags, + jfloatArray advances, jint advancesIndex) { + NPE_CHECK_RETURN_ZERO(env, text); + + if ((start | count | contextCount | advancesIndex) < 0 || contextCount < count) { + doThrowAIOOBE(env); + return 0; + } + if (count == 0) { + return 0; + } + if (advances) { + size_t advancesLength = env->GetArrayLength(advances); + if ((size_t)(count + advancesIndex) > advancesLength) { + doThrowAIOOBE(env); + return 0; + } + } + std::unique_ptr<jfloat[]> advancesArray; + if (advances) { + advancesArray.reset(new jfloat[count]); + } + const float advance = MinikinUtils::measureText(paint, + static_cast<minikin::Bidi>(bidiFlags), typeface, text, start, count, contextCount, + advancesArray.get()); + if (advances) { + env->SetFloatArrayRegion(advances, advancesIndex, count, advancesArray.get()); + } + return advance; + } + + static jfloat getTextAdvances___CIIIII_FI(JNIEnv* env, jobject clazz, jlong paintHandle, + jcharArray text, jint index, jint count, jint contextIndex, jint contextCount, + jint bidiFlags, jfloatArray advances, jint advancesIndex) { + Paint* paint = reinterpret_cast<Paint*>(paintHandle); + const Typeface* typeface = paint->getAndroidTypeface(); + jchar* textArray = env->GetCharArrayElements(text, nullptr); + jfloat result = doTextAdvances(env, paint, typeface, textArray + contextIndex, + index - contextIndex, count, contextCount, bidiFlags, advances, advancesIndex); + env->ReleaseCharArrayElements(text, textArray, JNI_ABORT); + return result; + } + + static jfloat getTextAdvances__StringIIIII_FI(JNIEnv* env, jobject clazz, jlong paintHandle, + jstring text, jint start, jint end, jint contextStart, jint contextEnd, jint bidiFlags, + jfloatArray advances, jint advancesIndex) { + Paint* paint = reinterpret_cast<Paint*>(paintHandle); + const Typeface* typeface = paint->getAndroidTypeface(); + const jchar* textArray = env->GetStringChars(text, nullptr); + jfloat result = doTextAdvances(env, paint, typeface, textArray + contextStart, + start - contextStart, end - start, contextEnd - contextStart, bidiFlags, + advances, advancesIndex); + env->ReleaseStringChars(text, textArray); + return result; + } + + static jint doTextRunCursor(JNIEnv *env, Paint* paint, const Typeface* typeface, + const jchar *text, jint start, jint count, jint dir, jint offset, jint opt) { + minikin::GraphemeBreak::MoveOpt moveOpt = minikin::GraphemeBreak::MoveOpt(opt); + minikin::Bidi bidiFlags = dir == 1 ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR; + std::unique_ptr<float[]> advancesArray(new float[count]); + MinikinUtils::measureText(paint, bidiFlags, typeface, text, start, count, start + count, + advancesArray.get()); + size_t result = minikin::GraphemeBreak::getTextRunCursor(advancesArray.get(), text, + start, count, offset, moveOpt); + return static_cast<jint>(result); + } + + static jint getTextRunCursor___C(JNIEnv* env, jobject clazz, jlong paintHandle, jcharArray text, + jint contextStart, jint contextCount, jint dir, jint offset, jint cursorOpt) { + Paint* paint = reinterpret_cast<Paint*>(paintHandle); + const Typeface* typeface = paint->getAndroidTypeface(); + jchar* textArray = env->GetCharArrayElements(text, nullptr); + jint result = doTextRunCursor(env, paint, typeface, textArray, + contextStart, contextCount, dir, offset, cursorOpt); + env->ReleaseCharArrayElements(text, textArray, JNI_ABORT); + return result; + } + + static jint getTextRunCursor__String(JNIEnv* env, jobject clazz, jlong paintHandle, + jstring text, jint contextStart, jint contextEnd, jint dir, jint offset, + jint cursorOpt) { + Paint* paint = reinterpret_cast<Paint*>(paintHandle); + const Typeface* typeface = paint->getAndroidTypeface(); + const jchar* textArray = env->GetStringChars(text, nullptr); + jint result = doTextRunCursor(env, paint, typeface, textArray, + contextStart, contextEnd - contextStart, dir, offset, cursorOpt); + env->ReleaseStringChars(text, textArray); + return result; + } + + class GetTextFunctor { + public: + GetTextFunctor(const minikin::Layout& layout, SkPath* path, jfloat x, jfloat y, + Paint* paint, uint16_t* glyphs, SkPoint* pos) + : layout(layout), path(path), x(x), y(y), paint(paint), glyphs(glyphs), pos(pos) { + } + + void operator()(size_t start, size_t end) { + for (size_t i = start; i < end; i++) { + glyphs[i] = layout.getGlyphId(i); + pos[i].fX = x + layout.getX(i); + pos[i].fY = y + layout.getY(i); + } + const SkFont& font = paint->getSkFont(); + if (start == 0) { + getPosTextPath(font, glyphs, end, pos, path); + } else { + getPosTextPath(font, glyphs + start, end - start, pos + start, &tmpPath); + path->addPath(tmpPath); + } + } + private: + const minikin::Layout& layout; + SkPath* path; + jfloat x; + jfloat y; + Paint* paint; + uint16_t* glyphs; + SkPoint* pos; + SkPath tmpPath; + }; + + static void getTextPath(JNIEnv* env, Paint* paint, const Typeface* typeface, const jchar* text, + jint count, jint bidiFlags, jfloat x, jfloat y, SkPath* path) { + minikin::Layout layout = MinikinUtils::doLayout( + paint, static_cast<minikin::Bidi>(bidiFlags), typeface, + text, count, // text buffer + 0, count, // draw range + 0, count, // context range + nullptr); + size_t nGlyphs = layout.nGlyphs(); + uint16_t* glyphs = new uint16_t[nGlyphs]; + SkPoint* pos = new SkPoint[nGlyphs]; + + x += MinikinUtils::xOffsetForTextAlign(paint, layout); + Paint::Align align = paint->getTextAlign(); + paint->setTextAlign(Paint::kLeft_Align); + GetTextFunctor f(layout, path, x, y, paint, glyphs, pos); + MinikinUtils::forFontRun(layout, paint, f); + paint->setTextAlign(align); + delete[] glyphs; + delete[] pos; + } + + static void getTextPath___C(JNIEnv* env, jobject clazz, jlong paintHandle, jint bidiFlags, + jcharArray text, jint index, jint count, jfloat x, jfloat y, jlong pathHandle) { + Paint* paint = reinterpret_cast<Paint*>(paintHandle); + const Typeface* typeface = paint->getAndroidTypeface(); + SkPath* path = reinterpret_cast<SkPath*>(pathHandle); + const jchar* textArray = env->GetCharArrayElements(text, nullptr); + getTextPath(env, paint, typeface, textArray + index, count, bidiFlags, x, y, path); + env->ReleaseCharArrayElements(text, const_cast<jchar*>(textArray), JNI_ABORT); + } + + static void getTextPath__String(JNIEnv* env, jobject clazz, jlong paintHandle, jint bidiFlags, + jstring text, jint start, jint end, jfloat x, jfloat y, jlong pathHandle) { + Paint* paint = reinterpret_cast<Paint*>(paintHandle); + const Typeface* typeface = paint->getAndroidTypeface(); + SkPath* path = reinterpret_cast<SkPath*>(pathHandle); + const jchar* textArray = env->GetStringChars(text, nullptr); + getTextPath(env, paint, typeface, textArray + start, end - start, bidiFlags, x, y, path); + env->ReleaseStringChars(text, textArray); + } + + static void doTextBounds(JNIEnv* env, const jchar* text, int count, jobject bounds, + const Paint& paint, const Typeface* typeface, jint bidiFlags) { + SkRect r; + SkIRect ir; + + minikin::Layout layout = MinikinUtils::doLayout(&paint, + static_cast<minikin::Bidi>(bidiFlags), typeface, + text, count, // text buffer + 0, count, // draw range + 0, count, // context range + nullptr); + minikin::MinikinRect rect; + layout.getBounds(&rect); + r.fLeft = rect.mLeft; + r.fTop = rect.mTop; + r.fRight = rect.mRight; + r.fBottom = rect.mBottom; + r.roundOut(&ir); + GraphicsJNI::irect_to_jrect(ir, env, bounds); + } + + static void getStringBounds(JNIEnv* env, jobject, jlong paintHandle, jstring text, jint start, + jint end, jint bidiFlags, jobject bounds) { + const Paint* paint = reinterpret_cast<Paint*>(paintHandle); + const Typeface* typeface = paint->getAndroidTypeface(); + const jchar* textArray = env->GetStringChars(text, nullptr); + doTextBounds(env, textArray + start, end - start, bounds, *paint, typeface, bidiFlags); + env->ReleaseStringChars(text, textArray); + } + + static void getCharArrayBounds(JNIEnv* env, jobject, jlong paintHandle, jcharArray text, + jint index, jint count, jint bidiFlags, jobject bounds) { + const Paint* paint = reinterpret_cast<Paint*>(paintHandle); + const Typeface* typeface = paint->getAndroidTypeface(); + const jchar* textArray = env->GetCharArrayElements(text, nullptr); + doTextBounds(env, textArray + index, count, bounds, *paint, typeface, bidiFlags); + env->ReleaseCharArrayElements(text, const_cast<jchar*>(textArray), + JNI_ABORT); + } + + // Returns true if the given string is exact one pair of regional indicators. + static bool isFlag(const jchar* str, size_t length) { + const jchar RI_LEAD_SURROGATE = 0xD83C; + const jchar RI_TRAIL_SURROGATE_MIN = 0xDDE6; + const jchar RI_TRAIL_SURROGATE_MAX = 0xDDFF; + + if (length != 4) { + return false; + } + if (str[0] != RI_LEAD_SURROGATE || str[2] != RI_LEAD_SURROGATE) { + return false; + } + return RI_TRAIL_SURROGATE_MIN <= str[1] && str[1] <= RI_TRAIL_SURROGATE_MAX && + RI_TRAIL_SURROGATE_MIN <= str[3] && str[3] <= RI_TRAIL_SURROGATE_MAX; + } + + static jboolean layoutContainsNotdef(const minikin::Layout& layout) { + for (size_t i = 0; i < layout.nGlyphs(); i++) { + if (layout.getGlyphId(i) == 0) { + return true; + } + } + return false; + } + + // Don't count glyphs that are the recommended "space" glyph and are zero width. + // This logic makes assumptions about HarfBuzz layout, but does correctly handle + // cases where ligatures form and zero width space glyphs are left in as + // placeholders. + static size_t countNonSpaceGlyphs(const minikin::Layout& layout) { + size_t count = 0; + static unsigned int kSpaceGlyphId = 3; + for (size_t i = 0; i < layout.nGlyphs(); i++) { + if (layout.getGlyphId(i) != kSpaceGlyphId || layout.getCharAdvance(i) != 0.0) { + count++; + } + } + return count; + } + + static jboolean hasGlyph(JNIEnv *env, jclass, jlong paintHandle, jint bidiFlags, + jstring string) { + const Paint* paint = reinterpret_cast<Paint*>(paintHandle); + const Typeface* typeface = paint->getAndroidTypeface(); + ScopedStringChars str(env, string); + + /* Start by rejecting unsupported base code point and variation selector pairs. */ + size_t nChars = 0; + const uint32_t kStartOfString = 0xFFFFFFFF; + uint32_t prevCp = kStartOfString; + for (size_t i = 0; i < str.size(); i++) { + jchar cu = str[i]; + uint32_t cp = cu; + if (U16_IS_TRAIL(cu)) { + // invalid UTF-16, unpaired trailing surrogate + return false; + } else if (U16_IS_LEAD(cu)) { + if (i + 1 == str.size()) { + // invalid UTF-16, unpaired leading surrogate at end of string + return false; + } + i++; + jchar cu2 = str[i]; + if (!U16_IS_TRAIL(cu2)) { + // invalid UTF-16, unpaired leading surrogate + return false; + } + cp = U16_GET_SUPPLEMENTARY(cu, cu2); + } + + if (prevCp != kStartOfString && + ((0xFE00 <= cp && cp <= 0xFE0F) || (0xE0100 <= cp && cp <= 0xE01EF))) { + bool hasVS = MinikinUtils::hasVariationSelector(typeface, prevCp, cp); + if (!hasVS) { + // No font has a glyph for the code point and variation selector pair. + return false; + } else if (nChars == 1 && i + 1 == str.size()) { + // The string is just a codepoint and a VS, we have an authoritative answer + return true; + } + } + nChars++; + prevCp = cp; + } + minikin::Layout layout = MinikinUtils::doLayout(paint, + static_cast<minikin::Bidi>(bidiFlags), typeface, + str.get(), str.size(), // text buffer + 0, str.size(), // draw range + 0, str.size(), // context range + nullptr); + size_t nGlyphs = countNonSpaceGlyphs(layout); + if (nGlyphs != 1 && nChars > 1) { + // multiple-character input, and was not a ligature + // TODO: handle ZWJ/ZWNJ characters specially so we can detect certain ligatures + // in joining scripts, such as Arabic and Mongolian. + return false; + } + + if (nGlyphs == 0 || layoutContainsNotdef(layout)) { + return false; // The collection doesn't have a glyph. + } + + if (nChars == 2 && isFlag(str.get(), str.size())) { + // Some font may have a special glyph for unsupported regional indicator pairs. + // To return false for this case, need to compare the glyph id with the one of ZZ + // since ZZ is reserved for unknown or invalid territory. + // U+1F1FF (REGIONAL INDICATOR SYMBOL LETTER Z) is \uD83C\uDDFF in UTF16. + static const jchar ZZ_FLAG_STR[] = { 0xD83C, 0xDDFF, 0xD83C, 0xDDFF }; + minikin::Layout zzLayout = MinikinUtils::doLayout(paint, + static_cast<minikin::Bidi>(bidiFlags), typeface, + ZZ_FLAG_STR, 4, // text buffer + 0, 4, // draw range + 0, 4, // context range + nullptr); + if (zzLayout.nGlyphs() != 1 || layoutContainsNotdef(zzLayout)) { + // The font collection doesn't have a glyph for unknown flag. Just return true. + return true; + } + return zzLayout.getGlyphId(0) != layout.getGlyphId(0); + } + return true; + } + + static jfloat doRunAdvance(const Paint* paint, const Typeface* typeface, const jchar buf[], + jint start, jint count, jint bufSize, jboolean isRtl, jint offset) { + minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR; + if (offset == start + count) { + return MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count, + bufSize, nullptr); + } + std::unique_ptr<float[]> advancesArray(new float[count]); + MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count, bufSize, + advancesArray.get()); + return minikin::getRunAdvance(advancesArray.get(), buf, start, count, offset); + } + + static jfloat getRunAdvance___CIIIIZI_F(JNIEnv *env, jclass, jlong paintHandle, jcharArray text, + jint start, jint end, jint contextStart, jint contextEnd, jboolean isRtl, jint offset) { + const Paint* paint = reinterpret_cast<Paint*>(paintHandle); + const Typeface* typeface = paint->getAndroidTypeface(); + ScopedCharArrayRO textArray(env, text); + jfloat result = doRunAdvance(paint, typeface, textArray.get() + contextStart, + start - contextStart, end - start, contextEnd - contextStart, isRtl, + offset - contextStart); + return result; + } + + static jint doOffsetForAdvance(const Paint* paint, const Typeface* typeface, const jchar buf[], + jint start, jint count, jint bufSize, jboolean isRtl, jfloat advance) { + minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR; + std::unique_ptr<float[]> advancesArray(new float[count]); + MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count, bufSize, + advancesArray.get()); + return minikin::getOffsetForAdvance(advancesArray.get(), buf, start, count, advance); + } + + static jint getOffsetForAdvance___CIIIIZF_I(JNIEnv *env, jclass, jlong paintHandle, + jcharArray text, jint start, jint end, jint contextStart, jint contextEnd, + jboolean isRtl, jfloat advance) { + const Paint* paint = reinterpret_cast<Paint*>(paintHandle); + const Typeface* typeface = paint->getAndroidTypeface(); + ScopedCharArrayRO textArray(env, text); + jint result = doOffsetForAdvance(paint, typeface, textArray.get() + contextStart, + start - contextStart, end - start, contextEnd - contextStart, isRtl, advance); + result += contextStart; + return result; + } + + // ------------------ @FastNative --------------------------- + + static jint setTextLocales(JNIEnv* env, jobject clazz, jlong objHandle, jstring locales) { + Paint* obj = reinterpret_cast<Paint*>(objHandle); + ScopedUtfChars localesChars(env, locales); + jint minikinLocaleListId = minikin::registerLocaleList(localesChars.c_str()); + obj->setMinikinLocaleListId(minikinLocaleListId); + return minikinLocaleListId; + } + + static void setFontFeatureSettings(JNIEnv* env, jobject clazz, jlong paintHandle, jstring settings) { + Paint* paint = reinterpret_cast<Paint*>(paintHandle); + if (!settings) { + paint->setFontFeatureSettings(std::string()); + } else { + ScopedUtfChars settingsChars(env, settings); + paint->setFontFeatureSettings(std::string(settingsChars.c_str(), settingsChars.size())); + } + } + + static SkScalar getMetricsInternal(jlong paintHandle, SkFontMetrics *metrics) { + const int kElegantTop = 2500; + const int kElegantBottom = -1000; + const int kElegantAscent = 1900; + const int kElegantDescent = -500; + const int kElegantLeading = 0; + Paint* paint = reinterpret_cast<Paint*>(paintHandle); + SkFont* font = &paint->getSkFont(); + const Typeface* typeface = paint->getAndroidTypeface(); + typeface = Typeface::resolveDefault(typeface); + minikin::FakedFont baseFont = typeface->fFontCollection->baseFontFaked(typeface->fStyle); + float saveSkewX = font->getSkewX(); + bool savefakeBold = font->isEmbolden(); + MinikinFontSkia::populateSkFont(font, baseFont.font->typeface().get(), baseFont.fakery); + SkScalar spacing = font->getMetrics(metrics); + // The populateSkPaint call may have changed fake bold / text skew + // because we want to measure with those effects applied, so now + // restore the original settings. + font->setSkewX(saveSkewX); + font->setEmbolden(savefakeBold); + if (paint->getFamilyVariant() == minikin::FamilyVariant::ELEGANT) { + SkScalar size = font->getSize(); + metrics->fTop = -size * kElegantTop / 2048; + metrics->fBottom = -size * kElegantBottom / 2048; + metrics->fAscent = -size * kElegantAscent / 2048; + metrics->fDescent = -size * kElegantDescent / 2048; + metrics->fLeading = size * kElegantLeading / 2048; + spacing = metrics->fDescent - metrics->fAscent + metrics->fLeading; + } + return spacing; + } + + static jfloat getFontMetrics(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj) { + SkFontMetrics metrics; + SkScalar spacing = getMetricsInternal(paintHandle, &metrics); + + if (metricsObj) { + SkASSERT(env->IsInstanceOf(metricsObj, gFontMetrics_class)); + env->SetFloatField(metricsObj, gFontMetrics_fieldID.top, SkScalarToFloat(metrics.fTop)); + env->SetFloatField(metricsObj, gFontMetrics_fieldID.ascent, SkScalarToFloat(metrics.fAscent)); + env->SetFloatField(metricsObj, gFontMetrics_fieldID.descent, SkScalarToFloat(metrics.fDescent)); + env->SetFloatField(metricsObj, gFontMetrics_fieldID.bottom, SkScalarToFloat(metrics.fBottom)); + env->SetFloatField(metricsObj, gFontMetrics_fieldID.leading, SkScalarToFloat(metrics.fLeading)); + } + return SkScalarToFloat(spacing); + } + + static jint getFontMetricsInt(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj) { + SkFontMetrics metrics; + + getMetricsInternal(paintHandle, &metrics); + int ascent = SkScalarRoundToInt(metrics.fAscent); + int descent = SkScalarRoundToInt(metrics.fDescent); + int leading = SkScalarRoundToInt(metrics.fLeading); + + if (metricsObj) { + SkASSERT(env->IsInstanceOf(metricsObj, gFontMetricsInt_class)); + env->SetIntField(metricsObj, gFontMetricsInt_fieldID.top, SkScalarFloorToInt(metrics.fTop)); + env->SetIntField(metricsObj, gFontMetricsInt_fieldID.ascent, ascent); + env->SetIntField(metricsObj, gFontMetricsInt_fieldID.descent, descent); + env->SetIntField(metricsObj, gFontMetricsInt_fieldID.bottom, SkScalarCeilToInt(metrics.fBottom)); + env->SetIntField(metricsObj, gFontMetricsInt_fieldID.leading, leading); + } + return descent - ascent + leading; + } + + + // ------------------ @CriticalNative --------------------------- + + static void reset(CRITICAL_JNI_PARAMS_COMMA jlong objHandle) { + reinterpret_cast<Paint*>(objHandle)->reset(); + } + + static void assign(CRITICAL_JNI_PARAMS_COMMA jlong dstPaintHandle, jlong srcPaintHandle) { + Paint* dst = reinterpret_cast<Paint*>(dstPaintHandle); + const Paint* src = reinterpret_cast<Paint*>(srcPaintHandle); + *dst = *src; + } + + static jint getFlags(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) { + uint32_t flags = reinterpret_cast<Paint*>(paintHandle)->getJavaFlags(); + return static_cast<jint>(flags); + } + + static void setFlags(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jint flags) { + reinterpret_cast<Paint*>(paintHandle)->setJavaFlags(flags); + } + + static jint getHinting(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) { + return (SkFontHinting)reinterpret_cast<Paint*>(paintHandle)->getSkFont().getHinting() + == SkFontHinting::kNone ? 0 : 1; + } + + static void setHinting(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jint mode) { + reinterpret_cast<Paint*>(paintHandle)->getSkFont().setHinting( + mode == 0 ? SkFontHinting::kNone : SkFontHinting::kNormal); + } + + static void setAntiAlias(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jboolean aa) { + reinterpret_cast<Paint*>(paintHandle)->setAntiAlias(aa); + } + + static void setLinearText(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jboolean linearText) { + reinterpret_cast<Paint*>(paintHandle)->getSkFont().setLinearMetrics(linearText); + } + + static void setSubpixelText(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jboolean subpixelText) { + reinterpret_cast<Paint*>(paintHandle)->getSkFont().setSubpixel(subpixelText); + } + + static void setUnderlineText(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jboolean underlineText) { + reinterpret_cast<Paint*>(paintHandle)->setUnderline(underlineText); + } + + static void setStrikeThruText(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jboolean strikeThruText) { + reinterpret_cast<Paint*>(paintHandle)->setStrikeThru(strikeThruText); + } + + static void setFakeBoldText(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jboolean fakeBoldText) { + reinterpret_cast<Paint*>(paintHandle)->getSkFont().setEmbolden(fakeBoldText); + } + + static void setFilterBitmap(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jboolean filterBitmap) { + reinterpret_cast<Paint*>(paintHandle)->setFilterQuality( + filterBitmap ? kLow_SkFilterQuality : kNone_SkFilterQuality); + } + + static void setDither(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jboolean dither) { + reinterpret_cast<Paint*>(paintHandle)->setDither(dither); + } + + static jint getStyle(CRITICAL_JNI_PARAMS_COMMA jlong objHandle) { + Paint* obj = reinterpret_cast<Paint*>(objHandle); + return static_cast<jint>(obj->getStyle()); + } + + static void setStyle(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jint styleHandle) { + Paint* obj = reinterpret_cast<Paint*>(objHandle); + Paint::Style style = static_cast<Paint::Style>(styleHandle); + obj->setStyle(style); + } + + static void setColorLong(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jlong colorSpaceHandle, + jlong colorLong) { + SkColor4f color = GraphicsJNI::convertColorLong(colorLong); + sk_sp<SkColorSpace> cs = GraphicsJNI::getNativeColorSpace(colorSpaceHandle); + reinterpret_cast<Paint*>(paintHandle)->setColor4f(color, cs.get()); + } + + static void setColor(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jint color) { + reinterpret_cast<Paint*>(paintHandle)->setColor(color); + } + + static void setAlpha(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jint a) { + reinterpret_cast<Paint*>(paintHandle)->setAlpha(a); + } + + static jfloat getStrokeWidth(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) { + return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getStrokeWidth()); + } + + static void setStrokeWidth(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jfloat width) { + reinterpret_cast<Paint*>(paintHandle)->setStrokeWidth(width); + } + + static jfloat getStrokeMiter(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) { + return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getStrokeMiter()); + } + + static void setStrokeMiter(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jfloat miter) { + reinterpret_cast<Paint*>(paintHandle)->setStrokeMiter(miter); + } + + static jint getStrokeCap(CRITICAL_JNI_PARAMS_COMMA jlong objHandle) { + Paint* obj = reinterpret_cast<Paint*>(objHandle); + return static_cast<jint>(obj->getStrokeCap()); + } + + static void setStrokeCap(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jint capHandle) { + Paint* obj = reinterpret_cast<Paint*>(objHandle); + Paint::Cap cap = static_cast<Paint::Cap>(capHandle); + obj->setStrokeCap(cap); + } + + static jint getStrokeJoin(CRITICAL_JNI_PARAMS_COMMA jlong objHandle) { + Paint* obj = reinterpret_cast<Paint*>(objHandle); + return static_cast<jint>(obj->getStrokeJoin()); + } + + static void setStrokeJoin(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jint joinHandle) { + Paint* obj = reinterpret_cast<Paint*>(objHandle); + Paint::Join join = (Paint::Join) joinHandle; + obj->setStrokeJoin(join); + } + + static jboolean getFillPath(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jlong srcHandle, jlong dstHandle) { + Paint* obj = reinterpret_cast<Paint*>(objHandle); + SkPath* src = reinterpret_cast<SkPath*>(srcHandle); + SkPath* dst = reinterpret_cast<SkPath*>(dstHandle); + return obj->getFillPath(*src, dst) ? JNI_TRUE : JNI_FALSE; + } + + static jlong setShader(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jlong shaderHandle) { + Paint* obj = reinterpret_cast<Paint*>(objHandle); + SkShader* shader = reinterpret_cast<SkShader*>(shaderHandle); + obj->setShader(sk_ref_sp(shader)); + return reinterpret_cast<jlong>(obj->getShader()); + } + + static jlong setColorFilter(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jlong filterHandle) { + Paint* obj = reinterpret_cast<Paint *>(objHandle); + SkColorFilter* filter = reinterpret_cast<SkColorFilter *>(filterHandle); + obj->setColorFilter(sk_ref_sp(filter)); + return reinterpret_cast<jlong>(obj->getColorFilter()); + } + + static void setXfermode(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jint xfermodeHandle) { + // validate that the Java enum values match our expectations + static_assert(0 == static_cast<int>(SkBlendMode::kClear), "xfermode_mismatch"); + static_assert(1 == static_cast<int>(SkBlendMode::kSrc), "xfermode_mismatch"); + static_assert(2 == static_cast<int>(SkBlendMode::kDst), "xfermode_mismatch"); + static_assert(3 == static_cast<int>(SkBlendMode::kSrcOver), "xfermode_mismatch"); + static_assert(4 == static_cast<int>(SkBlendMode::kDstOver), "xfermode_mismatch"); + static_assert(5 == static_cast<int>(SkBlendMode::kSrcIn), "xfermode_mismatch"); + static_assert(6 == static_cast<int>(SkBlendMode::kDstIn), "xfermode_mismatch"); + static_assert(7 == static_cast<int>(SkBlendMode::kSrcOut), "xfermode_mismatch"); + static_assert(8 == static_cast<int>(SkBlendMode::kDstOut), "xfermode_mismatch"); + static_assert(9 == static_cast<int>(SkBlendMode::kSrcATop), "xfermode_mismatch"); + static_assert(10 == static_cast<int>(SkBlendMode::kDstATop), "xfermode_mismatch"); + static_assert(11 == static_cast<int>(SkBlendMode::kXor), "xfermode_mismatch"); + static_assert(12 == static_cast<int>(SkBlendMode::kPlus), "xfermode_mismatch"); + static_assert(13 == static_cast<int>(SkBlendMode::kModulate), "xfermode_mismatch"); + static_assert(14 == static_cast<int>(SkBlendMode::kScreen), "xfermode_mismatch"); + static_assert(15 == static_cast<int>(SkBlendMode::kOverlay), "xfermode_mismatch"); + static_assert(16 == static_cast<int>(SkBlendMode::kDarken), "xfermode_mismatch"); + static_assert(17 == static_cast<int>(SkBlendMode::kLighten), "xfermode_mismatch"); + static_assert(18 == static_cast<int>(SkBlendMode::kColorDodge), "xfermode mismatch"); + static_assert(19 == static_cast<int>(SkBlendMode::kColorBurn), "xfermode mismatch"); + static_assert(20 == static_cast<int>(SkBlendMode::kHardLight), "xfermode mismatch"); + static_assert(21 == static_cast<int>(SkBlendMode::kSoftLight), "xfermode mismatch"); + static_assert(22 == static_cast<int>(SkBlendMode::kDifference), "xfermode mismatch"); + static_assert(23 == static_cast<int>(SkBlendMode::kExclusion), "xfermode mismatch"); + static_assert(24 == static_cast<int>(SkBlendMode::kMultiply), "xfermode mismatch"); + static_assert(25 == static_cast<int>(SkBlendMode::kHue), "xfermode mismatch"); + static_assert(26 == static_cast<int>(SkBlendMode::kSaturation), "xfermode mismatch"); + static_assert(27 == static_cast<int>(SkBlendMode::kColor), "xfermode mismatch"); + static_assert(28 == static_cast<int>(SkBlendMode::kLuminosity), "xfermode mismatch"); + + SkBlendMode mode = static_cast<SkBlendMode>(xfermodeHandle); + Paint* paint = reinterpret_cast<Paint*>(paintHandle); + paint->setBlendMode(mode); + } + + static jlong setPathEffect(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jlong effectHandle) { + Paint* obj = reinterpret_cast<Paint*>(objHandle); + SkPathEffect* effect = reinterpret_cast<SkPathEffect*>(effectHandle); + obj->setPathEffect(sk_ref_sp(effect)); + return reinterpret_cast<jlong>(obj->getPathEffect()); + } + + static jlong setMaskFilter(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jlong maskfilterHandle) { + Paint* obj = reinterpret_cast<Paint*>(objHandle); + SkMaskFilter* maskfilter = reinterpret_cast<SkMaskFilter*>(maskfilterHandle); + obj->setMaskFilter(sk_ref_sp(maskfilter)); + return reinterpret_cast<jlong>(obj->getMaskFilter()); + } + + static void setTypeface(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jlong typefaceHandle) { + Paint* paint = reinterpret_cast<Paint*>(objHandle); + paint->setAndroidTypeface(reinterpret_cast<Typeface*>(typefaceHandle)); + } + + static jint getTextAlign(CRITICAL_JNI_PARAMS_COMMA jlong objHandle) { + Paint* obj = reinterpret_cast<Paint*>(objHandle); + return static_cast<jint>(obj->getTextAlign()); + } + + static void setTextAlign(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jint alignHandle) { + Paint* obj = reinterpret_cast<Paint*>(objHandle); + Paint::Align align = static_cast<Paint::Align>(alignHandle); + obj->setTextAlign(align); + } + + static void setTextLocalesByMinikinLocaleListId(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, + jint minikinLocaleListId) { + Paint* obj = reinterpret_cast<Paint*>(objHandle); + obj->setMinikinLocaleListId(minikinLocaleListId); + } + + static jboolean isElegantTextHeight(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) { + Paint* obj = reinterpret_cast<Paint*>(paintHandle); + return obj->getFamilyVariant() == minikin::FamilyVariant::ELEGANT; + } + + static void setElegantTextHeight(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jboolean aa) { + Paint* obj = reinterpret_cast<Paint*>(paintHandle); + obj->setFamilyVariant( + aa ? minikin::FamilyVariant::ELEGANT : minikin::FamilyVariant::DEFAULT); + } + + static jfloat getTextSize(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) { + return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSize()); + } + + static void setTextSize(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jfloat textSize) { + if (textSize >= 0) { + reinterpret_cast<Paint*>(paintHandle)->getSkFont().setSize(textSize); + } + } + + static jfloat getTextScaleX(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) { + return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getSkFont().getScaleX()); + } + + static void setTextScaleX(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jfloat scaleX) { + reinterpret_cast<Paint*>(paintHandle)->getSkFont().setScaleX(scaleX); + } + + static jfloat getTextSkewX(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) { + return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSkewX()); + } + + static void setTextSkewX(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jfloat skewX) { + reinterpret_cast<Paint*>(paintHandle)->getSkFont().setSkewX(skewX); + } + + static jfloat getLetterSpacing(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) { + Paint* paint = reinterpret_cast<Paint*>(paintHandle); + return paint->getLetterSpacing(); + } + + static void setLetterSpacing(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jfloat letterSpacing) { + Paint* paint = reinterpret_cast<Paint*>(paintHandle); + paint->setLetterSpacing(letterSpacing); + } + + static jfloat getWordSpacing(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) { + Paint* paint = reinterpret_cast<Paint*>(paintHandle); + return paint->getWordSpacing(); + } + + static void setWordSpacing(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jfloat wordSpacing) { + Paint* paint = reinterpret_cast<Paint*>(paintHandle); + paint->setWordSpacing(wordSpacing); + } + + static jint getStartHyphenEdit(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jint hyphen) { + Paint* paint = reinterpret_cast<Paint*>(paintHandle); + return static_cast<jint>(paint->getStartHyphenEdit()); + } + + static jint getEndHyphenEdit(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jint hyphen) { + Paint* paint = reinterpret_cast<Paint*>(paintHandle); + return static_cast<jint>(paint->getEndHyphenEdit()); + } + + static void setStartHyphenEdit(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jint hyphen) { + Paint* paint = reinterpret_cast<Paint*>(paintHandle); + paint->setStartHyphenEdit((uint32_t)hyphen); + } + + static void setEndHyphenEdit(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jint hyphen) { + Paint* paint = reinterpret_cast<Paint*>(paintHandle); + paint->setEndHyphenEdit((uint32_t)hyphen); + } + + static jfloat ascent(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) { + SkFontMetrics metrics; + getMetricsInternal(paintHandle, &metrics); + return SkScalarToFloat(metrics.fAscent); + } + + static jfloat descent(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) { + SkFontMetrics metrics; + getMetricsInternal(paintHandle, &metrics); + return SkScalarToFloat(metrics.fDescent); + } + + static jfloat getUnderlinePosition(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) { + SkFontMetrics metrics; + getMetricsInternal(paintHandle, &metrics); + SkScalar position; + if (metrics.hasUnderlinePosition(&position)) { + return SkScalarToFloat(position); + } else { + const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSize(); + return SkScalarToFloat(Paint::kStdUnderline_Top * textSize); + } + } + + static jfloat getUnderlineThickness(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) { + SkFontMetrics metrics; + getMetricsInternal(paintHandle, &metrics); + SkScalar thickness; + if (metrics.hasUnderlineThickness(&thickness)) { + return SkScalarToFloat(thickness); + } else { + const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSize(); + return SkScalarToFloat(Paint::kStdUnderline_Thickness * textSize); + } + } + + static jfloat getStrikeThruPosition(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) { + const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSize(); + return SkScalarToFloat(Paint::kStdStrikeThru_Top * textSize); + } + + static jfloat getStrikeThruThickness(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) { + const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSize(); + return SkScalarToFloat(Paint::kStdStrikeThru_Thickness * textSize); + } + + static void setShadowLayer(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jfloat radius, + jfloat dx, jfloat dy, jlong colorSpaceHandle, + jlong colorLong) { + SkColor4f color = GraphicsJNI::convertColorLong(colorLong); + sk_sp<SkColorSpace> cs = GraphicsJNI::getNativeColorSpace(colorSpaceHandle); + + Paint* paint = reinterpret_cast<Paint*>(paintHandle); + if (radius <= 0) { + paint->setLooper(nullptr); + } + else { + SkScalar sigma = android::uirenderer::Blur::convertRadiusToSigma(radius); + paint->setLooper(SkBlurDrawLooper::Make(color, cs.get(), sigma, dx, dy)); + } + } + + static jboolean hasShadowLayer(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) { + Paint* paint = reinterpret_cast<Paint*>(paintHandle); + return paint->getLooper() && paint->getLooper()->asABlurShadow(nullptr); + } + + static jboolean equalsForTextMeasurement(CRITICAL_JNI_PARAMS_COMMA jlong lPaint, jlong rPaint) { + if (lPaint == rPaint) { + return true; + } + Paint* leftPaint = reinterpret_cast<Paint*>(lPaint); + Paint* rightPaint = reinterpret_cast<Paint*>(rPaint); + + const Typeface* leftTypeface = Typeface::resolveDefault(leftPaint->getAndroidTypeface()); + const Typeface* rightTypeface = Typeface::resolveDefault(rightPaint->getAndroidTypeface()); + minikin::MinikinPaint leftMinikinPaint + = MinikinUtils::prepareMinikinPaint(leftPaint, leftTypeface); + minikin::MinikinPaint rightMinikinPaint + = MinikinUtils::prepareMinikinPaint(rightPaint, rightTypeface); + + return leftMinikinPaint == rightMinikinPaint; + } + +}; // namespace PaintGlue + +static const JNINativeMethod methods[] = { + {"nGetNativeFinalizer", "()J", (void*) PaintGlue::getNativeFinalizer}, + {"nInit","()J", (void*) PaintGlue::init}, + {"nInitWithPaint","(J)J", (void*) PaintGlue::initWithPaint}, + {"nBreakText","(J[CIIFI[F)I", (void*) PaintGlue::breakTextC}, + {"nBreakText","(JLjava/lang/String;ZFI[F)I", (void*) PaintGlue::breakTextS}, + {"nGetTextAdvances","(J[CIIIII[FI)F", + (void*) PaintGlue::getTextAdvances___CIIIII_FI}, + {"nGetTextAdvances","(JLjava/lang/String;IIIII[FI)F", + (void*) PaintGlue::getTextAdvances__StringIIIII_FI}, + + {"nGetTextRunCursor", "(J[CIIIII)I", (void*) PaintGlue::getTextRunCursor___C}, + {"nGetTextRunCursor", "(JLjava/lang/String;IIIII)I", + (void*) PaintGlue::getTextRunCursor__String}, + {"nGetTextPath", "(JI[CIIFFJ)V", (void*) PaintGlue::getTextPath___C}, + {"nGetTextPath", "(JILjava/lang/String;IIFFJ)V", (void*) PaintGlue::getTextPath__String}, + {"nGetStringBounds", "(JLjava/lang/String;IIILandroid/graphics/Rect;)V", + (void*) PaintGlue::getStringBounds }, + {"nGetCharArrayBounds", "(J[CIIILandroid/graphics/Rect;)V", + (void*) PaintGlue::getCharArrayBounds }, + {"nHasGlyph", "(JILjava/lang/String;)Z", (void*) PaintGlue::hasGlyph }, + {"nGetRunAdvance", "(J[CIIIIZI)F", (void*) PaintGlue::getRunAdvance___CIIIIZI_F}, + {"nGetOffsetForAdvance", "(J[CIIIIZF)I", + (void*) PaintGlue::getOffsetForAdvance___CIIIIZF_I}, + + // --------------- @FastNative ---------------------- + + {"nSetTextLocales","(JLjava/lang/String;)I", (void*) PaintGlue::setTextLocales}, + {"nSetFontFeatureSettings","(JLjava/lang/String;)V", + (void*) PaintGlue::setFontFeatureSettings}, + {"nGetFontMetrics", "(JLandroid/graphics/Paint$FontMetrics;)F", + (void*)PaintGlue::getFontMetrics}, + {"nGetFontMetricsInt", "(JLandroid/graphics/Paint$FontMetricsInt;)I", + (void*)PaintGlue::getFontMetricsInt}, + + // --------------- @CriticalNative ------------------ + + {"nReset","(J)V", (void*) PaintGlue::reset}, + {"nSet","(JJ)V", (void*) PaintGlue::assign}, + {"nGetFlags","(J)I", (void*) PaintGlue::getFlags}, + {"nSetFlags","(JI)V", (void*) PaintGlue::setFlags}, + {"nGetHinting","(J)I", (void*) PaintGlue::getHinting}, + {"nSetHinting","(JI)V", (void*) PaintGlue::setHinting}, + {"nSetAntiAlias","(JZ)V", (void*) PaintGlue::setAntiAlias}, + {"nSetSubpixelText","(JZ)V", (void*) PaintGlue::setSubpixelText}, + {"nSetLinearText","(JZ)V", (void*) PaintGlue::setLinearText}, + {"nSetUnderlineText","(JZ)V", (void*) PaintGlue::setUnderlineText}, + {"nSetStrikeThruText","(JZ)V", (void*) PaintGlue::setStrikeThruText}, + {"nSetFakeBoldText","(JZ)V", (void*) PaintGlue::setFakeBoldText}, + {"nSetFilterBitmap","(JZ)V", (void*) PaintGlue::setFilterBitmap}, + {"nSetDither","(JZ)V", (void*) PaintGlue::setDither}, + {"nGetStyle","(J)I", (void*) PaintGlue::getStyle}, + {"nSetStyle","(JI)V", (void*) PaintGlue::setStyle}, + {"nSetColor","(JI)V", (void*) PaintGlue::setColor}, + {"nSetColor","(JJJ)V", (void*) PaintGlue::setColorLong}, + {"nSetAlpha","(JI)V", (void*) PaintGlue::setAlpha}, + {"nGetStrokeWidth","(J)F", (void*) PaintGlue::getStrokeWidth}, + {"nSetStrokeWidth","(JF)V", (void*) PaintGlue::setStrokeWidth}, + {"nGetStrokeMiter","(J)F", (void*) PaintGlue::getStrokeMiter}, + {"nSetStrokeMiter","(JF)V", (void*) PaintGlue::setStrokeMiter}, + {"nGetStrokeCap","(J)I", (void*) PaintGlue::getStrokeCap}, + {"nSetStrokeCap","(JI)V", (void*) PaintGlue::setStrokeCap}, + {"nGetStrokeJoin","(J)I", (void*) PaintGlue::getStrokeJoin}, + {"nSetStrokeJoin","(JI)V", (void*) PaintGlue::setStrokeJoin}, + {"nGetFillPath","(JJJ)Z", (void*) PaintGlue::getFillPath}, + {"nSetShader","(JJ)J", (void*) PaintGlue::setShader}, + {"nSetColorFilter","(JJ)J", (void*) PaintGlue::setColorFilter}, + {"nSetXfermode","(JI)V", (void*) PaintGlue::setXfermode}, + {"nSetPathEffect","(JJ)J", (void*) PaintGlue::setPathEffect}, + {"nSetMaskFilter","(JJ)J", (void*) PaintGlue::setMaskFilter}, + {"nSetTypeface","(JJ)V", (void*) PaintGlue::setTypeface}, + {"nGetTextAlign","(J)I", (void*) PaintGlue::getTextAlign}, + {"nSetTextAlign","(JI)V", (void*) PaintGlue::setTextAlign}, + {"nSetTextLocalesByMinikinLocaleListId","(JI)V", + (void*) PaintGlue::setTextLocalesByMinikinLocaleListId}, + {"nIsElegantTextHeight","(J)Z", (void*) PaintGlue::isElegantTextHeight}, + {"nSetElegantTextHeight","(JZ)V", (void*) PaintGlue::setElegantTextHeight}, + {"nGetTextSize","(J)F", (void*) PaintGlue::getTextSize}, + {"nSetTextSize","(JF)V", (void*) PaintGlue::setTextSize}, + {"nGetTextScaleX","(J)F", (void*) PaintGlue::getTextScaleX}, + {"nSetTextScaleX","(JF)V", (void*) PaintGlue::setTextScaleX}, + {"nGetTextSkewX","(J)F", (void*) PaintGlue::getTextSkewX}, + {"nSetTextSkewX","(JF)V", (void*) PaintGlue::setTextSkewX}, + {"nGetLetterSpacing","(J)F", (void*) PaintGlue::getLetterSpacing}, + {"nSetLetterSpacing","(JF)V", (void*) PaintGlue::setLetterSpacing}, + {"nGetWordSpacing","(J)F", (void*) PaintGlue::getWordSpacing}, + {"nSetWordSpacing","(JF)V", (void*) PaintGlue::setWordSpacing}, + {"nGetStartHyphenEdit", "(J)I", (void*) PaintGlue::getStartHyphenEdit}, + {"nGetEndHyphenEdit", "(J)I", (void*) PaintGlue::getEndHyphenEdit}, + {"nSetStartHyphenEdit", "(JI)V", (void*) PaintGlue::setStartHyphenEdit}, + {"nSetEndHyphenEdit", "(JI)V", (void*) PaintGlue::setEndHyphenEdit}, + {"nAscent","(J)F", (void*) PaintGlue::ascent}, + {"nDescent","(J)F", (void*) PaintGlue::descent}, + {"nGetUnderlinePosition","(J)F", (void*) PaintGlue::getUnderlinePosition}, + {"nGetUnderlineThickness","(J)F", (void*) PaintGlue::getUnderlineThickness}, + {"nGetStrikeThruPosition","(J)F", (void*) PaintGlue::getStrikeThruPosition}, + {"nGetStrikeThruThickness","(J)F", (void*) PaintGlue::getStrikeThruThickness}, + {"nSetShadowLayer", "(JFFFJJ)V", (void*)PaintGlue::setShadowLayer}, + {"nHasShadowLayer", "(J)Z", (void*)PaintGlue::hasShadowLayer}, + {"nEqualsForTextMeasurement", "(JJ)Z", (void*)PaintGlue::equalsForTextMeasurement}, +}; + +int register_android_graphics_Paint(JNIEnv* env) { + gFontMetrics_class = FindClassOrDie(env, "android/graphics/Paint$FontMetrics"); + gFontMetrics_class = MakeGlobalRefOrDie(env, gFontMetrics_class); + + gFontMetrics_fieldID.top = GetFieldIDOrDie(env, gFontMetrics_class, "top", "F"); + gFontMetrics_fieldID.ascent = GetFieldIDOrDie(env, gFontMetrics_class, "ascent", "F"); + gFontMetrics_fieldID.descent = GetFieldIDOrDie(env, gFontMetrics_class, "descent", "F"); + gFontMetrics_fieldID.bottom = GetFieldIDOrDie(env, gFontMetrics_class, "bottom", "F"); + gFontMetrics_fieldID.leading = GetFieldIDOrDie(env, gFontMetrics_class, "leading", "F"); + + gFontMetricsInt_class = FindClassOrDie(env, "android/graphics/Paint$FontMetricsInt"); + gFontMetricsInt_class = MakeGlobalRefOrDie(env, gFontMetricsInt_class); + + gFontMetricsInt_fieldID.top = GetFieldIDOrDie(env, gFontMetricsInt_class, "top", "I"); + gFontMetricsInt_fieldID.ascent = GetFieldIDOrDie(env, gFontMetricsInt_class, "ascent", "I"); + gFontMetricsInt_fieldID.descent = GetFieldIDOrDie(env, gFontMetricsInt_class, "descent", "I"); + gFontMetricsInt_fieldID.bottom = GetFieldIDOrDie(env, gFontMetricsInt_class, "bottom", "I"); + gFontMetricsInt_fieldID.leading = GetFieldIDOrDie(env, gFontMetricsInt_class, "leading", "I"); + + return RegisterMethodsOrDie(env, "android/graphics/Paint", methods, NELEM(methods)); +} + +} diff --git a/libs/hwui/jni/PaintFilter.cpp b/libs/hwui/jni/PaintFilter.cpp new file mode 100644 index 000000000000..ec115b4e141c --- /dev/null +++ b/libs/hwui/jni/PaintFilter.cpp @@ -0,0 +1,80 @@ +/* libs/android_runtime/android/graphics/ColorFilter.cpp +** +** Copyright 2006, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#include "GraphicsJNI.h" + +#include "hwui/Paint.h" +#include "hwui/PaintFilter.h" +#include "SkPaint.h" + +namespace android { + +class PaintFlagsFilter : public PaintFilter { +public: + PaintFlagsFilter(uint32_t clearFlags, uint32_t setFlags) { + fClearFlags = static_cast<uint16_t>(clearFlags); + fSetFlags = static_cast<uint16_t>(setFlags); + } + void filter(SkPaint* paint) override { + uint32_t flags = Paint::GetSkPaintJavaFlags(*paint); + Paint::SetSkPaintJavaFlags(paint, (flags & ~fClearFlags) | fSetFlags); + } + void filterFullPaint(Paint* paint) override { + paint->setJavaFlags((paint->getJavaFlags() & ~fClearFlags) | fSetFlags); + } + +private: + uint16_t fClearFlags; + uint16_t fSetFlags; +}; + +class PaintFilterGlue { +public: + + static void finalizer(JNIEnv* env, jobject clazz, jlong objHandle) { + PaintFilter* obj = reinterpret_cast<PaintFilter*>(objHandle); + SkSafeUnref(obj); + } + + static jlong CreatePaintFlagsFilter(JNIEnv* env, jobject clazz, + jint clearFlags, jint setFlags) { + PaintFilter* filter = nullptr; + if (clearFlags | setFlags) { + filter = new PaintFlagsFilter(clearFlags, setFlags); + } + return reinterpret_cast<jlong>(filter); + } +}; + +static const JNINativeMethod drawfilter_methods[] = { + {"nativeDestructor", "(J)V", (void*) PaintFilterGlue::finalizer} +}; + +static const JNINativeMethod paintflags_methods[] = { + {"nativeConstructor","(II)J", (void*) PaintFilterGlue::CreatePaintFlagsFilter} +}; + +int register_android_graphics_DrawFilter(JNIEnv* env) { + int result = RegisterMethodsOrDie(env, "android/graphics/DrawFilter", drawfilter_methods, + NELEM(drawfilter_methods)); + result |= RegisterMethodsOrDie(env, "android/graphics/PaintFlagsDrawFilter", paintflags_methods, + NELEM(paintflags_methods)); + + return 0; +} + +} diff --git a/libs/hwui/jni/Path.cpp b/libs/hwui/jni/Path.cpp new file mode 100644 index 000000000000..d67bcf221681 --- /dev/null +++ b/libs/hwui/jni/Path.cpp @@ -0,0 +1,560 @@ +/* libs/android_runtime/android/graphics/Path.cpp +** +** Copyright 2006, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +// This file was generated from the C++ include file: SkPath.h +// Any changes made to this file will be discarded by the build. +// To change this file, either edit the include, or device/tools/gluemaker/main.cpp, +// or one of the auxilary file specifications in device/tools/gluemaker. + +#include "GraphicsJNI.h" + +#include "SkPath.h" +#include "SkPathOps.h" +#include "SkGeometry.h" // WARNING: Internal Skia Header + +#include <vector> +#include <map> + +namespace android { + +class SkPathGlue { +public: + + static void finalizer(SkPath* obj) { + delete obj; + } + + // ---------------- Regular JNI ----------------------------- + + static jlong init(JNIEnv* env, jclass clazz) { + return reinterpret_cast<jlong>(new SkPath()); + } + + static jlong init_Path(JNIEnv* env, jclass clazz, jlong valHandle) { + SkPath* val = reinterpret_cast<SkPath*>(valHandle); + return reinterpret_cast<jlong>(new SkPath(*val)); + } + + static jlong getFinalizer(JNIEnv* env, jclass clazz) { + return static_cast<jlong>(reinterpret_cast<uintptr_t>(&finalizer)); + } + + static void set(JNIEnv* env, jclass clazz, jlong dstHandle, jlong srcHandle) { + SkPath* dst = reinterpret_cast<SkPath*>(dstHandle); + const SkPath* src = reinterpret_cast<SkPath*>(srcHandle); + *dst = *src; + } + + static void computeBounds(JNIEnv* env, jclass clazz, jlong objHandle, jobject jbounds) { + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + const SkRect& bounds = obj->getBounds(); + GraphicsJNI::rect_to_jrectf(bounds, env, jbounds); + } + + static void incReserve(JNIEnv* env, jclass clazz, jlong objHandle, jint extraPtCount) { + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + obj->incReserve(extraPtCount); + } + + static void moveTo__FF(JNIEnv* env, jclass clazz, jlong objHandle, jfloat x, jfloat y) { + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + obj->moveTo(x, y); + } + + static void rMoveTo(JNIEnv* env, jclass clazz, jlong objHandle, jfloat dx, jfloat dy) { + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + obj->rMoveTo(dx, dy); + } + + static void lineTo__FF(JNIEnv* env, jclass clazz, jlong objHandle, jfloat x, jfloat y) { + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + obj->lineTo(x, y); + } + + static void rLineTo(JNIEnv* env, jclass clazz, jlong objHandle, jfloat dx, jfloat dy) { + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + obj->rLineTo(dx, dy); + } + + static void quadTo__FFFF(JNIEnv* env, jclass clazz, jlong objHandle, jfloat x1, jfloat y1, + jfloat x2, jfloat y2) { + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + obj->quadTo(x1, y1, x2, y2); + } + + static void rQuadTo(JNIEnv* env, jclass clazz, jlong objHandle, jfloat dx1, jfloat dy1, + jfloat dx2, jfloat dy2) { + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + obj->rQuadTo(dx1, dy1, dx2, dy2); + } + + static void cubicTo__FFFFFF(JNIEnv* env, jclass clazz, jlong objHandle, jfloat x1, jfloat y1, + jfloat x2, jfloat y2, jfloat x3, jfloat y3) { + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + obj->cubicTo(x1, y1, x2, y2, x3, y3); + } + + static void rCubicTo(JNIEnv* env, jclass clazz, jlong objHandle, jfloat x1, jfloat y1, + jfloat x2, jfloat y2, jfloat x3, jfloat y3) { + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + obj->rCubicTo(x1, y1, x2, y2, x3, y3); + } + + static void arcTo(JNIEnv* env, jclass clazz, jlong objHandle, jfloat left, jfloat top, + jfloat right, jfloat bottom, jfloat startAngle, jfloat sweepAngle, + jboolean forceMoveTo) { + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + SkRect oval = SkRect::MakeLTRB(left, top, right, bottom); + obj->arcTo(oval, startAngle, sweepAngle, forceMoveTo); + } + + static void close(JNIEnv* env, jclass clazz, jlong objHandle) { + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + obj->close(); + } + + static void addRect(JNIEnv* env, jclass clazz, jlong objHandle, + jfloat left, jfloat top, jfloat right, jfloat bottom, jint dirHandle) { + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + SkPathDirection dir = static_cast<SkPathDirection>(dirHandle); + obj->addRect(left, top, right, bottom, dir); + } + + static void addOval(JNIEnv* env, jclass clazz, jlong objHandle, + jfloat left, jfloat top, jfloat right, jfloat bottom, jint dirHandle) { + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + SkPathDirection dir = static_cast<SkPathDirection>(dirHandle); + SkRect oval = SkRect::MakeLTRB(left, top, right, bottom); + obj->addOval(oval, dir); + } + + static void addCircle(JNIEnv* env, jclass clazz, jlong objHandle, jfloat x, jfloat y, + jfloat radius, jint dirHandle) { + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + SkPathDirection dir = static_cast<SkPathDirection>(dirHandle); + obj->addCircle(x, y, radius, dir); + } + + static void addArc(JNIEnv* env, jclass clazz, jlong objHandle, jfloat left, jfloat top, + jfloat right, jfloat bottom, jfloat startAngle, jfloat sweepAngle) { + SkRect oval = SkRect::MakeLTRB(left, top, right, bottom); + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + obj->addArc(oval, startAngle, sweepAngle); + } + + static void addRoundRectXY(JNIEnv* env, jclass clazz, jlong objHandle, jfloat left, jfloat top, + jfloat right, jfloat bottom, jfloat rx, jfloat ry, jint dirHandle) { + SkRect rect = SkRect::MakeLTRB(left, top, right, bottom); + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + SkPathDirection dir = static_cast<SkPathDirection>(dirHandle); + obj->addRoundRect(rect, rx, ry, dir); + } + + static void addRoundRect8(JNIEnv* env, jclass clazz, jlong objHandle, jfloat left, jfloat top, + jfloat right, jfloat bottom, jfloatArray array, jint dirHandle) { + SkRect rect = SkRect::MakeLTRB(left, top, right, bottom); + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + SkPathDirection dir = static_cast<SkPathDirection>(dirHandle); + AutoJavaFloatArray afa(env, array, 8); +#ifdef SK_SCALAR_IS_FLOAT + const float* src = afa.ptr(); +#else + #error Need to convert float array to SkScalar array before calling the following function. +#endif + obj->addRoundRect(rect, src, dir); + } + + static void addPath__PathFF(JNIEnv* env, jclass clazz, jlong objHandle, jlong srcHandle, + jfloat dx, jfloat dy) { + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + SkPath* src = reinterpret_cast<SkPath*>(srcHandle); + obj->addPath(*src, dx, dy); + } + + static void addPath__Path(JNIEnv* env, jclass clazz, jlong objHandle, jlong srcHandle) { + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + SkPath* src = reinterpret_cast<SkPath*>(srcHandle); + obj->addPath(*src); + } + + static void addPath__PathMatrix(JNIEnv* env, jclass clazz, jlong objHandle, jlong srcHandle, + jlong matrixHandle) { + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + SkPath* src = reinterpret_cast<SkPath*>(srcHandle); + SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle); + obj->addPath(*src, *matrix); + } + + static void offset__FF(JNIEnv* env, jclass clazz, jlong objHandle, jfloat dx, jfloat dy) { + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + obj->offset(dx, dy); + } + + static void setLastPoint(JNIEnv* env, jclass clazz, jlong objHandle, jfloat dx, jfloat dy) { + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + obj->setLastPt(dx, dy); + } + + static void transform__MatrixPath(JNIEnv* env, jclass clazz, jlong objHandle, jlong matrixHandle, + jlong dstHandle) { + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle); + SkPath* dst = reinterpret_cast<SkPath*>(dstHandle); + obj->transform(*matrix, dst); + } + + static void transform__Matrix(JNIEnv* env, jclass clazz, jlong objHandle, jlong matrixHandle) { + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle); + obj->transform(*matrix); + } + + static jboolean op(JNIEnv* env, jclass clazz, jlong p1Handle, jlong p2Handle, jint opHandle, + jlong rHandle) { + SkPath* p1 = reinterpret_cast<SkPath*>(p1Handle); + SkPath* p2 = reinterpret_cast<SkPath*>(p2Handle); + SkPathOp op = static_cast<SkPathOp>(opHandle); + SkPath* r = reinterpret_cast<SkPath*>(rHandle); + return Op(*p1, *p2, op, r); + } + + typedef SkPoint (*bezierCalculation)(float t, const SkPoint* points); + + static void addMove(std::vector<SkPoint>& segmentPoints, std::vector<float>& lengths, + const SkPoint& point) { + float length = 0; + if (!lengths.empty()) { + length = lengths.back(); + } + segmentPoints.push_back(point); + lengths.push_back(length); + } + + static void addLine(std::vector<SkPoint>& segmentPoints, std::vector<float>& lengths, + const SkPoint& toPoint) { + if (segmentPoints.empty()) { + segmentPoints.push_back(SkPoint::Make(0, 0)); + lengths.push_back(0); + } else if (segmentPoints.back() == toPoint) { + return; // Empty line + } + float length = lengths.back() + SkPoint::Distance(segmentPoints.back(), toPoint); + segmentPoints.push_back(toPoint); + lengths.push_back(length); + } + + static float cubicCoordinateCalculation(float t, float p0, float p1, float p2, float p3) { + float oneMinusT = 1 - t; + float oneMinusTSquared = oneMinusT * oneMinusT; + float oneMinusTCubed = oneMinusTSquared * oneMinusT; + float tSquared = t * t; + float tCubed = tSquared * t; + return (oneMinusTCubed * p0) + (3 * oneMinusTSquared * t * p1) + + (3 * oneMinusT * tSquared * p2) + (tCubed * p3); + } + + static SkPoint cubicBezierCalculation(float t, const SkPoint* points) { + float x = cubicCoordinateCalculation(t, points[0].x(), points[1].x(), + points[2].x(), points[3].x()); + float y = cubicCoordinateCalculation(t, points[0].y(), points[1].y(), + points[2].y(), points[3].y()); + return SkPoint::Make(x, y); + } + + static float quadraticCoordinateCalculation(float t, float p0, float p1, float p2) { + float oneMinusT = 1 - t; + return oneMinusT * ((oneMinusT * p0) + (t * p1)) + t * ((oneMinusT * p1) + (t * p2)); + } + + static SkPoint quadraticBezierCalculation(float t, const SkPoint* points) { + float x = quadraticCoordinateCalculation(t, points[0].x(), points[1].x(), points[2].x()); + float y = quadraticCoordinateCalculation(t, points[0].y(), points[1].y(), points[2].y()); + return SkPoint::Make(x, y); + } + + // Subdivide a section of the Bezier curve, set the mid-point and the mid-t value. + // Returns true if further subdivision is necessary as defined by errorSquared. + static bool subdividePoints(const SkPoint* points, bezierCalculation bezierFunction, + float t0, const SkPoint &p0, float t1, const SkPoint &p1, + float& midT, SkPoint &midPoint, float errorSquared) { + midT = (t1 + t0) / 2; + float midX = (p1.x() + p0.x()) / 2; + float midY = (p1.y() + p0.y()) / 2; + + midPoint = (*bezierFunction)(midT, points); + float xError = midPoint.x() - midX; + float yError = midPoint.y() - midY; + float midErrorSquared = (xError * xError) + (yError * yError); + return midErrorSquared > errorSquared; + } + + // Divides Bezier curves until linear interpolation is very close to accurate, using + // errorSquared as a metric. Cubic Bezier curves can have an inflection point that improperly + // short-circuit subdivision. If you imagine an S shape, the top and bottom points being the + // starting and end points, linear interpolation would mark the center where the curve places + // the point. It is clearly not the case that we can linearly interpolate at that point. + // doubleCheckDivision forces a second examination between subdivisions to ensure that linear + // interpolation works. + static void addBezier(const SkPoint* points, + bezierCalculation bezierFunction, std::vector<SkPoint>& segmentPoints, + std::vector<float>& lengths, float errorSquared, bool doubleCheckDivision) { + typedef std::map<float, SkPoint> PointMap; + PointMap tToPoint; + + tToPoint[0] = (*bezierFunction)(0, points); + tToPoint[1] = (*bezierFunction)(1, points); + + PointMap::iterator iter = tToPoint.begin(); + PointMap::iterator next = iter; + ++next; + while (next != tToPoint.end()) { + bool needsSubdivision = true; + SkPoint midPoint; + do { + float midT; + needsSubdivision = subdividePoints(points, bezierFunction, iter->first, + iter->second, next->first, next->second, midT, midPoint, errorSquared); + if (!needsSubdivision && doubleCheckDivision) { + SkPoint quarterPoint; + float quarterT; + needsSubdivision = subdividePoints(points, bezierFunction, iter->first, + iter->second, midT, midPoint, quarterT, quarterPoint, errorSquared); + if (needsSubdivision) { + // Found an inflection point. No need to double-check. + doubleCheckDivision = false; + } + } + if (needsSubdivision) { + next = tToPoint.insert(iter, PointMap::value_type(midT, midPoint)); + } + } while (needsSubdivision); + iter = next; + next++; + } + + // Now that each division can use linear interpolation with less than the allowed error + for (iter = tToPoint.begin(); iter != tToPoint.end(); ++iter) { + addLine(segmentPoints, lengths, iter->second); + } + } + + static void createVerbSegments(const SkPath::Iter& pathIter, SkPath::Verb verb, + const SkPoint* points, std::vector<SkPoint>& segmentPoints, + std::vector<float>& lengths, float errorSquared, float errorConic) { + switch (verb) { + case SkPath::kMove_Verb: + addMove(segmentPoints, lengths, points[0]); + break; + case SkPath::kClose_Verb: + addLine(segmentPoints, lengths, points[0]); + break; + case SkPath::kLine_Verb: + addLine(segmentPoints, lengths, points[1]); + break; + case SkPath::kQuad_Verb: + addBezier(points, quadraticBezierCalculation, segmentPoints, lengths, + errorSquared, false); + break; + case SkPath::kCubic_Verb: + addBezier(points, cubicBezierCalculation, segmentPoints, lengths, + errorSquared, true); + break; + case SkPath::kConic_Verb: { + SkAutoConicToQuads converter; + const SkPoint* quads = converter.computeQuads( + points, pathIter.conicWeight(), errorConic); + for (int i = 0; i < converter.countQuads(); i++) { + // Note: offset each subsequent quad by 2, since end points are shared + const SkPoint* quad = quads + i * 2; + addBezier(quad, quadraticBezierCalculation, segmentPoints, lengths, + errorConic, false); + } + break; + } + default: + static_assert(SkPath::kMove_Verb == 0 + && SkPath::kLine_Verb == 1 + && SkPath::kQuad_Verb == 2 + && SkPath::kConic_Verb == 3 + && SkPath::kCubic_Verb == 4 + && SkPath::kClose_Verb == 5 + && SkPath::kDone_Verb == 6, + "Path enum changed, new types may have been added."); + break; + } + } + + // Returns a float[] with each point along the path represented by 3 floats + // * fractional length along the path that the point resides + // * x coordinate + // * y coordinate + // Note that more than one point may have the same length along the path in + // the case of a move. + // NULL can be returned if the Path is empty. + static jfloatArray approximate(JNIEnv* env, jclass clazz, jlong pathHandle, + float acceptableError) { + SkPath* path = reinterpret_cast<SkPath*>(pathHandle); + SkASSERT(path); + SkPath::Iter pathIter(*path, false); + SkPath::Verb verb; + SkPoint points[4]; + std::vector<SkPoint> segmentPoints; + std::vector<float> lengths; + float errorSquared = acceptableError * acceptableError; + float errorConic = acceptableError / 2; // somewhat arbitrary + + while ((verb = pathIter.next(points)) != SkPath::kDone_Verb) { + createVerbSegments(pathIter, verb, points, segmentPoints, lengths, + errorSquared, errorConic); + } + + if (segmentPoints.empty()) { + int numVerbs = path->countVerbs(); + if (numVerbs == 1) { + addMove(segmentPoints, lengths, path->getPoint(0)); + } else { + // Invalid or empty path. Fall back to point(0,0) + addMove(segmentPoints, lengths, SkPoint()); + } + } + + float totalLength = lengths.back(); + if (totalLength == 0) { + // Lone Move instructions should still be able to animate at the same value. + segmentPoints.push_back(segmentPoints.back()); + lengths.push_back(1); + totalLength = 1; + } + + size_t numPoints = segmentPoints.size(); + size_t approximationArraySize = numPoints * 3; + + float* approximation = new float[approximationArraySize]; + + int approximationIndex = 0; + for (size_t i = 0; i < numPoints; i++) { + const SkPoint& point = segmentPoints[i]; + approximation[approximationIndex++] = lengths[i] / totalLength; + approximation[approximationIndex++] = point.x(); + approximation[approximationIndex++] = point.y(); + } + + jfloatArray result = env->NewFloatArray(approximationArraySize); + env->SetFloatArrayRegion(result, 0, approximationArraySize, approximation); + delete[] approximation; + return result; + } + + // ---------------- @FastNative ----------------------------- + + static jboolean isRect(JNIEnv* env, jclass clazz, jlong objHandle, jobject jrect) { + SkRect rect; + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + jboolean result = obj->isRect(&rect); + if (jrect) { + GraphicsJNI::rect_to_jrectf(rect, env, jrect); + } + return result; + } + + // ---------------- @CriticalNative ------------------------- + + static void reset(CRITICAL_JNI_PARAMS_COMMA jlong objHandle) { + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + obj->reset(); + } + + static void rewind(CRITICAL_JNI_PARAMS_COMMA jlong objHandle) { + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + obj->rewind(); + } + + static jboolean isEmpty(CRITICAL_JNI_PARAMS_COMMA jlong objHandle) { + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + return obj->isEmpty(); + } + + static jboolean isConvex(CRITICAL_JNI_PARAMS_COMMA jlong objHandle) { + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + return obj->isConvex(); + } + + static jint getFillType(CRITICAL_JNI_PARAMS_COMMA jlong objHandle) { + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + return static_cast<int>(obj->getFillType()); + } + + static void setFillType(CRITICAL_JNI_PARAMS_COMMA jlong pathHandle, jint ftHandle) {; + SkPath* path = reinterpret_cast<SkPath*>(pathHandle); + SkPathFillType ft = static_cast<SkPathFillType>(ftHandle); + path->setFillType(ft); + } +}; + +static const JNINativeMethod methods[] = { + {"nInit","()J", (void*) SkPathGlue::init}, + {"nInit","(J)J", (void*) SkPathGlue::init_Path}, + {"nGetFinalizer", "()J", (void*) SkPathGlue::getFinalizer}, + {"nSet","(JJ)V", (void*) SkPathGlue::set}, + {"nComputeBounds","(JLandroid/graphics/RectF;)V", (void*) SkPathGlue::computeBounds}, + {"nIncReserve","(JI)V", (void*) SkPathGlue::incReserve}, + {"nMoveTo","(JFF)V", (void*) SkPathGlue::moveTo__FF}, + {"nRMoveTo","(JFF)V", (void*) SkPathGlue::rMoveTo}, + {"nLineTo","(JFF)V", (void*) SkPathGlue::lineTo__FF}, + {"nRLineTo","(JFF)V", (void*) SkPathGlue::rLineTo}, + {"nQuadTo","(JFFFF)V", (void*) SkPathGlue::quadTo__FFFF}, + {"nRQuadTo","(JFFFF)V", (void*) SkPathGlue::rQuadTo}, + {"nCubicTo","(JFFFFFF)V", (void*) SkPathGlue::cubicTo__FFFFFF}, + {"nRCubicTo","(JFFFFFF)V", (void*) SkPathGlue::rCubicTo}, + {"nArcTo","(JFFFFFFZ)V", (void*) SkPathGlue::arcTo}, + {"nClose","(J)V", (void*) SkPathGlue::close}, + {"nAddRect","(JFFFFI)V", (void*) SkPathGlue::addRect}, + {"nAddOval","(JFFFFI)V", (void*) SkPathGlue::addOval}, + {"nAddCircle","(JFFFI)V", (void*) SkPathGlue::addCircle}, + {"nAddArc","(JFFFFFF)V", (void*) SkPathGlue::addArc}, + {"nAddRoundRect","(JFFFFFFI)V", (void*) SkPathGlue::addRoundRectXY}, + {"nAddRoundRect","(JFFFF[FI)V", (void*) SkPathGlue::addRoundRect8}, + {"nAddPath","(JJFF)V", (void*) SkPathGlue::addPath__PathFF}, + {"nAddPath","(JJ)V", (void*) SkPathGlue::addPath__Path}, + {"nAddPath","(JJJ)V", (void*) SkPathGlue::addPath__PathMatrix}, + {"nOffset","(JFF)V", (void*) SkPathGlue::offset__FF}, + {"nSetLastPoint","(JFF)V", (void*) SkPathGlue::setLastPoint}, + {"nTransform","(JJJ)V", (void*) SkPathGlue::transform__MatrixPath}, + {"nTransform","(JJ)V", (void*) SkPathGlue::transform__Matrix}, + {"nOp","(JJIJ)Z", (void*) SkPathGlue::op}, + {"nApproximate", "(JF)[F", (void*) SkPathGlue::approximate}, + + // ------- @FastNative below here ---------------------- + {"nIsRect","(JLandroid/graphics/RectF;)Z", (void*) SkPathGlue::isRect}, + + // ------- @CriticalNative below here ------------------ + {"nReset","(J)V", (void*) SkPathGlue::reset}, + {"nRewind","(J)V", (void*) SkPathGlue::rewind}, + {"nIsEmpty","(J)Z", (void*) SkPathGlue::isEmpty}, + {"nIsConvex","(J)Z", (void*) SkPathGlue::isConvex}, + {"nGetFillType","(J)I", (void*) SkPathGlue::getFillType}, + {"nSetFillType","(JI)V", (void*) SkPathGlue::setFillType}, +}; + +int register_android_graphics_Path(JNIEnv* env) { + return RegisterMethodsOrDie(env, "android/graphics/Path", methods, NELEM(methods)); + + static_assert(0 == (int)SkPathDirection::kCW, "direction_mismatch"); + static_assert(1 == (int)SkPathDirection::kCCW, "direction_mismatch"); +} + +} diff --git a/libs/hwui/jni/PathEffect.cpp b/libs/hwui/jni/PathEffect.cpp new file mode 100644 index 000000000000..f99bef7b7d58 --- /dev/null +++ b/libs/hwui/jni/PathEffect.cpp @@ -0,0 +1,117 @@ +#include "GraphicsJNI.h" +#include "Sk1DPathEffect.h" +#include "SkCornerPathEffect.h" +#include "SkDashPathEffect.h" +#include "SkDiscretePathEffect.h" +#include "SkPathEffect.h" + +class SkPathEffectGlue { +public: + + static void destructor(JNIEnv* env, jobject, jlong effectHandle) { + SkPathEffect* effect = reinterpret_cast<SkPathEffect*>(effectHandle); + SkSafeUnref(effect); + } + + static jlong Compose_constructor(JNIEnv* env, jobject, + jlong outerHandle, jlong innerHandle) { + SkPathEffect* outer = reinterpret_cast<SkPathEffect*>(outerHandle); + SkPathEffect* inner = reinterpret_cast<SkPathEffect*>(innerHandle); + SkPathEffect* effect = SkPathEffect::MakeCompose(sk_ref_sp(outer), + sk_ref_sp(inner)).release(); + return reinterpret_cast<jlong>(effect); + } + + static jlong Sum_constructor(JNIEnv* env, jobject, + jlong firstHandle, jlong secondHandle) { + SkPathEffect* first = reinterpret_cast<SkPathEffect*>(firstHandle); + SkPathEffect* second = reinterpret_cast<SkPathEffect*>(secondHandle); + SkPathEffect* effect = SkPathEffect::MakeSum(sk_ref_sp(first), + sk_ref_sp(second)).release(); + return reinterpret_cast<jlong>(effect); + } + + static jlong Dash_constructor(JNIEnv* env, jobject, + jfloatArray intervalArray, jfloat phase) { + AutoJavaFloatArray autoInterval(env, intervalArray); + int count = autoInterval.length() & ~1; // even number +#ifdef SK_SCALAR_IS_FLOAT + SkScalar* intervals = autoInterval.ptr(); +#else + #error Need to convert float array to SkScalar array before calling the following function. +#endif + SkPathEffect* effect = SkDashPathEffect::Make(intervals, count, phase).release(); + return reinterpret_cast<jlong>(effect); + } + + static jlong OneD_constructor(JNIEnv* env, jobject, + jlong shapeHandle, jfloat advance, jfloat phase, jint style) { + const SkPath* shape = reinterpret_cast<SkPath*>(shapeHandle); + SkASSERT(shape != NULL); + SkPathEffect* effect = SkPath1DPathEffect::Make(*shape, advance, phase, + (SkPath1DPathEffect::Style)style).release(); + return reinterpret_cast<jlong>(effect); + } + + static jlong Corner_constructor(JNIEnv* env, jobject, jfloat radius){ + SkPathEffect* effect = SkCornerPathEffect::Make(radius).release(); + return reinterpret_cast<jlong>(effect); + } + + static jlong Discrete_constructor(JNIEnv* env, jobject, + jfloat length, jfloat deviation) { + SkPathEffect* effect = SkDiscretePathEffect::Make(length, deviation).release(); + return reinterpret_cast<jlong>(effect); + } + +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////// + +static const JNINativeMethod gPathEffectMethods[] = { + { "nativeDestructor", "(J)V", (void*)SkPathEffectGlue::destructor } +}; + +static const JNINativeMethod gComposePathEffectMethods[] = { + { "nativeCreate", "(JJ)J", (void*)SkPathEffectGlue::Compose_constructor } +}; + +static const JNINativeMethod gSumPathEffectMethods[] = { + { "nativeCreate", "(JJ)J", (void*)SkPathEffectGlue::Sum_constructor } +}; + +static const JNINativeMethod gDashPathEffectMethods[] = { + { "nativeCreate", "([FF)J", (void*)SkPathEffectGlue::Dash_constructor } +}; + +static const JNINativeMethod gPathDashPathEffectMethods[] = { + { "nativeCreate", "(JFFI)J", (void*)SkPathEffectGlue::OneD_constructor } +}; + +static const JNINativeMethod gCornerPathEffectMethods[] = { + { "nativeCreate", "(F)J", (void*)SkPathEffectGlue::Corner_constructor } +}; + +static const JNINativeMethod gDiscretePathEffectMethods[] = { + { "nativeCreate", "(FF)J", (void*)SkPathEffectGlue::Discrete_constructor } +}; + +int register_android_graphics_PathEffect(JNIEnv* env) +{ + android::RegisterMethodsOrDie(env, "android/graphics/PathEffect", gPathEffectMethods, + NELEM(gPathEffectMethods)); + android::RegisterMethodsOrDie(env, "android/graphics/ComposePathEffect", + gComposePathEffectMethods, NELEM(gComposePathEffectMethods)); + android::RegisterMethodsOrDie(env, "android/graphics/SumPathEffect", gSumPathEffectMethods, + NELEM(gSumPathEffectMethods)); + android::RegisterMethodsOrDie(env, "android/graphics/DashPathEffect", gDashPathEffectMethods, + NELEM(gDashPathEffectMethods)); + android::RegisterMethodsOrDie(env, "android/graphics/PathDashPathEffect", + gPathDashPathEffectMethods, NELEM(gPathDashPathEffectMethods)); + android::RegisterMethodsOrDie(env, "android/graphics/CornerPathEffect", + gCornerPathEffectMethods, NELEM(gCornerPathEffectMethods)); + android::RegisterMethodsOrDie(env, "android/graphics/DiscretePathEffect", + gDiscretePathEffectMethods, NELEM(gDiscretePathEffectMethods)); + + return 0; +} diff --git a/libs/hwui/jni/PathMeasure.cpp b/libs/hwui/jni/PathMeasure.cpp new file mode 100644 index 000000000000..acf893e9544c --- /dev/null +++ b/libs/hwui/jni/PathMeasure.cpp @@ -0,0 +1,160 @@ +/* libs/android_runtime/android/graphics/PathMeasure.cpp +** +** Copyright 2007, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#include "GraphicsJNI.h" + +#include "SkPathMeasure.h" + +/* We declare an explicit pair, so that we don't have to rely on the java + client to be sure not to edit the path while we have an active measure + object associated with it. + + This costs us the copy of the path, for the sake of not allowing a bad + java client to randomly crash (since we can't detect the case where the + native path has been modified). + + The C side does have this risk, but it chooses for speed over safety. If it + later changes this, and is internally safe from changes to the path, then + we can remove this explicit copy from our JNI code. + + Note that we do not have a reference on the java side to the java path. + Were we to not need the native copy here, we would want to add a java + reference, so that the java path would not get GD'd while the measure object + was still alive. +*/ +struct PathMeasurePair { + PathMeasurePair() {} + PathMeasurePair(const SkPath& path, bool forceClosed) + : fPath(path), fMeasure(fPath, forceClosed) {} + + SkPath fPath; // copy of the user's path + SkPathMeasure fMeasure; // this guy points to fPath +}; + +namespace android { + +class SkPathMeasureGlue { +public: + + static jlong create(JNIEnv* env, jobject clazz, jlong pathHandle, + jboolean forceClosedHandle) { + const SkPath* path = reinterpret_cast<SkPath*>(pathHandle); + bool forceClosed = (forceClosedHandle == JNI_TRUE); + PathMeasurePair* pair; + if(path) + pair = new PathMeasurePair(*path, forceClosed); + else + pair = new PathMeasurePair; + return reinterpret_cast<jlong>(pair); + } + + static void setPath(JNIEnv* env, jobject clazz, jlong pairHandle, + jlong pathHandle, jboolean forceClosedHandle) { + PathMeasurePair* pair = reinterpret_cast<PathMeasurePair*>(pairHandle); + const SkPath* path = reinterpret_cast<SkPath*>(pathHandle); + bool forceClosed = (forceClosedHandle == JNI_TRUE); + + if (NULL == path) { + pair->fPath.reset(); + } else { + pair->fPath = *path; + } + pair->fMeasure.setPath(&pair->fPath, forceClosed); + } + + static jfloat getLength(JNIEnv* env, jobject clazz, jlong pairHandle) { + PathMeasurePair* pair = reinterpret_cast<PathMeasurePair*>(pairHandle); + return static_cast<jfloat>(SkScalarToFloat(pair->fMeasure.getLength())); + } + + static void convertTwoElemFloatArray(JNIEnv* env, jfloatArray array, const SkScalar src[2]) { + AutoJavaFloatArray autoArray(env, array, 2); + jfloat* ptr = autoArray.ptr(); + ptr[0] = SkScalarToFloat(src[0]); + ptr[1] = SkScalarToFloat(src[1]); + } + + static jboolean getPosTan(JNIEnv* env, jobject clazz, jlong pairHandle, jfloat dist, jfloatArray pos, jfloatArray tan) { + PathMeasurePair* pair = reinterpret_cast<PathMeasurePair*>(pairHandle); + SkScalar tmpPos[2], tmpTan[2]; + SkScalar* posPtr = pos ? tmpPos : NULL; + SkScalar* tanPtr = tan ? tmpTan : NULL; + + if (!pair->fMeasure.getPosTan(dist, (SkPoint*)posPtr, (SkVector*)tanPtr)) { + return JNI_FALSE; + } + + if (pos) { + convertTwoElemFloatArray(env, pos, tmpPos); + } + if (tan) { + convertTwoElemFloatArray(env, tan, tmpTan); + } + return JNI_TRUE; + } + + static jboolean getMatrix(JNIEnv* env, jobject clazz, jlong pairHandle, jfloat dist, + jlong matrixHandle, jint flags) { + PathMeasurePair* pair = reinterpret_cast<PathMeasurePair*>(pairHandle); + SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle); + bool result = pair->fMeasure.getMatrix(dist, matrix, (SkPathMeasure::MatrixFlags)flags); + return result ? JNI_TRUE : JNI_FALSE; + } + + static jboolean getSegment(JNIEnv* env, jobject clazz, jlong pairHandle, jfloat startF, + jfloat stopF, jlong dstHandle, jboolean startWithMoveTo) { + PathMeasurePair* pair = reinterpret_cast<PathMeasurePair*>(pairHandle); + SkPath* dst = reinterpret_cast<SkPath*>(dstHandle); + bool result = pair->fMeasure.getSegment(startF, stopF, dst, startWithMoveTo); + return result ? JNI_TRUE : JNI_FALSE; + } + + static jboolean isClosed(JNIEnv* env, jobject clazz, jlong pairHandle) { + PathMeasurePair* pair = reinterpret_cast<PathMeasurePair*>(pairHandle); + bool result = pair->fMeasure.isClosed(); + return result ? JNI_TRUE : JNI_FALSE; + } + + static jboolean nextContour(JNIEnv* env, jobject clazz, jlong pairHandle) { + PathMeasurePair* pair = reinterpret_cast<PathMeasurePair*>(pairHandle); + bool result = pair->fMeasure.nextContour(); + return result ? JNI_TRUE : JNI_FALSE; + } + + static void destroy(JNIEnv* env, jobject clazz, jlong pairHandle) { + PathMeasurePair* pair = reinterpret_cast<PathMeasurePair*>(pairHandle); + delete pair; + } +}; + +static const JNINativeMethod methods[] = { + {"native_create", "(JZ)J", (void*) SkPathMeasureGlue::create }, + {"native_setPath", "(JJZ)V", (void*) SkPathMeasureGlue::setPath }, + {"native_getLength", "(J)F", (void*) SkPathMeasureGlue::getLength }, + {"native_getPosTan", "(JF[F[F)Z", (void*) SkPathMeasureGlue::getPosTan }, + {"native_getMatrix", "(JFJI)Z", (void*) SkPathMeasureGlue::getMatrix }, + {"native_getSegment", "(JFFJZ)Z", (void*) SkPathMeasureGlue::getSegment }, + {"native_isClosed", "(J)Z", (void*) SkPathMeasureGlue::isClosed }, + {"native_nextContour", "(J)Z", (void*) SkPathMeasureGlue::nextContour }, + {"native_destroy", "(J)V", (void*) SkPathMeasureGlue::destroy } +}; + +int register_android_graphics_PathMeasure(JNIEnv* env) { + return RegisterMethodsOrDie(env, "android/graphics/PathMeasure", methods, NELEM(methods)); +} + +} diff --git a/libs/hwui/jni/Picture.cpp b/libs/hwui/jni/Picture.cpp new file mode 100644 index 000000000000..d1b952130e88 --- /dev/null +++ b/libs/hwui/jni/Picture.cpp @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Picture.h" +#include "SkStream.h" + +#include <memory> +#include <hwui/Canvas.h> + +namespace android { + +Picture::Picture(const Picture* src) { + if (NULL != src) { + mWidth = src->width(); + mHeight = src->height(); + if (NULL != src->mPicture.get()) { + mPicture = src->mPicture; + } else if (NULL != src->mRecorder.get()) { + mPicture = src->makePartialCopy(); + } + } else { + mWidth = 0; + mHeight = 0; + } +} + +Picture::Picture(sk_sp<SkPicture>&& src) { + mPicture = std::move(src); + mWidth = 0; + mHeight = 0; +} + +Canvas* Picture::beginRecording(int width, int height) { + mPicture.reset(NULL); + mRecorder.reset(new SkPictureRecorder); + mWidth = width; + mHeight = height; + SkCanvas* canvas = mRecorder->beginRecording(SkIntToScalar(width), SkIntToScalar(height)); + return Canvas::create_canvas(canvas); +} + +void Picture::endRecording() { + if (NULL != mRecorder.get()) { + mPicture = mRecorder->finishRecordingAsPicture(); + mRecorder.reset(NULL); + } +} + +int Picture::width() const { + return mWidth; +} + +int Picture::height() const { + return mHeight; +} + +Picture* Picture::CreateFromStream(SkStream* stream) { + Picture* newPict = new Picture; + + sk_sp<SkPicture> skPicture = SkPicture::MakeFromStream(stream); + if (NULL != skPicture) { + newPict->mPicture = skPicture; + + const SkIRect cullRect = skPicture->cullRect().roundOut(); + newPict->mWidth = cullRect.width(); + newPict->mHeight = cullRect.height(); + } + + return newPict; +} + +void Picture::serialize(SkWStream* stream) const { + if (NULL != mRecorder.get()) { + this->makePartialCopy()->serialize(stream); + } else if (NULL != mPicture.get()) { + mPicture->serialize(stream); + } else { + // serialize "empty" picture + SkPictureRecorder recorder; + recorder.beginRecording(0, 0); + recorder.finishRecordingAsPicture()->serialize(stream); + } +} + +void Picture::draw(Canvas* canvas) { + if (NULL != mRecorder.get()) { + this->endRecording(); + SkASSERT(NULL != mPicture.get()); + } + + if (mPicture) { + canvas->drawPicture(*mPicture); + } +} + +sk_sp<SkPicture> Picture::makePartialCopy() const { + SkASSERT(NULL != mRecorder.get()); + + SkPictureRecorder reRecorder; + + SkCanvas* canvas = reRecorder.beginRecording(mWidth, mHeight, NULL, 0); + mRecorder->partialReplay(canvas); + return reRecorder.finishRecordingAsPicture(); +} + +}; // namespace android diff --git a/libs/hwui/jni/Picture.h b/libs/hwui/jni/Picture.h new file mode 100644 index 000000000000..536f651473a9 --- /dev/null +++ b/libs/hwui/jni/Picture.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_GRAPHICS_PICTURE_H_ +#define ANDROID_GRAPHICS_PICTURE_H_ + +#include "SkPicture.h" +#include "SkPictureRecorder.h" +#include "SkRefCnt.h" + +#include <memory> + +class SkStream; +class SkWStream; + +namespace android { + +class Canvas; + +// Skia's SkPicture class has been split into an SkPictureRecorder +// and an SkPicture. AndroidPicture recreates the functionality +// of the old SkPicture interface by flip-flopping between the two +// new classes. +class Picture { +public: + explicit Picture(const Picture* src = NULL); + explicit Picture(sk_sp<SkPicture>&& src); + + Canvas* beginRecording(int width, int height); + + void endRecording(); + + int width() const; + + int height() const; + + static Picture* CreateFromStream(SkStream* stream); + + void serialize(SkWStream* stream) const; + + void draw(Canvas* canvas); + +private: + int mWidth; + int mHeight; + sk_sp<SkPicture> mPicture; + std::unique_ptr<SkPictureRecorder> mRecorder; + + // Make a copy of a picture that is in the midst of being recorded. The + // resulting picture will have balanced saves and restores. + sk_sp<SkPicture> makePartialCopy() const; +}; + +}; // namespace android +#endif // ANDROID_GRAPHICS_PICTURE_H_ diff --git a/libs/hwui/jni/Region.cpp b/libs/hwui/jni/Region.cpp new file mode 100644 index 000000000000..1e064b820591 --- /dev/null +++ b/libs/hwui/jni/Region.cpp @@ -0,0 +1,359 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "SkRegion.h" +#include "SkPath.h" +#include "GraphicsJNI.h" + +#ifdef __ANDROID__ // Layoutlib does not support parcel +#include <android/binder_parcel.h> +#include <android/binder_parcel_jni.h> +#include <android/binder_parcel_utils.h> +#endif + +namespace android { + +static jfieldID gRegion_nativeInstanceFieldID; + +static inline jboolean boolTojboolean(bool value) { + return value ? JNI_TRUE : JNI_FALSE; +} + +static inline SkRegion* GetSkRegion(JNIEnv* env, jobject regionObject) { + jlong regionHandle = env->GetLongField(regionObject, gRegion_nativeInstanceFieldID); + SkRegion* region = reinterpret_cast<SkRegion*>(regionHandle); + SkASSERT(region != NULL); + return region; +} + +static jlong Region_constructor(JNIEnv* env, jobject) { + return reinterpret_cast<jlong>(new SkRegion); +} + +static void Region_destructor(JNIEnv* env, jobject, jlong regionHandle) { + SkRegion* region = reinterpret_cast<SkRegion*>(regionHandle); + SkASSERT(region); + delete region; +} + +static void Region_setRegion(JNIEnv* env, jobject, jlong dstHandle, jlong srcHandle) { + SkRegion* dst = reinterpret_cast<SkRegion*>(dstHandle); + const SkRegion* src = reinterpret_cast<SkRegion*>(srcHandle); + SkASSERT(dst && src); + *dst = *src; +} + +static jboolean Region_setRect(JNIEnv* env, jobject, jlong dstHandle, jint left, jint top, jint right, jint bottom) { + SkRegion* dst = reinterpret_cast<SkRegion*>(dstHandle); + bool result = dst->setRect({left, top, right, bottom}); + return boolTojboolean(result); +} + +static jboolean Region_setPath(JNIEnv* env, jobject, jlong dstHandle, + jlong pathHandle, jlong clipHandle) { + SkRegion* dst = reinterpret_cast<SkRegion*>(dstHandle); + const SkPath* path = reinterpret_cast<SkPath*>(pathHandle); + const SkRegion* clip = reinterpret_cast<SkRegion*>(clipHandle); + SkASSERT(dst && path && clip); + bool result = dst->setPath(*path, *clip); + return boolTojboolean(result); + +} + +static jboolean Region_getBounds(JNIEnv* env, jobject, jlong regionHandle, jobject rectBounds) { + SkRegion* region = reinterpret_cast<SkRegion*>(regionHandle); + GraphicsJNI::irect_to_jrect(region->getBounds(), env, rectBounds); + bool result = !region->isEmpty(); + return boolTojboolean(result); +} + +static jboolean Region_getBoundaryPath(JNIEnv* env, jobject, jlong regionHandle, jlong pathHandle) { + const SkRegion* region = reinterpret_cast<SkRegion*>(regionHandle); + SkPath* path = reinterpret_cast<SkPath*>(pathHandle); + bool result = region->getBoundaryPath(path); + return boolTojboolean(result); +} + +static jboolean Region_op0(JNIEnv* env, jobject, jlong dstHandle, jint left, jint top, jint right, jint bottom, jint op) { + SkRegion* dst = reinterpret_cast<SkRegion*>(dstHandle); + bool result = dst->op({left, top, right, bottom}, (SkRegion::Op)op); + return boolTojboolean(result); +} + +static jboolean Region_op1(JNIEnv* env, jobject, jlong dstHandle, jobject rectObject, jlong regionHandle, jint op) { + SkRegion* dst = reinterpret_cast<SkRegion*>(dstHandle); + const SkRegion* region = reinterpret_cast<SkRegion*>(regionHandle); + SkIRect ir; + GraphicsJNI::jrect_to_irect(env, rectObject, &ir); + bool result = dst->op(ir, *region, (SkRegion::Op)op); + return boolTojboolean(result); +} + +static jboolean Region_op2(JNIEnv* env, jobject, jlong dstHandle, jlong region1Handle, jlong region2Handle, jint op) { + SkRegion* dst = reinterpret_cast<SkRegion*>(dstHandle); + const SkRegion* region1 = reinterpret_cast<SkRegion*>(region1Handle); + const SkRegion* region2 = reinterpret_cast<SkRegion*>(region2Handle); + bool result = dst->op(*region1, *region2, (SkRegion::Op)op); + return boolTojboolean(result); +} + +//////////////////////////////////// These are methods, not static + +static jboolean Region_isEmpty(JNIEnv* env, jobject region) { + bool result = GetSkRegion(env, region)->isEmpty(); + return boolTojboolean(result); +} + +static jboolean Region_isRect(JNIEnv* env, jobject region) { + bool result = GetSkRegion(env, region)->isRect(); + return boolTojboolean(result); +} + +static jboolean Region_isComplex(JNIEnv* env, jobject region) { + bool result = GetSkRegion(env, region)->isComplex(); + return boolTojboolean(result); +} + +static jboolean Region_contains(JNIEnv* env, jobject region, jint x, jint y) { + bool result = GetSkRegion(env, region)->contains(x, y); + return boolTojboolean(result); +} + +static jboolean Region_quickContains(JNIEnv* env, jobject region, jint left, jint top, jint right, jint bottom) { + bool result = GetSkRegion(env, region)->quickContains({left, top, right, bottom}); + return boolTojboolean(result); +} + +static jboolean Region_quickRejectIIII(JNIEnv* env, jobject region, jint left, jint top, jint right, jint bottom) { + SkIRect ir; + ir.setLTRB(left, top, right, bottom); + bool result = GetSkRegion(env, region)->quickReject(ir); + return boolTojboolean(result); +} + +static jboolean Region_quickRejectRgn(JNIEnv* env, jobject region, jobject other) { + bool result = GetSkRegion(env, region)->quickReject(*GetSkRegion(env, other)); + return boolTojboolean(result); +} + +static void Region_translate(JNIEnv* env, jobject region, jint x, jint y, jobject dst) { + SkRegion* rgn = GetSkRegion(env, region); + if (dst) + rgn->translate(x, y, GetSkRegion(env, dst)); + else + rgn->translate(x, y); +} + +// Scale the rectangle by given scale and set the reuslt to the dst. +static void scale_rect(SkIRect* dst, const SkIRect& src, float scale) { + dst->fLeft = (int)::roundf(src.fLeft * scale); + dst->fTop = (int)::roundf(src.fTop * scale); + dst->fRight = (int)::roundf(src.fRight * scale); + dst->fBottom = (int)::roundf(src.fBottom * scale); +} + +// Scale the region by given scale and set the reuslt to the dst. +// dest and src can be the same region instance. +static void scale_rgn(SkRegion* dst, const SkRegion& src, float scale) { + SkRegion tmp; + SkRegion::Iterator iter(src); + + for (; !iter.done(); iter.next()) { + SkIRect r; + scale_rect(&r, iter.rect(), scale); + tmp.op(r, SkRegion::kUnion_Op); + } + dst->swap(tmp); +} + +static void Region_scale(JNIEnv* env, jobject region, jfloat scale, jobject dst) { + SkRegion* rgn = GetSkRegion(env, region); + if (dst) + scale_rgn(GetSkRegion(env, dst), *rgn, scale); + else + scale_rgn(rgn, *rgn, scale); +} + +static jstring Region_toString(JNIEnv* env, jobject clazz, jlong regionHandle) { + SkRegion* region = reinterpret_cast<SkRegion*>(regionHandle); + char* str = region->toString(); + if (str == NULL) { + return NULL; + } + jstring result = env->NewStringUTF(str); + free(str); + return result; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////// + +static jlong Region_createFromParcel(JNIEnv* env, jobject clazz, jobject parcel) +{ +#ifdef __ANDROID__ // Layoutlib does not support parcel + if (parcel == nullptr) { + return 0; + } + + std::vector<int32_t> rects; + + AParcel* p = AParcel_fromJavaParcel(env, parcel); + ndk::AParcel_readVector(p, &rects); + AParcel_delete(p); + + if ((rects.size() % 4) != 0) { + return 0; + } + + SkRegion* region = new SkRegion; + for (size_t x = 0; x + 4 <= rects.size(); x += 4) { + region->op({rects[x], rects[x+1], rects[x+2], rects[x+3]}, SkRegion::kUnion_Op); + } + + return reinterpret_cast<jlong>(region); +#else + return 0; +#endif +} + +static jboolean Region_writeToParcel(JNIEnv* env, jobject clazz, jlong regionHandle, jobject parcel) +{ +#ifdef __ANDROID__ // Layoutlib does not support parcel + const SkRegion* region = reinterpret_cast<SkRegion*>(regionHandle); + if (parcel == nullptr) { + return JNI_FALSE; + } + + std::vector<int32_t> rects; + SkRegion::Iterator it(*region); + while (!it.done()) { + const SkIRect& r = it.rect(); + rects.push_back(r.fLeft); + rects.push_back(r.fTop); + rects.push_back(r.fRight); + rects.push_back(r.fBottom); + it.next(); + } + + AParcel* p = AParcel_fromJavaParcel(env, parcel); + ndk::AParcel_writeVector(p, rects); + AParcel_delete(p); + + return JNI_TRUE; +#else + return JNI_FALSE; +#endif +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////// + +static jboolean Region_equals(JNIEnv* env, jobject clazz, jlong r1Handle, jlong r2Handle) +{ + const SkRegion *r1 = reinterpret_cast<SkRegion*>(r1Handle); + const SkRegion *r2 = reinterpret_cast<SkRegion*>(r2Handle); + return boolTojboolean(*r1 == *r2); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////// + +struct RgnIterPair { + SkRegion fRgn; // a copy of the caller's region + SkRegion::Iterator fIter; // an iterator acting upon the copy (fRgn) + + explicit RgnIterPair(const SkRegion& rgn) : fRgn(rgn) { + // have our iterator reference our copy (fRgn), so we know it will be + // unchanged for the lifetime of the iterator + fIter.reset(fRgn); + } +}; + +static jlong RegionIter_constructor(JNIEnv* env, jobject, jlong regionHandle) +{ + const SkRegion* region = reinterpret_cast<SkRegion*>(regionHandle); + SkASSERT(region); + return reinterpret_cast<jlong>(new RgnIterPair(*region)); +} + +static void RegionIter_destructor(JNIEnv* env, jobject, jlong pairHandle) +{ + RgnIterPair* pair = reinterpret_cast<RgnIterPair*>(pairHandle); + SkASSERT(pair); + delete pair; +} + +static jboolean RegionIter_next(JNIEnv* env, jobject, jlong pairHandle, jobject rectObject) +{ + RgnIterPair* pair = reinterpret_cast<RgnIterPair*>(pairHandle); + // the caller has checked that rectObject is not nul + SkASSERT(pair); + SkASSERT(rectObject); + + if (!pair->fIter.done()) { + GraphicsJNI::irect_to_jrect(pair->fIter.rect(), env, rectObject); + pair->fIter.next(); + return JNI_TRUE; + } + return JNI_FALSE; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////// + +static const JNINativeMethod gRegionIterMethods[] = { + { "nativeConstructor", "(J)J", (void*)RegionIter_constructor }, + { "nativeDestructor", "(J)V", (void*)RegionIter_destructor }, + { "nativeNext", "(JLandroid/graphics/Rect;)Z", (void*)RegionIter_next } +}; + +static const JNINativeMethod gRegionMethods[] = { + // these are static methods + { "nativeConstructor", "()J", (void*)Region_constructor }, + { "nativeDestructor", "(J)V", (void*)Region_destructor }, + { "nativeSetRegion", "(JJ)V", (void*)Region_setRegion }, + { "nativeSetRect", "(JIIII)Z", (void*)Region_setRect }, + { "nativeSetPath", "(JJJ)Z", (void*)Region_setPath }, + { "nativeGetBounds", "(JLandroid/graphics/Rect;)Z", (void*)Region_getBounds }, + { "nativeGetBoundaryPath", "(JJ)Z", (void*)Region_getBoundaryPath }, + { "nativeOp", "(JIIIII)Z", (void*)Region_op0 }, + { "nativeOp", "(JLandroid/graphics/Rect;JI)Z", (void*)Region_op1 }, + { "nativeOp", "(JJJI)Z", (void*)Region_op2 }, + // these are methods that take the java region object + { "isEmpty", "()Z", (void*)Region_isEmpty }, + { "isRect", "()Z", (void*)Region_isRect }, + { "isComplex", "()Z", (void*)Region_isComplex }, + { "contains", "(II)Z", (void*)Region_contains }, + { "quickContains", "(IIII)Z", (void*)Region_quickContains }, + { "quickReject", "(IIII)Z", (void*)Region_quickRejectIIII }, + { "quickReject", "(Landroid/graphics/Region;)Z", (void*)Region_quickRejectRgn }, + { "scale", "(FLandroid/graphics/Region;)V", (void*)Region_scale }, + { "translate", "(IILandroid/graphics/Region;)V", (void*)Region_translate }, + { "nativeToString", "(J)Ljava/lang/String;", (void*)Region_toString }, + // parceling methods + { "nativeCreateFromParcel", "(Landroid/os/Parcel;)J", (void*)Region_createFromParcel }, + { "nativeWriteToParcel", "(JLandroid/os/Parcel;)Z", (void*)Region_writeToParcel }, + { "nativeEquals", "(JJ)Z", (void*)Region_equals }, +}; + +int register_android_graphics_Region(JNIEnv* env) +{ + jclass clazz = FindClassOrDie(env, "android/graphics/Region"); + + gRegion_nativeInstanceFieldID = GetFieldIDOrDie(env, clazz, "mNativeRegion", "J"); + + RegisterMethodsOrDie(env, "android/graphics/Region", gRegionMethods, NELEM(gRegionMethods)); + return RegisterMethodsOrDie(env, "android/graphics/RegionIterator", gRegionIterMethods, + NELEM(gRegionIterMethods)); +} + +} // namespace android diff --git a/libs/hwui/jni/RtlProperties.h b/libs/hwui/jni/RtlProperties.h new file mode 100644 index 000000000000..907dd59b6e68 --- /dev/null +++ b/libs/hwui/jni/RtlProperties.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _ANDROID_GRAPHICS_RTL_PROPERTIES_H_ +#define _ANDROID_GRAPHICS_RTL_PROPERTIES_H_ + +#include <cutils/properties.h> +#include <stdlib.h> + +namespace android { + +/** + * Debug level for app developers. + */ +#define RTL_PROPERTY_DEBUG "rtl.debug_level" + +/** + * Debug levels. Debug levels are used as flags. + */ +enum RtlDebugLevel { + kRtlDebugDisabled = 0, + kRtlDebugMemory = 1, + kRtlDebugCaches = 2, + kRtlDebugAllocations = 3 +}; + +static RtlDebugLevel readRtlDebugLevel() { + char property[PROPERTY_VALUE_MAX]; + if (property_get(RTL_PROPERTY_DEBUG, property, NULL) > 0) { + return (RtlDebugLevel) atoi(property); + } + return kRtlDebugDisabled; +} + +} // namespace android +#endif // _ANDROID_GRAPHICS_RTL_PROPERTIES_H_ diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp new file mode 100644 index 000000000000..0f6837640524 --- /dev/null +++ b/libs/hwui/jni/Shader.cpp @@ -0,0 +1,305 @@ +#include "GraphicsJNI.h" +#include "SkColorFilter.h" +#include "SkGradientShader.h" +#include "SkImagePriv.h" +#include "SkShader.h" +#include "SkBlendMode.h" +#include "include/effects/SkRuntimeEffect.h" + +#include <vector> + +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 compatiblity 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); +#ifdef SK_SCALAR_IS_FLOAT + SkScalar* hsv = autoHSV.ptr(); +#else + #error Need to convert float array to SkScalar array before calling the following function. +#endif + + 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 jlong BitmapShader_constructor(JNIEnv* env, jobject o, jlong matrixPtr, jlong bitmapHandle, + jint tileModeX, jint tileModeY) { + const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr); + 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. + image = android::bitmap::toBitmap(bitmapHandle).makeImage(); + } + + if (!image.get()) { + SkBitmap bitmap; + image = SkMakeImageFromRasterBitmap(bitmap, kNever_SkCopyPixelsMode); + } + sk_sp<SkShader> shader = image->makeShader( + (SkTileMode)tileModeX, (SkTileMode)tileModeY); + 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()); +#ifdef SK_SCALAR_IS_FLOAT + SkScalar* pos = autoPos.ptr(); +#else + #error Need to convert float array to SkScalar array before calling the following function. +#endif + + 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 x, jfloat y, + jfloat radius, jlongArray colorArray, jfloatArray posArray, jint tileMode, + jlong colorSpaceHandle) { + SkPoint center; + center.set(x, y); + + std::vector<SkColor4f> colors = convertColorLongs(env, colorArray); + + AutoJavaFloatArray autoPos(env, posArray, colors.size()); +#ifdef SK_SCALAR_IS_FLOAT + SkScalar* pos = autoPos.ptr(); +#else + #error Need to convert float array to SkScalar array before calling the following function. +#endif + + sk_sp<SkShader> shader = SkGradientShader::MakeRadial(center, radius, &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 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()); +#ifdef SK_SCALAR_IS_FLOAT + SkScalar* pos = autoPos.ptr(); +#else + #error Need to convert float array to SkScalar array before calling the following function. +#endif + + 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_create(JNIEnv* env, jobject, jlong shaderFactory, jlong matrixPtr, + jbyteArray inputs, jlong colorSpaceHandle, jboolean isOpaque) { + SkRuntimeEffect* effect = reinterpret_cast<SkRuntimeEffect*>(shaderFactory); + AutoJavaByteArray arInputs(env, inputs); + + sk_sp<SkData> fData; + fData = SkData::MakeWithCopy(arInputs.ptr(), arInputs.length()); + const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr); + sk_sp<SkShader> shader = effect->makeShader(fData, nullptr, 0, matrix, isOpaque == JNI_TRUE); + ThrowIAE_IfNull(env, shader); + + return reinterpret_cast<jlong>(shader.release()); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// + +static jlong RuntimeShader_createShaderFactory(JNIEnv* env, jobject, jstring sksl) { + ScopedUtfChars strSksl(env, sksl); + sk_sp<SkRuntimeEffect> effect = std::get<0>(SkRuntimeEffect::Make(SkString(strSksl.c_str()))); + ThrowIAE_IfNull(env, effect); + + return reinterpret_cast<jlong>(effect.release()); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// + +static void Effect_safeUnref(SkRuntimeEffect* effect) { + SkSafeUnref(effect); +} + +static jlong RuntimeShader_getNativeFinalizer(JNIEnv*, jobject) { + return static_cast<jlong>(reinterpret_cast<uintptr_t>(&Effect_safeUnref)); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// + +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", "(JJII)J", (void*)BitmapShader_constructor }, +}; + +static const JNINativeMethod gLinearGradientMethods[] = { + { "nativeCreate", "(JFFFF[J[FIJ)J", (void*)LinearGradient_create }, +}; + +static const JNINativeMethod gRadialGradientMethods[] = { + { "nativeCreate", "(JFFF[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 }, + { "nativeCreate", "(JJ[BJZ)J", (void*)RuntimeShader_create }, + { "nativeCreateShaderFactory", "(Ljava/lang/String;)J", + (void*)RuntimeShader_createShaderFactory }, +}; + +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; +} diff --git a/libs/hwui/jni/TEST_MAPPING b/libs/hwui/jni/TEST_MAPPING new file mode 100644 index 000000000000..10bd0ee906fd --- /dev/null +++ b/libs/hwui/jni/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "CtsGraphicsTestCases" + } + ] +} diff --git a/libs/hwui/jni/Typeface.cpp b/libs/hwui/jni/Typeface.cpp new file mode 100644 index 000000000000..2a5f402a4fa6 --- /dev/null +++ b/libs/hwui/jni/Typeface.cpp @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "FontUtils.h" +#include "GraphicsJNI.h" +#include <nativehelper/ScopedPrimitiveArray.h> +#include <nativehelper/ScopedUtfChars.h> +#include "SkTypeface.h" +#include <hwui/Typeface.h> +#include <minikin/FontFamily.h> +#include <minikin/SystemFonts.h> + +using namespace android; + +static inline Typeface* toTypeface(jlong ptr) { + return reinterpret_cast<Typeface*>(ptr); +} + +template<typename Ptr> static inline jlong toJLong(Ptr ptr) { + return reinterpret_cast<jlong>(ptr); +} + +static jlong Typeface_createFromTypeface(JNIEnv* env, jobject, jlong familyHandle, jint style) { + Typeface* family = toTypeface(familyHandle); + Typeface* face = Typeface::createRelative(family, (Typeface::Style)style); + // TODO: the following logic shouldn't be necessary, the above should always succeed. + // Try to find the closest matching font, using the standard heuristic + if (NULL == face) { + face = Typeface::createRelative(family, (Typeface::Style)(style ^ Typeface::kItalic)); + } + for (int i = 0; NULL == face && i < 4; i++) { + face = Typeface::createRelative(family, (Typeface::Style)i); + } + return toJLong(face); +} + +static jlong Typeface_createFromTypefaceWithExactStyle(JNIEnv* env, jobject, jlong nativeInstance, + jint weight, jboolean italic) { + return toJLong(Typeface::createAbsolute(toTypeface(nativeInstance), weight, italic)); +} + +static jlong Typeface_createFromTypefaceWithVariation(JNIEnv* env, jobject, jlong familyHandle, + jobject listOfAxis) { + std::vector<minikin::FontVariation> variations; + ListHelper list(env, listOfAxis); + for (jint i = 0; i < list.size(); i++) { + jobject axisObject = list.get(i); + if (axisObject == nullptr) { + continue; + } + AxisHelper axis(env, axisObject); + variations.push_back(minikin::FontVariation(axis.getTag(), axis.getStyleValue())); + } + return toJLong(Typeface::createFromTypefaceWithVariation(toTypeface(familyHandle), variations)); +} + +static jlong Typeface_createWeightAlias(JNIEnv* env, jobject, jlong familyHandle, jint weight) { + return toJLong(Typeface::createWithDifferentBaseWeight(toTypeface(familyHandle), weight)); +} + +static void releaseFunc(jlong ptr) { + delete toTypeface(ptr); +} + +// CriticalNative +static jlong Typeface_getReleaseFunc(CRITICAL_JNI_PARAMS) { + return toJLong(&releaseFunc); +} + +// CriticalNative +static jint Typeface_getStyle(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle) { + return toTypeface(faceHandle)->fAPIStyle; +} + +// CriticalNative +static jint Typeface_getWeight(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle) { + return toTypeface(faceHandle)->fStyle.weight(); +} + +static jlong Typeface_createFromArray(JNIEnv *env, jobject, jlongArray familyArray, + int weight, int italic) { + ScopedLongArrayRO families(env, familyArray); + std::vector<std::shared_ptr<minikin::FontFamily>> familyVec; + familyVec.reserve(families.size()); + for (size_t i = 0; i < families.size(); i++) { + FontFamilyWrapper* family = reinterpret_cast<FontFamilyWrapper*>(families[i]); + familyVec.emplace_back(family->family); + } + return toJLong(Typeface::createFromFamilies(std::move(familyVec), weight, italic)); +} + +// CriticalNative +static void Typeface_setDefault(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle) { + Typeface::setDefault(toTypeface(faceHandle)); + minikin::SystemFonts::registerDefault(toTypeface(faceHandle)->fFontCollection); +} + +static jobject Typeface_getSupportedAxes(JNIEnv *env, jobject, jlong faceHandle) { + Typeface* face = toTypeface(faceHandle); + const std::unordered_set<minikin::AxisTag>& tagSet = face->fFontCollection->getSupportedTags(); + const size_t length = tagSet.size(); + if (length == 0) { + return nullptr; + } + std::vector<jint> tagVec(length); + int index = 0; + for (const auto& tag : tagSet) { + tagVec[index++] = tag; + } + std::sort(tagVec.begin(), tagVec.end()); + const jintArray result = env->NewIntArray(length); + env->SetIntArrayRegion(result, 0, length, tagVec.data()); + return result; +} + +static void Typeface_registerGenericFamily(JNIEnv *env, jobject, jstring familyName, jlong ptr) { + ScopedUtfChars familyNameChars(env, familyName); + minikin::SystemFonts::registerFallback(familyNameChars.c_str(), + toTypeface(ptr)->fFontCollection); +} + +/////////////////////////////////////////////////////////////////////////////// + +static const JNINativeMethod gTypefaceMethods[] = { + { "nativeCreateFromTypeface", "(JI)J", (void*)Typeface_createFromTypeface }, + { "nativeCreateFromTypefaceWithExactStyle", "(JIZ)J", + (void*)Typeface_createFromTypefaceWithExactStyle }, + { "nativeCreateFromTypefaceWithVariation", "(JLjava/util/List;)J", + (void*)Typeface_createFromTypefaceWithVariation }, + { "nativeCreateWeightAlias", "(JI)J", (void*)Typeface_createWeightAlias }, + { "nativeGetReleaseFunc", "()J", (void*)Typeface_getReleaseFunc }, + { "nativeGetStyle", "(J)I", (void*)Typeface_getStyle }, + { "nativeGetWeight", "(J)I", (void*)Typeface_getWeight }, + { "nativeCreateFromArray", "([JII)J", + (void*)Typeface_createFromArray }, + { "nativeSetDefault", "(J)V", (void*)Typeface_setDefault }, + { "nativeGetSupportedAxes", "(J)[I", (void*)Typeface_getSupportedAxes }, + { "nativeRegisterGenericFamily", "(Ljava/lang/String;J)V", + (void*)Typeface_registerGenericFamily }, +}; + +int register_android_graphics_Typeface(JNIEnv* env) +{ + return RegisterMethodsOrDie(env, "android/graphics/Typeface", gTypefaceMethods, + NELEM(gTypefaceMethods)); +} diff --git a/libs/hwui/jni/Utils.cpp b/libs/hwui/jni/Utils.cpp new file mode 100644 index 000000000000..17c194d04f84 --- /dev/null +++ b/libs/hwui/jni/Utils.cpp @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Utils.h" +#include "SkUtils.h" +#include "SkData.h" + +#include <log/log.h> + +using namespace android; + +AssetStreamAdaptor::AssetStreamAdaptor(Asset* asset) + : fAsset(asset) +{ +} + +bool AssetStreamAdaptor::rewind() { + off64_t pos = fAsset->seek(0, SEEK_SET); + if (pos == (off64_t)-1) { + SkDebugf("----- fAsset->seek(rewind) failed\n"); + return false; + } + return true; +} + +size_t AssetStreamAdaptor::getLength() const { + return fAsset->getLength(); +} + +bool AssetStreamAdaptor::isAtEnd() const { + return fAsset->getRemainingLength() == 0; +} + +SkStreamRewindable* AssetStreamAdaptor::onDuplicate() const { + // Cannot create a duplicate, since each AssetStreamAdaptor + // would be modifying the Asset. + //return new AssetStreamAdaptor(fAsset); + return NULL; +} + +bool AssetStreamAdaptor::hasPosition() const { + return fAsset->seek(0, SEEK_CUR) != -1; +} + +size_t AssetStreamAdaptor::getPosition() const { + const off64_t offset = fAsset->seek(0, SEEK_CUR); + if (offset == -1) { + SkDebugf("---- fAsset->seek(0, SEEK_CUR) failed\n"); + return 0; + } + + return offset; +} + +bool AssetStreamAdaptor::seek(size_t position) { + if (fAsset->seek(position, SEEK_SET) == -1) { + SkDebugf("---- fAsset->seek(0, SEEK_SET) failed\n"); + return false; + } + + return true; +} + +bool AssetStreamAdaptor::move(long offset) { + if (fAsset->seek(offset, SEEK_CUR) == -1) { + SkDebugf("---- fAsset->seek(%i, SEEK_CUR) failed\n", offset); + return false; + } + + return true; +} + +size_t AssetStreamAdaptor::read(void* buffer, size_t size) { + ssize_t amount; + + if (NULL == buffer) { + if (0 == size) { + return 0; + } + // asset->seek returns new total offset + // we want to return amount that was skipped + + off64_t oldOffset = fAsset->seek(0, SEEK_CUR); + if (-1 == oldOffset) { + SkDebugf("---- fAsset->seek(oldOffset) failed\n"); + return 0; + } + off64_t newOffset = fAsset->seek(size, SEEK_CUR); + if (-1 == newOffset) { + SkDebugf("---- fAsset->seek(%d) failed\n", size); + return 0; + } + amount = newOffset - oldOffset; + } else { + amount = fAsset->read(buffer, size); + } + + if (amount < 0) { + amount = 0; + } + return amount; +} + +SkMemoryStream* android::CopyAssetToStream(Asset* asset) { + if (NULL == asset) { + return NULL; + } + + const off64_t seekReturnVal = asset->seek(0, SEEK_SET); + if ((off64_t)-1 == seekReturnVal) { + SkDebugf("---- copyAsset: asset rewind failed\n"); + return NULL; + } + + const off64_t size = asset->getLength(); + if (size <= 0) { + SkDebugf("---- copyAsset: asset->getLength() returned %d\n", size); + return NULL; + } + + sk_sp<SkData> data(SkData::MakeUninitialized(size)); + const off64_t len = asset->read(data->writable_data(), size); + if (len != size) { + SkDebugf("---- copyAsset: asset->read(%d) returned %d\n", size, len); + return NULL; + } + + return new SkMemoryStream(std::move(data)); +} + +jobject android::nullObjectReturn(const char msg[]) { + if (msg) { + SkDebugf("--- %s\n", msg); + } + return NULL; +} + +bool android::isSeekable(int descriptor) { + return ::lseek64(descriptor, 0, SEEK_CUR) != -1; +} + +JNIEnv* android::get_env_or_die(JavaVM* jvm) { + JNIEnv* env; + if (jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { + LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", jvm); + } + return env; +} diff --git a/libs/hwui/jni/Utils.h b/libs/hwui/jni/Utils.h new file mode 100644 index 000000000000..89255177ba2e --- /dev/null +++ b/libs/hwui/jni/Utils.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _ANDROID_GRAPHICS_UTILS_H_ +#define _ANDROID_GRAPHICS_UTILS_H_ + +#include "SkStream.h" + +#include <jni.h> +#include <androidfw/Asset.h> + +namespace android { + +class AssetStreamAdaptor : public SkStreamRewindable { +public: + explicit AssetStreamAdaptor(Asset*); + + virtual bool rewind(); + virtual size_t read(void* buffer, size_t size); + virtual bool hasLength() const { return true; } + virtual size_t getLength() const; + virtual bool hasPosition() const; + virtual size_t getPosition() const; + virtual bool seek(size_t position); + virtual bool move(long offset); + virtual bool isAtEnd() const; + +protected: + SkStreamRewindable* onDuplicate() const override; + +private: + Asset* fAsset; +}; + +/** + * Make a deep copy of the asset, and return it as a stream, or NULL if there + * was an error. + * FIXME: If we could "ref/reopen" the asset, we may not need to copy it here. + */ + +SkMemoryStream* CopyAssetToStream(Asset*); + +/** Restore the file descriptor's offset in our destructor + */ +class AutoFDSeek { +public: + explicit AutoFDSeek(int fd) : fFD(fd) { + fCurr = ::lseek(fd, 0, SEEK_CUR); + } + ~AutoFDSeek() { + if (fCurr >= 0) { + ::lseek(fFD, fCurr, SEEK_SET); + } + } +private: + int fFD; + off64_t fCurr; +}; + +jobject nullObjectReturn(const char msg[]); + +/** Check if the file descriptor is seekable. + */ +bool isSeekable(int descriptor); + +JNIEnv* get_env_or_die(JavaVM* jvm); + +}; // namespace android + +#endif // _ANDROID_GRAPHICS_UTILS_H_ diff --git a/libs/hwui/jni/YuvToJpegEncoder.cpp b/libs/hwui/jni/YuvToJpegEncoder.cpp new file mode 100644 index 000000000000..689cf0bea741 --- /dev/null +++ b/libs/hwui/jni/YuvToJpegEncoder.cpp @@ -0,0 +1,268 @@ +#include "CreateJavaOutputStreamAdaptor.h" +#include "SkJPEGWriteUtility.h" +#include "YuvToJpegEncoder.h" +#include <ui/PixelFormat.h> +#include <hardware/hardware.h> + +#include "graphics_jni_helpers.h" + +YuvToJpegEncoder* YuvToJpegEncoder::create(int format, int* strides) { + // Only ImageFormat.NV21 and ImageFormat.YUY2 are supported + // for now. + if (format == HAL_PIXEL_FORMAT_YCrCb_420_SP) { + return new Yuv420SpToJpegEncoder(strides); + } else if (format == HAL_PIXEL_FORMAT_YCbCr_422_I) { + return new Yuv422IToJpegEncoder(strides); + } else { + return NULL; + } +} + +YuvToJpegEncoder::YuvToJpegEncoder(int* strides) : fStrides(strides) { +} + +struct ErrorMgr { + struct jpeg_error_mgr pub; + jmp_buf jmp; +}; + +void error_exit(j_common_ptr cinfo) { + ErrorMgr* err = (ErrorMgr*) cinfo->err; + (*cinfo->err->output_message) (cinfo); + longjmp(err->jmp, 1); +} + +bool YuvToJpegEncoder::encode(SkWStream* stream, void* inYuv, int width, + int height, int* offsets, int jpegQuality) { + jpeg_compress_struct cinfo; + ErrorMgr err; + skjpeg_destination_mgr sk_wstream(stream); + + cinfo.err = jpeg_std_error(&err.pub); + err.pub.error_exit = error_exit; + + if (setjmp(err.jmp)) { + jpeg_destroy_compress(&cinfo); + return false; + } + jpeg_create_compress(&cinfo); + + cinfo.dest = &sk_wstream; + + setJpegCompressStruct(&cinfo, width, height, jpegQuality); + + jpeg_start_compress(&cinfo, TRUE); + + compress(&cinfo, (uint8_t*) inYuv, offsets); + + jpeg_finish_compress(&cinfo); + + jpeg_destroy_compress(&cinfo); + + return true; +} + +void YuvToJpegEncoder::setJpegCompressStruct(jpeg_compress_struct* cinfo, + int width, int height, int quality) { + cinfo->image_width = width; + cinfo->image_height = height; + cinfo->input_components = 3; + cinfo->in_color_space = JCS_YCbCr; + jpeg_set_defaults(cinfo); + + jpeg_set_quality(cinfo, quality, TRUE); + jpeg_set_colorspace(cinfo, JCS_YCbCr); + cinfo->raw_data_in = TRUE; + cinfo->dct_method = JDCT_IFAST; + configSamplingFactors(cinfo); +} + +/////////////////////////////////////////////////////////////////// +Yuv420SpToJpegEncoder::Yuv420SpToJpegEncoder(int* strides) : + YuvToJpegEncoder(strides) { + fNumPlanes = 2; +} + +void Yuv420SpToJpegEncoder::compress(jpeg_compress_struct* cinfo, + uint8_t* yuv, int* offsets) { + SkDebugf("onFlyCompress"); + JSAMPROW y[16]; + JSAMPROW cb[8]; + JSAMPROW cr[8]; + JSAMPARRAY planes[3]; + planes[0] = y; + planes[1] = cb; + planes[2] = cr; + + int width = cinfo->image_width; + int height = cinfo->image_height; + uint8_t* yPlanar = yuv + offsets[0]; + uint8_t* vuPlanar = yuv + offsets[1]; //width * height; + uint8_t* uRows = new uint8_t [8 * (width >> 1)]; + uint8_t* vRows = new uint8_t [8 * (width >> 1)]; + + + // process 16 lines of Y and 8 lines of U/V each time. + while (cinfo->next_scanline < cinfo->image_height) { + //deitnerleave u and v + deinterleave(vuPlanar, uRows, vRows, cinfo->next_scanline, width, height); + + // Jpeg library ignores the rows whose indices are greater than height. + for (int i = 0; i < 16; i++) { + // y row + y[i] = yPlanar + (cinfo->next_scanline + i) * fStrides[0]; + + // construct u row and v row + if ((i & 1) == 0) { + // height and width are both halved because of downsampling + int offset = (i >> 1) * (width >> 1); + cb[i/2] = uRows + offset; + cr[i/2] = vRows + offset; + } + } + jpeg_write_raw_data(cinfo, planes, 16); + } + delete [] uRows; + delete [] vRows; + +} + +void Yuv420SpToJpegEncoder::deinterleave(uint8_t* vuPlanar, uint8_t* uRows, + uint8_t* vRows, int rowIndex, int width, int height) { + int numRows = (height - rowIndex) / 2; + if (numRows > 8) numRows = 8; + for (int row = 0; row < numRows; ++row) { + int offset = ((rowIndex >> 1) + row) * fStrides[1]; + uint8_t* vu = vuPlanar + offset; + for (int i = 0; i < (width >> 1); ++i) { + int index = row * (width >> 1) + i; + uRows[index] = vu[1]; + vRows[index] = vu[0]; + vu += 2; + } + } +} + +void Yuv420SpToJpegEncoder::configSamplingFactors(jpeg_compress_struct* cinfo) { + // cb and cr are horizontally downsampled and vertically downsampled as well. + cinfo->comp_info[0].h_samp_factor = 2; + cinfo->comp_info[0].v_samp_factor = 2; + cinfo->comp_info[1].h_samp_factor = 1; + cinfo->comp_info[1].v_samp_factor = 1; + cinfo->comp_info[2].h_samp_factor = 1; + cinfo->comp_info[2].v_samp_factor = 1; +} + +/////////////////////////////////////////////////////////////////////////////// +Yuv422IToJpegEncoder::Yuv422IToJpegEncoder(int* strides) : + YuvToJpegEncoder(strides) { + fNumPlanes = 1; +} + +void Yuv422IToJpegEncoder::compress(jpeg_compress_struct* cinfo, + uint8_t* yuv, int* offsets) { + SkDebugf("onFlyCompress_422"); + JSAMPROW y[16]; + JSAMPROW cb[16]; + JSAMPROW cr[16]; + JSAMPARRAY planes[3]; + planes[0] = y; + planes[1] = cb; + planes[2] = cr; + + int width = cinfo->image_width; + int height = cinfo->image_height; + uint8_t* yRows = new uint8_t [16 * width]; + uint8_t* uRows = new uint8_t [16 * (width >> 1)]; + uint8_t* vRows = new uint8_t [16 * (width >> 1)]; + + uint8_t* yuvOffset = yuv + offsets[0]; + + // process 16 lines of Y and 16 lines of U/V each time. + while (cinfo->next_scanline < cinfo->image_height) { + deinterleave(yuvOffset, yRows, uRows, vRows, cinfo->next_scanline, width, height); + + // Jpeg library ignores the rows whose indices are greater than height. + for (int i = 0; i < 16; i++) { + // y row + y[i] = yRows + i * width; + + // construct u row and v row + // width is halved because of downsampling + int offset = i * (width >> 1); + cb[i] = uRows + offset; + cr[i] = vRows + offset; + } + + jpeg_write_raw_data(cinfo, planes, 16); + } + delete [] yRows; + delete [] uRows; + delete [] vRows; +} + + +void Yuv422IToJpegEncoder::deinterleave(uint8_t* yuv, uint8_t* yRows, uint8_t* uRows, + uint8_t* vRows, int rowIndex, int width, int height) { + int numRows = height - rowIndex; + if (numRows > 16) numRows = 16; + for (int row = 0; row < numRows; ++row) { + uint8_t* yuvSeg = yuv + (rowIndex + row) * fStrides[0]; + for (int i = 0; i < (width >> 1); ++i) { + int indexY = row * width + (i << 1); + int indexU = row * (width >> 1) + i; + yRows[indexY] = yuvSeg[0]; + yRows[indexY + 1] = yuvSeg[2]; + uRows[indexU] = yuvSeg[1]; + vRows[indexU] = yuvSeg[3]; + yuvSeg += 4; + } + } +} + +void Yuv422IToJpegEncoder::configSamplingFactors(jpeg_compress_struct* cinfo) { + // cb and cr are horizontally downsampled and vertically downsampled as well. + cinfo->comp_info[0].h_samp_factor = 2; + cinfo->comp_info[0].v_samp_factor = 2; + cinfo->comp_info[1].h_samp_factor = 1; + cinfo->comp_info[1].v_samp_factor = 2; + cinfo->comp_info[2].h_samp_factor = 1; + cinfo->comp_info[2].v_samp_factor = 2; +} +/////////////////////////////////////////////////////////////////////////////// + +static jboolean YuvImage_compressToJpeg(JNIEnv* env, jobject, jbyteArray inYuv, + jint format, jint width, jint height, jintArray offsets, + jintArray strides, jint jpegQuality, jobject jstream, + jbyteArray jstorage) { + jbyte* yuv = env->GetByteArrayElements(inYuv, NULL); + SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage); + + jint* imgOffsets = env->GetIntArrayElements(offsets, NULL); + jint* imgStrides = env->GetIntArrayElements(strides, NULL); + YuvToJpegEncoder* encoder = YuvToJpegEncoder::create(format, imgStrides); + jboolean result = JNI_FALSE; + if (encoder != NULL) { + encoder->encode(strm, yuv, width, height, imgOffsets, jpegQuality); + delete encoder; + result = JNI_TRUE; + } + + env->ReleaseByteArrayElements(inYuv, yuv, 0); + env->ReleaseIntArrayElements(offsets, imgOffsets, 0); + env->ReleaseIntArrayElements(strides, imgStrides, 0); + delete strm; + return result; +} +/////////////////////////////////////////////////////////////////////////////// + +static const JNINativeMethod gYuvImageMethods[] = { + { "nativeCompressToJpeg", "([BIII[I[IILjava/io/OutputStream;[B)Z", + (void*)YuvImage_compressToJpeg } +}; + +int register_android_graphics_YuvImage(JNIEnv* env) +{ + return android::RegisterMethodsOrDie(env, "android/graphics/YuvImage", gYuvImageMethods, + NELEM(gYuvImageMethods)); +} diff --git a/libs/hwui/jni/YuvToJpegEncoder.h b/libs/hwui/jni/YuvToJpegEncoder.h new file mode 100644 index 000000000000..7e7b935df276 --- /dev/null +++ b/libs/hwui/jni/YuvToJpegEncoder.h @@ -0,0 +1,74 @@ +#ifndef _ANDROID_GRAPHICS_YUV_TO_JPEG_ENCODER_H_ +#define _ANDROID_GRAPHICS_YUV_TO_JPEG_ENCODER_H_ + +#include "SkTypes.h" +#include "SkStream.h" +extern "C" { + #include "jpeglib.h" + #include "jerror.h" +} + +class YuvToJpegEncoder { +public: + /** Create an encoder based on the YUV format. + * + * @param pixelFormat The yuv pixel format as defined in ui/PixelFormat.h. + * @param strides The number of row bytes in each image plane. + * @return an encoder based on the pixelFormat. + */ + static YuvToJpegEncoder* create(int pixelFormat, int* strides); + + explicit YuvToJpegEncoder(int* strides); + + /** Encode YUV data to jpeg, which is output to a stream. + * + * @param stream The jpeg output stream. + * @param inYuv The input yuv data. + * @param width Width of the the Yuv data in terms of pixels. + * @param height Height of the Yuv data in terms of pixels. + * @param offsets The offsets in each image plane with respect to inYuv. + * @param jpegQuality Picture quality in [0, 100]. + * @return true if successfully compressed the stream. + */ + bool encode(SkWStream* stream, void* inYuv, int width, + int height, int* offsets, int jpegQuality); + + virtual ~YuvToJpegEncoder() {} + +protected: + int fNumPlanes; + int* fStrides; + void setJpegCompressStruct(jpeg_compress_struct* cinfo, int width, + int height, int quality); + virtual void configSamplingFactors(jpeg_compress_struct* cinfo) = 0; + virtual void compress(jpeg_compress_struct* cinfo, + uint8_t* yuv, int* offsets) = 0; +}; + +class Yuv420SpToJpegEncoder : public YuvToJpegEncoder { +public: + explicit Yuv420SpToJpegEncoder(int* strides); + virtual ~Yuv420SpToJpegEncoder() {} + +private: + void configSamplingFactors(jpeg_compress_struct* cinfo); + void deinterleaveYuv(uint8_t* yuv, int width, int height, + uint8_t*& yPlanar, uint8_t*& uPlanar, uint8_t*& vPlanar); + void deinterleave(uint8_t* vuPlanar, uint8_t* uRows, uint8_t* vRows, + int rowIndex, int width, int height); + void compress(jpeg_compress_struct* cinfo, uint8_t* yuv, int* offsets); +}; + +class Yuv422IToJpegEncoder : public YuvToJpegEncoder { +public: + explicit Yuv422IToJpegEncoder(int* strides); + virtual ~Yuv422IToJpegEncoder() {} + +private: + void configSamplingFactors(jpeg_compress_struct* cinfo); + void compress(jpeg_compress_struct* cinfo, uint8_t* yuv, int* offsets); + void deinterleave(uint8_t* yuv, uint8_t* yRows, uint8_t* uRows, + uint8_t* vRows, int rowIndex, int width, int height); +}; + +#endif // _ANDROID_GRAPHICS_YUV_TO_JPEG_ENCODER_H_ diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp new file mode 100644 index 000000000000..4aff3e544efa --- /dev/null +++ b/libs/hwui/jni/android_graphics_Canvas.cpp @@ -0,0 +1,739 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "GraphicsJNI.h" + +#ifdef __ANDROID_ +#include <android/api-level.h> +#else +#define __ANDROID_API_P__ 28 +#endif +#include <androidfw/ResourceTypes.h> +#include <hwui/Canvas.h> +#include <hwui/Paint.h> +#include <hwui/PaintFilter.h> +#include <hwui/Typeface.h> +#include <minikin/Layout.h> +#include <nativehelper/ScopedPrimitiveArray.h> +#include <nativehelper/ScopedStringChars.h> + +#include "Bitmap.h" +#include "SkGraphics.h" +#include "SkRegion.h" +#include "SkVertices.h" + +namespace minikin { +class MeasuredText; +} // namespace minikin + +namespace android { + +namespace CanvasJNI { + +static Canvas* get_canvas(jlong canvasHandle) { + return reinterpret_cast<Canvas*>(canvasHandle); +} + +static void delete_canvas(Canvas* canvas) { + delete canvas; +} + +static jlong getNativeFinalizer(JNIEnv* env, jobject clazz) { + return static_cast<jlong>(reinterpret_cast<uintptr_t>(&delete_canvas)); +} + +// Native wrapper constructor used by Canvas(Bitmap) +static jlong initRaster(JNIEnv* env, jobject, jlong bitmapHandle) { + SkBitmap bitmap; + if (bitmapHandle != 0) { + bitmap::toBitmap(bitmapHandle).getSkBitmap(&bitmap); + } + return reinterpret_cast<jlong>(Canvas::create_canvas(bitmap)); +} + +// Set the given bitmap as the new draw target (wrapped in a new SkCanvas), +// optionally copying canvas matrix & clip state. +static void setBitmap(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle) { + SkBitmap bitmap; + if (bitmapHandle != 0) { + bitmap::toBitmap(bitmapHandle).getSkBitmap(&bitmap); + } + get_canvas(canvasHandle)->setBitmap(bitmap); +} + +static jboolean isOpaque(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle) { + return get_canvas(canvasHandle)->isOpaque() ? JNI_TRUE : JNI_FALSE; +} + +static jint getWidth(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle) { + return static_cast<jint>(get_canvas(canvasHandle)->width()); +} + +static jint getHeight(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle) { + return static_cast<jint>(get_canvas(canvasHandle)->height()); +} + +static jint save(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle, jint flagsHandle) { + SaveFlags::Flags flags = static_cast<SaveFlags::Flags>(flagsHandle); + return static_cast<jint>(get_canvas(canvasHandle)->save(flags)); +} + +static jint saveLayer(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle, jfloat l, jfloat t, + jfloat r, jfloat b, jlong paintHandle, jint flagsHandle) { + Paint* paint = reinterpret_cast<Paint*>(paintHandle); + SaveFlags::Flags flags = static_cast<SaveFlags::Flags>(flagsHandle); + return static_cast<jint>(get_canvas(canvasHandle)->saveLayer(l, t, r, b, paint, flags)); +} + +static jint saveLayerAlpha(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle, jfloat l, jfloat t, + jfloat r, jfloat b, jint alpha, jint flagsHandle) { + SaveFlags::Flags flags = static_cast<SaveFlags::Flags>(flagsHandle); + return static_cast<jint>(get_canvas(canvasHandle)->saveLayerAlpha(l, t, r, b, alpha, flags)); +} + +static jint saveUnclippedLayer(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle, jint l, jint t, jint r, jint b) { + return reinterpret_cast<jint>(get_canvas(canvasHandle)->saveUnclippedLayer(l, t, r, b)); +} + +static void restoreUnclippedLayer(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle, jint saveCount, jlong paintHandle) { + Paint* paint = reinterpret_cast<Paint*>(paintHandle); + get_canvas(canvasHandle)->restoreUnclippedLayer(saveCount, *paint); +} + +static bool restore(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle) { + Canvas* canvas = get_canvas(canvasHandle); + if (canvas->getSaveCount() <= 1) { + return false; // cannot restore anymore + } + canvas->restore(); + return true; // success +} + +static void restoreToCount(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle, jint saveCount) { + Canvas* canvas = get_canvas(canvasHandle); + canvas->restoreToCount(saveCount); +} + +static jint getSaveCount(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle) { + return static_cast<jint>(get_canvas(canvasHandle)->getSaveCount()); +} + +static void getMatrix(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle, jlong matrixHandle) { + SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle); + get_canvas(canvasHandle)->getMatrix(matrix); +} + +static void setMatrix(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle, jlong matrixHandle) { + const SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle); + get_canvas(canvasHandle)->setMatrix(matrix ? *matrix : SkMatrix::I()); +} + +static void concat(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle, jlong matrixHandle) { + const SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle); + get_canvas(canvasHandle)->concat(*matrix); +} + +static void rotate(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle, jfloat degrees) { + get_canvas(canvasHandle)->rotate(degrees); +} + +static void scale(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle, jfloat sx, jfloat sy) { + get_canvas(canvasHandle)->scale(sx, sy); +} + +static void skew(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle, jfloat sx, jfloat sy) { + get_canvas(canvasHandle)->skew(sx, sy); +} + +static void translate(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle, jfloat dx, jfloat dy) { + get_canvas(canvasHandle)->translate(dx, dy); +} + +static jboolean getClipBounds(JNIEnv* env, jobject, jlong canvasHandle, jobject bounds) { + SkRect r; + SkIRect ir; + bool result = get_canvas(canvasHandle)->getClipBounds(&r); + + if (!result) { + r.setEmpty(); + } + r.round(&ir); + + (void)GraphicsJNI::irect_to_jrect(ir, env, bounds); + return result ? JNI_TRUE : JNI_FALSE; +} + +static jboolean quickRejectRect(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle, + jfloat left, jfloat top, jfloat right, jfloat bottom) { + bool result = get_canvas(canvasHandle)->quickRejectRect(left, top, right, bottom); + return result ? JNI_TRUE : JNI_FALSE; +} + +static jboolean quickRejectPath(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle, jlong pathHandle) { + SkPath* path = reinterpret_cast<SkPath*>(pathHandle); + bool result = get_canvas(canvasHandle)->quickRejectPath(*path); + return result ? JNI_TRUE : JNI_FALSE; +} + +// SkRegion::Op and SkClipOp are numerically identical, so we can freely cast +// from one to the other (though SkClipOp is destined to become a strict subset) +static_assert(SkRegion::kDifference_Op == static_cast<SkRegion::Op>(SkClipOp::kDifference), ""); +static_assert(SkRegion::kIntersect_Op == static_cast<SkRegion::Op>(SkClipOp::kIntersect), ""); +static_assert(SkRegion::kUnion_Op == static_cast<SkRegion::Op>(SkClipOp::kUnion_deprecated), ""); +static_assert(SkRegion::kXOR_Op == static_cast<SkRegion::Op>(SkClipOp::kXOR_deprecated), ""); +static_assert(SkRegion::kReverseDifference_Op == static_cast<SkRegion::Op>(SkClipOp::kReverseDifference_deprecated), ""); +static_assert(SkRegion::kReplace_Op == static_cast<SkRegion::Op>(SkClipOp::kReplace_deprecated), ""); + +static SkClipOp opHandleToClipOp(jint opHandle) { + // The opHandle is defined in Canvas.java to be Region::Op + SkRegion::Op rgnOp = static_cast<SkRegion::Op>(opHandle); + + // In the future, when we no longer support the wide range of ops (e.g. Union, Xor) + // this function can perform a range check and throw an unsupported-exception. + // e.g. if (rgnOp != kIntersect && rgnOp != kDifference) throw... + + // Skia now takes a different type, SkClipOp, as the parameter to clipping calls + // This type is binary compatible with SkRegion::Op, so a static_cast<> is safe. + return static_cast<SkClipOp>(rgnOp); +} + +static jboolean clipRect(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle, jfloat l, jfloat t, + jfloat r, jfloat b, jint opHandle) { + bool nonEmptyClip = get_canvas(canvasHandle)->clipRect(l, t, r, b, + opHandleToClipOp(opHandle)); + return nonEmptyClip ? JNI_TRUE : JNI_FALSE; +} + +static jboolean clipPath(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle, jlong pathHandle, + jint opHandle) { + SkPath* path = reinterpret_cast<SkPath*>(pathHandle); + bool nonEmptyClip = get_canvas(canvasHandle)->clipPath(path, opHandleToClipOp(opHandle)); + return nonEmptyClip ? JNI_TRUE : JNI_FALSE; +} + +static void drawColor(JNIEnv* env, jobject, jlong canvasHandle, jint color, jint modeHandle) { + SkBlendMode mode = static_cast<SkBlendMode>(modeHandle); + get_canvas(canvasHandle)->drawColor(color, mode); +} + +static void drawColorLong(JNIEnv* env, jobject, jlong canvasHandle, jlong colorSpaceHandle, + jlong colorLong, jint modeHandle) { + SkColor4f color = GraphicsJNI::convertColorLong(colorLong); + sk_sp<SkColorSpace> cs = GraphicsJNI::getNativeColorSpace(colorSpaceHandle); + SkPaint p; + p.setColor4f(color, cs.get()); + + SkBlendMode mode = static_cast<SkBlendMode>(modeHandle); + p.setBlendMode(mode); + get_canvas(canvasHandle)->drawPaint(p); +} + +static void drawPaint(JNIEnv* env, jobject, jlong canvasHandle, jlong paintHandle) { + Paint* paint = reinterpret_cast<Paint*>(paintHandle); + get_canvas(canvasHandle)->drawPaint(*paint); +} + +static void drawPoint(JNIEnv*, jobject, jlong canvasHandle, jfloat x, jfloat y, + jlong paintHandle) { + const Paint* paint = reinterpret_cast<Paint*>(paintHandle); + get_canvas(canvasHandle)->drawPoint(x, y, *paint); +} + +static void drawPoints(JNIEnv* env, jobject, jlong canvasHandle, jfloatArray jptsArray, + jint offset, jint count, jlong paintHandle) { + NPE_CHECK_RETURN_VOID(env, jptsArray); + AutoJavaFloatArray autoPts(env, jptsArray); + float* floats = autoPts.ptr(); + const int length = autoPts.length(); + + if ((offset | count) < 0 || offset + count > length) { + doThrowAIOOBE(env); + return; + } + + const Paint* paint = reinterpret_cast<Paint*>(paintHandle); + get_canvas(canvasHandle)->drawPoints(floats + offset, count, *paint); +} + +static void drawLine(JNIEnv* env, jobject, jlong canvasHandle, jfloat startX, jfloat startY, + jfloat stopX, jfloat stopY, jlong paintHandle) { + Paint* paint = reinterpret_cast<Paint*>(paintHandle); + get_canvas(canvasHandle)->drawLine(startX, startY, stopX, stopY, *paint); +} + +static void drawLines(JNIEnv* env, jobject, jlong canvasHandle, jfloatArray jptsArray, + jint offset, jint count, jlong paintHandle) { + NPE_CHECK_RETURN_VOID(env, jptsArray); + AutoJavaFloatArray autoPts(env, jptsArray); + float* floats = autoPts.ptr(); + const int length = autoPts.length(); + + if ((offset | count) < 0 || offset + count > length) { + doThrowAIOOBE(env); + return; + } + + const Paint* paint = reinterpret_cast<Paint*>(paintHandle); + get_canvas(canvasHandle)->drawLines(floats + offset, count, *paint); +} + +static void drawRect(JNIEnv* env, jobject, jlong canvasHandle, jfloat left, jfloat top, + jfloat right, jfloat bottom, jlong paintHandle) { + const Paint* paint = reinterpret_cast<Paint*>(paintHandle); + get_canvas(canvasHandle)->drawRect(left, top, right, bottom, *paint); +} + +static void drawDoubleRoundRectXY(JNIEnv* env, jobject, jlong canvasHandle, jfloat outerLeft, + jfloat outerTop, jfloat outerRight, jfloat outerBottom, jfloat outerRx, + jfloat outerRy, jfloat innerLeft, jfloat innerTop, jfloat innerRight, + jfloat innerBottom, jfloat innerRx, jfloat innerRy, jlong paintHandle) { + const Paint* paint = reinterpret_cast<Paint*>(paintHandle); + get_canvas(canvasHandle)->drawDoubleRoundRectXY( + outerLeft, outerTop, outerRight, outerBottom, outerRx, outerRy, + innerLeft, innerTop, innerRight, innerBottom, innerRx, innerRy, *paint); +} + +static void drawDoubleRoundRectRadii(JNIEnv* env, jobject, jlong canvasHandle, jfloat outerLeft, + jfloat outerTop, jfloat outerRight, jfloat outerBottom, jfloatArray jouterRadii, + jfloat innerLeft, jfloat innerTop, jfloat innerRight, + jfloat innerBottom, jfloatArray jinnerRadii, jlong paintHandle) { + const Paint* paint = reinterpret_cast<Paint*>(paintHandle); + + float outerRadii[8]; + float innerRadii[8]; + env->GetFloatArrayRegion(jouterRadii, 0, 8, outerRadii); + env->GetFloatArrayRegion(jinnerRadii, 0, 8, innerRadii); + get_canvas(canvasHandle)->drawDoubleRoundRectRadii( + outerLeft, outerTop, outerRight, outerBottom, outerRadii, + innerLeft, innerTop, innerRight, innerBottom, innerRadii, *paint); + +} + +static void drawRegion(JNIEnv* env, jobject, jlong canvasHandle, jlong regionHandle, + jlong paintHandle) { + const SkRegion* region = reinterpret_cast<SkRegion*>(regionHandle); + const Paint* paint = reinterpret_cast<Paint*>(paintHandle); + get_canvas(canvasHandle)->drawRegion(*region, *paint); +} + +static void drawRoundRect(JNIEnv* env, jobject, jlong canvasHandle, jfloat left, jfloat top, + jfloat right, jfloat bottom, jfloat rx, jfloat ry, jlong paintHandle) { + const Paint* paint = reinterpret_cast<Paint*>(paintHandle); + get_canvas(canvasHandle)->drawRoundRect(left, top, right, bottom, rx, ry, *paint); +} + +static void drawCircle(JNIEnv* env, jobject, jlong canvasHandle, jfloat cx, jfloat cy, + jfloat radius, jlong paintHandle) { + const Paint* paint = reinterpret_cast<Paint*>(paintHandle); + get_canvas(canvasHandle)->drawCircle(cx, cy, radius, *paint); +} + +static void drawOval(JNIEnv* env, jobject, jlong canvasHandle, jfloat left, jfloat top, + jfloat right, jfloat bottom, jlong paintHandle) { + const Paint* paint = reinterpret_cast<Paint*>(paintHandle); + get_canvas(canvasHandle)->drawOval(left, top, right, bottom, *paint); +} + +static void drawArc(JNIEnv* env, jobject, jlong canvasHandle, jfloat left, jfloat top, + jfloat right, jfloat bottom, jfloat startAngle, jfloat sweepAngle, + jboolean useCenter, jlong paintHandle) { + const Paint* paint = reinterpret_cast<Paint*>(paintHandle); + get_canvas(canvasHandle)->drawArc(left, top, right, bottom, startAngle, sweepAngle, + useCenter, *paint); +} + +static void drawPath(JNIEnv* env, jobject, jlong canvasHandle, jlong pathHandle, + jlong paintHandle) { + const SkPath* path = reinterpret_cast<SkPath*>(pathHandle); + const Paint* paint = reinterpret_cast<Paint*>(paintHandle); + get_canvas(canvasHandle)->drawPath(*path, *paint); +} + +static void drawVertices(JNIEnv* env, jobject, jlong canvasHandle, + jint modeHandle, jint floatCount, + jfloatArray jverts, jint vertIndex, + jfloatArray jtexs, jint texIndex, + jintArray jcolors, jint colorIndex, + jshortArray jindices, jint indexIndex, + jint indexCount, jlong paintHandle) { + + const int vertexCount = floatCount >> 1; // 2 floats per SkPoint + + AutoJavaFloatArray vertA(env, jverts, vertIndex + floatCount); + AutoJavaFloatArray texA(env, jtexs, texIndex + floatCount); + AutoJavaIntArray colorA(env, jcolors, colorIndex + vertexCount); + AutoJavaShortArray indexA(env, jindices, indexIndex + indexCount); + + const float* verts = vertA.ptr() + vertIndex; + const float* texs = texA.ptr() + vertIndex; + const int* colors = NULL; + const uint16_t* indices = NULL; + + if (jcolors != NULL) { + colors = colorA.ptr() + colorIndex; + } + if (jindices != NULL) { + indices = (const uint16_t*)(indexA.ptr() + indexIndex); + } + + SkVertices::VertexMode mode = static_cast<SkVertices::VertexMode>(modeHandle); + const Paint* paint = reinterpret_cast<Paint*>(paintHandle); + get_canvas(canvasHandle)->drawVertices(SkVertices::MakeCopy(mode, vertexCount, + reinterpret_cast<const SkPoint*>(verts), + reinterpret_cast<const SkPoint*>(texs), + reinterpret_cast<const SkColor*>(colors), + indexCount, indices).get(), + SkBlendMode::kModulate, *paint); +} + +static void drawNinePatch(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle, + jlong chunkHandle, jfloat left, jfloat top, jfloat right, jfloat bottom, + jlong paintHandle, jint dstDensity, jint srcDensity) { + + Canvas* canvas = get_canvas(canvasHandle); + Bitmap& bitmap = android::bitmap::toBitmap(bitmapHandle); + const android::Res_png_9patch* chunk = reinterpret_cast<android::Res_png_9patch*>(chunkHandle); + const Paint* paint = reinterpret_cast<Paint*>(paintHandle); + + if (CC_LIKELY(dstDensity == srcDensity || dstDensity == 0 || srcDensity == 0)) { + canvas->drawNinePatch(bitmap, *chunk, left, top, right, bottom, paint); + } else { + canvas->save(SaveFlags::MatrixClip); + + SkScalar scale = dstDensity / (float)srcDensity; + canvas->translate(left, top); + canvas->scale(scale, scale); + + Paint filteredPaint; + if (paint) { + filteredPaint = *paint; + } + filteredPaint.setFilterQuality(kLow_SkFilterQuality); + + canvas->drawNinePatch(bitmap, *chunk, 0, 0, (right-left)/scale, (bottom-top)/scale, + &filteredPaint); + + canvas->restore(); + } +} + +static void drawBitmap(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle, + jfloat left, jfloat top, jlong paintHandle, jint canvasDensity, + jint screenDensity, jint bitmapDensity) { + Canvas* canvas = get_canvas(canvasHandle); + Bitmap& bitmap = android::bitmap::toBitmap(bitmapHandle); + const Paint* paint = reinterpret_cast<Paint*>(paintHandle); + + if (canvasDensity == bitmapDensity || canvasDensity == 0 || bitmapDensity == 0) { + if (screenDensity != 0 && screenDensity != bitmapDensity) { + Paint filteredPaint; + if (paint) { + filteredPaint = *paint; + } + filteredPaint.setFilterQuality(kLow_SkFilterQuality); + canvas->drawBitmap(bitmap, left, top, &filteredPaint); + } else { + canvas->drawBitmap(bitmap, left, top, paint); + } + } else { + canvas->save(SaveFlags::MatrixClip); + SkScalar scale = canvasDensity / (float)bitmapDensity; + canvas->translate(left, top); + canvas->scale(scale, scale); + + Paint filteredPaint; + if (paint) { + filteredPaint = *paint; + } + filteredPaint.setFilterQuality(kLow_SkFilterQuality); + + canvas->drawBitmap(bitmap, 0, 0, &filteredPaint); + canvas->restore(); + } +} + +static void drawBitmapMatrix(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle, + jlong matrixHandle, jlong paintHandle) { + const SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle); + const Paint* paint = reinterpret_cast<Paint*>(paintHandle); + Bitmap& bitmap = android::bitmap::toBitmap(bitmapHandle); + get_canvas(canvasHandle)->drawBitmap(bitmap, *matrix, paint); +} + +static void drawBitmapRect(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle, + float srcLeft, float srcTop, float srcRight, float srcBottom, + float dstLeft, float dstTop, float dstRight, float dstBottom, + jlong paintHandle, jint screenDensity, jint bitmapDensity) { + Canvas* canvas = get_canvas(canvasHandle); + const Paint* paint = reinterpret_cast<Paint*>(paintHandle); + + Bitmap& bitmap = android::bitmap::toBitmap(bitmapHandle); + if (screenDensity != 0 && screenDensity != bitmapDensity) { + Paint filteredPaint; + if (paint) { + filteredPaint = *paint; + } + filteredPaint.setFilterQuality(kLow_SkFilterQuality); + canvas->drawBitmap(bitmap, srcLeft, srcTop, srcRight, srcBottom, + dstLeft, dstTop, dstRight, dstBottom, &filteredPaint); + } else { + canvas->drawBitmap(bitmap, srcLeft, srcTop, srcRight, srcBottom, + dstLeft, dstTop, dstRight, dstBottom, paint); + } +} + +static void drawBitmapArray(JNIEnv* env, jobject, jlong canvasHandle, + jintArray jcolors, jint offset, jint stride, + jfloat x, jfloat y, jint width, jint height, + jboolean hasAlpha, jlong paintHandle) { + // Note: If hasAlpha is false, kRGB_565_SkColorType will be used, which will + // correct the alphaType to kOpaque_SkAlphaType. + SkImageInfo info = SkImageInfo::Make(width, height, + hasAlpha ? kN32_SkColorType : kRGB_565_SkColorType, + kPremul_SkAlphaType); + SkBitmap bitmap; + bitmap.setInfo(info); + sk_sp<Bitmap> androidBitmap = Bitmap::allocateHeapBitmap(&bitmap); + if (!androidBitmap) { + return; + } + + if (!GraphicsJNI::SetPixels(env, jcolors, offset, stride, 0, 0, width, height, &bitmap)) { + return; + } + + const Paint* paint = reinterpret_cast<Paint*>(paintHandle); + get_canvas(canvasHandle)->drawBitmap(*androidBitmap, x, y, paint); +} + +static void drawBitmapMesh(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle, + jint meshWidth, jint meshHeight, jfloatArray jverts, + jint vertIndex, jintArray jcolors, jint colorIndex, jlong paintHandle) { + if (Canvas::GetApiLevel() < __ANDROID_API_P__) { + // Before P we forgot to respect these. Now that we do respect them, explicitly + // zero them for backward compatibility. + vertIndex = 0; + colorIndex = 0; + } + + const int ptCount = (meshWidth + 1) * (meshHeight + 1); + AutoJavaFloatArray vertA(env, jverts, vertIndex + (ptCount << 1)); + AutoJavaIntArray colorA(env, jcolors, colorIndex + ptCount); + + const Paint* paint = reinterpret_cast<Paint*>(paintHandle); + Bitmap& bitmap = android::bitmap::toBitmap(bitmapHandle); + get_canvas(canvasHandle)->drawBitmapMesh(bitmap, meshWidth, meshHeight, + vertA.ptr() + vertIndex*2, + colorA.ptr() + colorIndex, paint); +} + +static void drawTextChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray charArray, + jint index, jint count, jfloat x, jfloat y, jint bidiFlags, + jlong paintHandle) { + Paint* paint = reinterpret_cast<Paint*>(paintHandle); + const Typeface* typeface = paint->getAndroidTypeface(); + ScopedCharArrayRO text(env, charArray); + // drawTextString and drawTextChars doesn't use context info + get_canvas(canvasHandle)->drawText( + text.get() + index, count, // text buffer + 0, count, // draw range + 0, count, // context range + x, y, // draw position + static_cast<minikin::Bidi>(bidiFlags), *paint, typeface, nullptr /* measured text */); +} + +static void drawTextString(JNIEnv* env, jobject, jlong canvasHandle, jstring strObj, + jint start, jint end, jfloat x, jfloat y, jint bidiFlags, + jlong paintHandle) { + ScopedStringChars text(env, strObj); + Paint* paint = reinterpret_cast<Paint*>(paintHandle); + const Typeface* typeface = paint->getAndroidTypeface(); + const int count = end - start; + // drawTextString and drawTextChars doesn't use context info + get_canvas(canvasHandle)->drawText( + text.get() + start, count, // text buffer + 0, count, // draw range + 0, count, // context range + x, y, // draw position + static_cast<minikin::Bidi>(bidiFlags), *paint, typeface, nullptr /* measured text */); +} + +static void drawTextRunChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray charArray, + jint index, jint count, jint contextIndex, jint contextCount, + jfloat x, jfloat y, jboolean isRtl, jlong paintHandle, + jlong mtHandle) { + minikin::MeasuredText* mt = reinterpret_cast<minikin::MeasuredText*>(mtHandle); + const minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR; + + ScopedCharArrayRO text(env, charArray); + Paint* paint = reinterpret_cast<Paint*>(paintHandle); + const Typeface* typeface = paint->getAndroidTypeface(); + get_canvas(canvasHandle)->drawText( + text.get(), text.size(), // text buffer + index, count, // draw range + contextIndex, contextCount, // context range, + x, y, // draw position + bidiFlags, *paint, typeface, mt); +} + +static void drawTextRunString(JNIEnv* env, jobject obj, jlong canvasHandle, jstring strObj, + jint start, jint end, jint contextStart, jint contextEnd, + jfloat x, jfloat y, jboolean isRtl, jlong paintHandle) { + const minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR; + + ScopedStringChars text(env, strObj); + Paint* paint = reinterpret_cast<Paint*>(paintHandle); + const Typeface* typeface = paint->getAndroidTypeface(); + get_canvas(canvasHandle)->drawText( + text.get(), text.size(), // text buffer + start, end - start, // draw range + contextStart, contextEnd - contextStart, // context range + x, y, // draw position + bidiFlags, *paint, typeface, nullptr /* measured text */); +} + +static void drawTextOnPathChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray text, + jint index, jint count, jlong pathHandle, jfloat hOffset, + jfloat vOffset, jint bidiFlags, jlong paintHandle) { + SkPath* path = reinterpret_cast<SkPath*>(pathHandle); + Paint* paint = reinterpret_cast<Paint*>(paintHandle); + const Typeface* typeface = paint->getAndroidTypeface(); + + jchar* jchars = env->GetCharArrayElements(text, NULL); + + get_canvas(canvasHandle)->drawTextOnPath(jchars + index, count, + static_cast<minikin::Bidi>(bidiFlags), *path, hOffset, vOffset, *paint, typeface); + + env->ReleaseCharArrayElements(text, jchars, 0); +} + +static void drawTextOnPathString(JNIEnv* env, jobject, jlong canvasHandle, jstring text, + jlong pathHandle, jfloat hOffset, jfloat vOffset, + jint bidiFlags, jlong paintHandle) { + SkPath* path = reinterpret_cast<SkPath*>(pathHandle); + Paint* paint = reinterpret_cast<Paint*>(paintHandle); + const Typeface* typeface = paint->getAndroidTypeface(); + + const jchar* jchars = env->GetStringChars(text, NULL); + int count = env->GetStringLength(text); + + get_canvas(canvasHandle)->drawTextOnPath(jchars, count, static_cast<minikin::Bidi>(bidiFlags), + *path, hOffset, vOffset, *paint, typeface); + + env->ReleaseStringChars(text, jchars); +} + +static void setPaintFilter(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle, jlong filterHandle) { + PaintFilter* paintFilter = reinterpret_cast<PaintFilter*>(filterHandle); + get_canvas(canvasHandle)->setPaintFilter(sk_ref_sp(paintFilter)); +} + +static void freeCaches(JNIEnv* env, jobject) { + SkGraphics::PurgeFontCache(); +} + +static void freeTextLayoutCaches(JNIEnv* env, jobject) { + minikin::Layout::purgeCaches(); +} + +static void setCompatibilityVersion(JNIEnv* env, jobject, jint apiLevel) { + Canvas::setCompatibilityVersion(apiLevel); +} + + +}; // namespace CanvasJNI + +static const JNINativeMethod gMethods[] = { + {"nGetNativeFinalizer", "()J", (void*) CanvasJNI::getNativeFinalizer}, + {"nFreeCaches", "()V", (void*) CanvasJNI::freeCaches}, + {"nFreeTextLayoutCaches", "()V", (void*) CanvasJNI::freeTextLayoutCaches}, + {"nSetCompatibilityVersion", "(I)V", (void*) CanvasJNI::setCompatibilityVersion}, + + // ------------ @FastNative ---------------- + {"nInitRaster", "(J)J", (void*) CanvasJNI::initRaster}, + {"nSetBitmap", "(JJ)V", (void*) CanvasJNI::setBitmap}, + {"nGetClipBounds","(JLandroid/graphics/Rect;)Z", (void*) CanvasJNI::getClipBounds}, + + // ------------ @CriticalNative ---------------- + {"nIsOpaque","(J)Z", (void*) CanvasJNI::isOpaque}, + {"nGetWidth","(J)I", (void*) CanvasJNI::getWidth}, + {"nGetHeight","(J)I", (void*) CanvasJNI::getHeight}, + {"nSave","(JI)I", (void*) CanvasJNI::save}, + {"nSaveLayer","(JFFFFJI)I", (void*) CanvasJNI::saveLayer}, + {"nSaveLayerAlpha","(JFFFFII)I", (void*) CanvasJNI::saveLayerAlpha}, + {"nSaveUnclippedLayer","(JIIII)I", (void*) CanvasJNI::saveUnclippedLayer}, + {"nRestoreUnclippedLayer","(JIJ)V", (void*) CanvasJNI::restoreUnclippedLayer}, + {"nGetSaveCount","(J)I", (void*) CanvasJNI::getSaveCount}, + {"nRestore","(J)Z", (void*) CanvasJNI::restore}, + {"nRestoreToCount","(JI)V", (void*) CanvasJNI::restoreToCount}, + {"nGetMatrix", "(JJ)V", (void*)CanvasJNI::getMatrix}, + {"nSetMatrix","(JJ)V", (void*) CanvasJNI::setMatrix}, + {"nConcat","(JJ)V", (void*) CanvasJNI::concat}, + {"nRotate","(JF)V", (void*) CanvasJNI::rotate}, + {"nScale","(JFF)V", (void*) CanvasJNI::scale}, + {"nSkew","(JFF)V", (void*) CanvasJNI::skew}, + {"nTranslate","(JFF)V", (void*) CanvasJNI::translate}, + {"nQuickReject","(JJ)Z", (void*) CanvasJNI::quickRejectPath}, + {"nQuickReject","(JFFFF)Z", (void*)CanvasJNI::quickRejectRect}, + {"nClipRect","(JFFFFI)Z", (void*) CanvasJNI::clipRect}, + {"nClipPath","(JJI)Z", (void*) CanvasJNI::clipPath}, + {"nSetDrawFilter", "(JJ)V", (void*) CanvasJNI::setPaintFilter}, +}; + +// If called from Canvas these are regular JNI +// If called from DisplayListCanvas they are @FastNative +static const JNINativeMethod gDrawMethods[] = { + {"nDrawColor","(JII)V", (void*) CanvasJNI::drawColor}, + {"nDrawColor","(JJJI)V", (void*) CanvasJNI::drawColorLong}, + {"nDrawPaint","(JJ)V", (void*) CanvasJNI::drawPaint}, + {"nDrawPoint", "(JFFJ)V", (void*) CanvasJNI::drawPoint}, + {"nDrawPoints", "(J[FIIJ)V", (void*) CanvasJNI::drawPoints}, + {"nDrawLine", "(JFFFFJ)V", (void*) CanvasJNI::drawLine}, + {"nDrawLines", "(J[FIIJ)V", (void*) CanvasJNI::drawLines}, + {"nDrawRect","(JFFFFJ)V", (void*) CanvasJNI::drawRect}, + {"nDrawRegion", "(JJJ)V", (void*) CanvasJNI::drawRegion }, + {"nDrawRoundRect","(JFFFFFFJ)V", (void*) CanvasJNI::drawRoundRect}, + {"nDrawDoubleRoundRect", "(JFFFFFFFFFFFFJ)V", (void*) CanvasJNI::drawDoubleRoundRectXY}, + {"nDrawDoubleRoundRect", "(JFFFF[FFFFF[FJ)V", (void*) CanvasJNI::drawDoubleRoundRectRadii}, + {"nDrawCircle","(JFFFJ)V", (void*) CanvasJNI::drawCircle}, + {"nDrawOval","(JFFFFJ)V", (void*) CanvasJNI::drawOval}, + {"nDrawArc","(JFFFFFFZJ)V", (void*) CanvasJNI::drawArc}, + {"nDrawPath","(JJJ)V", (void*) CanvasJNI::drawPath}, + {"nDrawVertices", "(JII[FI[FI[II[SIIJ)V", (void*)CanvasJNI::drawVertices}, + {"nDrawNinePatch", "(JJJFFFFJII)V", (void*)CanvasJNI::drawNinePatch}, + {"nDrawBitmapMatrix", "(JJJJ)V", (void*)CanvasJNI::drawBitmapMatrix}, + {"nDrawBitmapMesh", "(JJII[FI[IIJ)V", (void*)CanvasJNI::drawBitmapMesh}, + {"nDrawBitmap","(JJFFJIII)V", (void*) CanvasJNI::drawBitmap}, + {"nDrawBitmap","(JJFFFFFFFFJII)V", (void*) CanvasJNI::drawBitmapRect}, + {"nDrawBitmap", "(J[IIIFFIIZJ)V", (void*)CanvasJNI::drawBitmapArray}, + {"nDrawText","(J[CIIFFIJ)V", (void*) CanvasJNI::drawTextChars}, + {"nDrawText","(JLjava/lang/String;IIFFIJ)V", (void*) CanvasJNI::drawTextString}, + {"nDrawTextRun","(J[CIIIIFFZJJ)V", (void*) CanvasJNI::drawTextRunChars}, + {"nDrawTextRun","(JLjava/lang/String;IIIIFFZJ)V", (void*) CanvasJNI::drawTextRunString}, + {"nDrawTextOnPath","(J[CIIJFFIJ)V", (void*) CanvasJNI::drawTextOnPathChars}, + {"nDrawTextOnPath","(JLjava/lang/String;JFFIJ)V", (void*) CanvasJNI::drawTextOnPathString}, +}; + +int register_android_graphics_Canvas(JNIEnv* env) { + int ret = 0; + ret |= RegisterMethodsOrDie(env, "android/graphics/Canvas", gMethods, NELEM(gMethods)); + ret |= RegisterMethodsOrDie(env, "android/graphics/BaseCanvas", gDrawMethods, NELEM(gDrawMethods)); + ret |= RegisterMethodsOrDie(env, "android/graphics/BaseRecordingCanvas", gDrawMethods, NELEM(gDrawMethods)); + return ret; + +} + +}; // namespace android diff --git a/libs/hwui/jni/android_graphics_ColorSpace.cpp b/libs/hwui/jni/android_graphics_ColorSpace.cpp new file mode 100644 index 000000000000..232fd71a12b4 --- /dev/null +++ b/libs/hwui/jni/android_graphics_ColorSpace.cpp @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "GraphicsJNI.h" + +#include "SkColor.h" +#include "SkColorSpace.h" +#include "SkHalf.h" + +using namespace android; + +static skcms_Matrix3x3 getNativeXYZMatrix(JNIEnv* env, jfloatArray xyzD50) { + skcms_Matrix3x3 xyzMatrix; + jfloat* array = env->GetFloatArrayElements(xyzD50, NULL); + xyzMatrix.vals[0][0] = array[0]; + xyzMatrix.vals[1][0] = array[1]; + xyzMatrix.vals[2][0] = array[2]; + xyzMatrix.vals[0][1] = array[3]; + xyzMatrix.vals[1][1] = array[4]; + xyzMatrix.vals[2][1] = array[5]; + xyzMatrix.vals[0][2] = array[6]; + xyzMatrix.vals[1][2] = array[7]; + xyzMatrix.vals[2][2] = array[8]; + env->ReleaseFloatArrayElements(xyzD50, array, 0); + return xyzMatrix; +} + +/////////////////////////////////////////////////////////////////////////////// + +static float halfToFloat(uint16_t bits) { +#ifdef __ANDROID__ // __fp16 is not defined on non-Android builds + __fp16 h; + memcpy(&h, &bits, 2); + return (float)h; +#else + return SkHalfToFloat(bits); +#endif +} + +SkColor4f GraphicsJNI::convertColorLong(jlong color) { + if ((color & 0x3f) == 0) { + // This corresponds to sRGB, which is treated differently than the rest. + uint8_t a = color >> 56 & 0xff; + uint8_t r = color >> 48 & 0xff; + uint8_t g = color >> 40 & 0xff; + uint8_t b = color >> 32 & 0xff; + SkColor c = SkColorSetARGB(a, r, g, b); + return SkColor4f::FromColor(c); + } + + // These match the implementation of android.graphics.Color#red(long) etc. + float r = halfToFloat((uint16_t)(color >> 48 & 0xffff)); + float g = halfToFloat((uint16_t)(color >> 32 & 0xffff)); + float b = halfToFloat((uint16_t)(color >> 16 & 0xffff)); + float a = (color >> 6 & 0x3ff) / 1023.0f; + + return SkColor4f{r, g, b, a}; +} + +sk_sp<SkColorSpace> GraphicsJNI::getNativeColorSpace(jlong colorSpaceHandle) { + if (colorSpaceHandle == 0) return nullptr; + return sk_ref_sp(reinterpret_cast<SkColorSpace*>(colorSpaceHandle)); +} + +static void unref_colorSpace(SkColorSpace* cs) { + SkSafeUnref(cs); +} + +static jlong ColorSpace_getNativeFinalizer(JNIEnv*, jobject) { + return static_cast<jlong>(reinterpret_cast<uintptr_t>(&unref_colorSpace)); +} + +static jlong ColorSpace_creator(JNIEnv* env, jobject, jfloat a, jfloat b, jfloat c, + jfloat d, jfloat e, jfloat f, jfloat g, jfloatArray xyzD50) { + skcms_TransferFunction p; + p.a = a; + p.b = b; + p.c = c; + p.d = d; + p.e = e; + p.f = f; + p.g = g; + skcms_Matrix3x3 xyzMatrix = getNativeXYZMatrix(env, xyzD50); + + return reinterpret_cast<jlong>(SkColorSpace::MakeRGB(p, xyzMatrix).release()); +} + +static const JNINativeMethod gColorSpaceRgbMethods[] = { + { "nativeGetNativeFinalizer", "()J", (void*)ColorSpace_getNativeFinalizer }, + { "nativeCreate", "(FFFFFFF[F)J", (void*)ColorSpace_creator } +}; + +namespace android { + +int register_android_graphics_ColorSpace(JNIEnv* env) { + return android::RegisterMethodsOrDie(env, "android/graphics/ColorSpace$Rgb", + gColorSpaceRgbMethods, NELEM(gColorSpaceRgbMethods)); +} + +}; // namespace android diff --git a/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp b/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp new file mode 100644 index 000000000000..54822f1f07e2 --- /dev/null +++ b/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "GraphicsJNI.h" + +#ifdef __ANDROID__ // Layoutlib does not support Looper and device properties +#include <utils/Looper.h> +#endif + +#include <SkBitmap.h> +#include <SkRegion.h> + +#include <Rect.h> +#include <RenderNode.h> +#include <CanvasProperty.h> +#include <hwui/Canvas.h> +#include <hwui/Paint.h> +#include <minikin/Layout.h> +#ifdef __ANDROID__ // Layoutlib does not support RenderThread +#include <renderthread/RenderProxy.h> +#endif + +namespace android { + +using namespace uirenderer; + +jmethodID gRunnableMethodId; + +static JNIEnv* jnienv(JavaVM* vm) { + JNIEnv* env; + if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { + LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", vm); + } + return env; +} + +#ifdef __ANDROID__ // Layoutlib does not support GL, Looper +class InvokeRunnableMessage : public MessageHandler { +public: + InvokeRunnableMessage(JNIEnv* env, jobject runnable) { + mRunnable = env->NewGlobalRef(runnable); + env->GetJavaVM(&mVm); + } + + virtual ~InvokeRunnableMessage() { + jnienv(mVm)->DeleteGlobalRef(mRunnable); + } + + virtual void handleMessage(const Message&) { + jnienv(mVm)->CallVoidMethod(mRunnable, gRunnableMethodId); + } + +private: + JavaVM* mVm; + jobject mRunnable; +}; + +class GlFunctorReleasedCallbackBridge : public GlFunctorLifecycleListener { +public: + GlFunctorReleasedCallbackBridge(JNIEnv* env, jobject javaCallback) { + mLooper = Looper::getForThread(); + mMessage = new InvokeRunnableMessage(env, javaCallback); + } + + virtual void onGlFunctorReleased(Functor* functor) override { + mLooper->sendMessage(mMessage, 0); + } + +private: + sp<Looper> mLooper; + sp<InvokeRunnableMessage> mMessage; +}; +#endif + +// ---------------- @FastNative ----------------------------- + +static void android_view_DisplayListCanvas_callDrawGLFunction(JNIEnv* env, jobject clazz, + jlong canvasPtr, jlong functorPtr, jobject releasedCallback) { +#ifdef __ANDROID__ // Layoutlib does not support GL + Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr); + Functor* functor = reinterpret_cast<Functor*>(functorPtr); + sp<GlFunctorReleasedCallbackBridge> bridge; + if (releasedCallback) { + bridge = new GlFunctorReleasedCallbackBridge(env, releasedCallback); + } + canvas->callDrawGLFunction(functor, bridge.get()); +#endif +} + + +// ---------------- @CriticalNative ------------------------- + +static jlong android_view_DisplayListCanvas_createDisplayListCanvas(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, + jint width, jint height) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + return reinterpret_cast<jlong>(Canvas::create_recording_canvas(width, height, renderNode)); +} + +static void android_view_DisplayListCanvas_resetDisplayListCanvas(CRITICAL_JNI_PARAMS_COMMA jlong canvasPtr, + jlong renderNodePtr, jint width, jint height) { + Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr); + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + canvas->resetRecording(width, height, renderNode); +} + +static jint android_view_DisplayListCanvas_getMaxTextureSize(CRITICAL_JNI_PARAMS) { +#ifdef __ANDROID__ // Layoutlib does not support RenderProxy (RenderThread) + return android::uirenderer::renderthread::RenderProxy::maxTextureSize(); +#else + return 4096; +#endif +} + +static void android_view_DisplayListCanvas_insertReorderBarrier(CRITICAL_JNI_PARAMS_COMMA jlong canvasPtr, + jboolean reorderEnable) { + Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr); + canvas->insertReorderBarrier(reorderEnable); +} + +static jlong android_view_DisplayListCanvas_finishRecording(CRITICAL_JNI_PARAMS_COMMA jlong canvasPtr) { + Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr); + return reinterpret_cast<jlong>(canvas->finishRecording()); +} + +static void android_view_DisplayListCanvas_drawRenderNode(CRITICAL_JNI_PARAMS_COMMA jlong canvasPtr, jlong renderNodePtr) { + Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr); + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + canvas->drawRenderNode(renderNode); +} + +static void android_view_DisplayListCanvas_drawTextureLayer(CRITICAL_JNI_PARAMS_COMMA jlong canvasPtr, jlong layerPtr) { + Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr); + DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerPtr); + canvas->drawLayer(layer); +} + +static void android_view_DisplayListCanvas_drawRoundRectProps(CRITICAL_JNI_PARAMS_COMMA jlong canvasPtr, + jlong leftPropPtr, jlong topPropPtr, jlong rightPropPtr, jlong bottomPropPtr, + jlong rxPropPtr, jlong ryPropPtr, jlong paintPropPtr) { + Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr); + CanvasPropertyPrimitive* leftProp = reinterpret_cast<CanvasPropertyPrimitive*>(leftPropPtr); + CanvasPropertyPrimitive* topProp = reinterpret_cast<CanvasPropertyPrimitive*>(topPropPtr); + CanvasPropertyPrimitive* rightProp = reinterpret_cast<CanvasPropertyPrimitive*>(rightPropPtr); + CanvasPropertyPrimitive* bottomProp = reinterpret_cast<CanvasPropertyPrimitive*>(bottomPropPtr); + CanvasPropertyPrimitive* rxProp = reinterpret_cast<CanvasPropertyPrimitive*>(rxPropPtr); + CanvasPropertyPrimitive* ryProp = reinterpret_cast<CanvasPropertyPrimitive*>(ryPropPtr); + CanvasPropertyPaint* paintProp = reinterpret_cast<CanvasPropertyPaint*>(paintPropPtr); + canvas->drawRoundRect(leftProp, topProp, rightProp, bottomProp, rxProp, ryProp, paintProp); +} + +static void android_view_DisplayListCanvas_drawCircleProps(CRITICAL_JNI_PARAMS_COMMA jlong canvasPtr, + jlong xPropPtr, jlong yPropPtr, jlong radiusPropPtr, jlong paintPropPtr) { + Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr); + CanvasPropertyPrimitive* xProp = reinterpret_cast<CanvasPropertyPrimitive*>(xPropPtr); + CanvasPropertyPrimitive* yProp = reinterpret_cast<CanvasPropertyPrimitive*>(yPropPtr); + CanvasPropertyPrimitive* radiusProp = reinterpret_cast<CanvasPropertyPrimitive*>(radiusPropPtr); + CanvasPropertyPaint* paintProp = reinterpret_cast<CanvasPropertyPaint*>(paintPropPtr); + canvas->drawCircle(xProp, yProp, radiusProp, paintProp); +} + +static void android_view_DisplayListCanvas_drawWebViewFunctor(CRITICAL_JNI_PARAMS_COMMA jlong canvasPtr, jint functor) { + Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr); + canvas->drawWebViewFunctor(functor); +} + +// ---------------------------------------------------------------------------- +// JNI Glue +// ---------------------------------------------------------------------------- + +const char* const kClassPathName = "android/graphics/RecordingCanvas"; + +static JNINativeMethod gMethods[] = { + + // ------------ @FastNative ------------------ + + { "nCallDrawGLFunction", "(JJLjava/lang/Runnable;)V", + (void*) android_view_DisplayListCanvas_callDrawGLFunction }, + + // ------------ @CriticalNative -------------- + { "nCreateDisplayListCanvas", "(JII)J", (void*) android_view_DisplayListCanvas_createDisplayListCanvas }, + { "nResetDisplayListCanvas", "(JJII)V", (void*) android_view_DisplayListCanvas_resetDisplayListCanvas }, + { "nGetMaximumTextureWidth", "()I", (void*) android_view_DisplayListCanvas_getMaxTextureSize }, + { "nGetMaximumTextureHeight", "()I", (void*) android_view_DisplayListCanvas_getMaxTextureSize }, + { "nInsertReorderBarrier", "(JZ)V", (void*) android_view_DisplayListCanvas_insertReorderBarrier }, + { "nFinishRecording", "(J)J", (void*) android_view_DisplayListCanvas_finishRecording }, + { "nDrawRenderNode", "(JJ)V", (void*) android_view_DisplayListCanvas_drawRenderNode }, + { "nDrawTextureLayer", "(JJ)V", (void*) android_view_DisplayListCanvas_drawTextureLayer }, + { "nDrawCircle", "(JJJJJ)V", (void*) android_view_DisplayListCanvas_drawCircleProps }, + { "nDrawRoundRect", "(JJJJJJJJ)V",(void*) android_view_DisplayListCanvas_drawRoundRectProps }, + { "nDrawWebViewFunctor", "(JI)V", (void*) android_view_DisplayListCanvas_drawWebViewFunctor }, +}; + +int register_android_view_DisplayListCanvas(JNIEnv* env) { + jclass runnableClass = FindClassOrDie(env, "java/lang/Runnable"); + gRunnableMethodId = GetMethodIDOrDie(env, runnableClass, "run", "()V"); + + return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods)); +} + +}; diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp new file mode 100644 index 000000000000..49c7fcd468e1 --- /dev/null +++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp @@ -0,0 +1,745 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#undef LOG_TAG +#define LOG_TAG "ThreadedRenderer" +#define ATRACE_TAG ATRACE_TAG_VIEW + +#include <FrameInfo.h> +#include <GraphicsJNI.h> +#include <Picture.h> +#include <Properties.h> +#include <RootRenderNode.h> +#include <dlfcn.h> +#include <inttypes.h> +#include <media/NdkImage.h> +#include <media/NdkImageReader.h> +#include <nativehelper/JNIHelp.h> +#include <pipeline/skia/ShaderCache.h> +#include <private/EGL/cache.h> +#include <renderthread/CanvasContext.h> +#include <renderthread/RenderProxy.h> +#include <renderthread/RenderTask.h> +#include <renderthread/RenderThread.h> +#include <utils/Color.h> +#include <utils/RefBase.h> +#include <utils/StrongPointer.h> +#include <utils/Timers.h> +#include <utils/TraceUtils.h> + +#include <algorithm> +#include <atomic> + +#include "android_graphics_HardwareRendererObserver.h" + +namespace android { + +using namespace android::uirenderer; +using namespace android::uirenderer::renderthread; + +struct { + jclass clazz; + jmethodID invokePictureCapturedCallback; +} gHardwareRenderer; + +struct { + jmethodID onFrameDraw; +} gFrameDrawingCallback; + +struct { + jmethodID onFrameComplete; +} gFrameCompleteCallback; + +static JNIEnv* getenv(JavaVM* vm) { + JNIEnv* env; + if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { + LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", vm); + } + return env; +} + +typedef ANativeWindow* (*ANW_fromSurface)(JNIEnv* env, jobject surface); +ANW_fromSurface fromSurface; + +class JvmErrorReporter : public ErrorHandler { +public: + JvmErrorReporter(JNIEnv* env) { + env->GetJavaVM(&mVm); + } + + virtual void onError(const std::string& message) override { + JNIEnv* env = getenv(mVm); + jniThrowException(env, "java/lang/IllegalStateException", message.c_str()); + } +private: + JavaVM* mVm; +}; + +class FrameCompleteWrapper : public LightRefBase<FrameCompleteWrapper> { +public: + explicit FrameCompleteWrapper(JNIEnv* env, jobject jobject) { + env->GetJavaVM(&mVm); + mObject = env->NewGlobalRef(jobject); + LOG_ALWAYS_FATAL_IF(!mObject, "Failed to make global ref"); + } + + ~FrameCompleteWrapper() { + releaseObject(); + } + + void onFrameComplete(int64_t frameNr) { + if (mObject) { + ATRACE_FORMAT("frameComplete %" PRId64, frameNr); + getenv(mVm)->CallVoidMethod(mObject, gFrameCompleteCallback.onFrameComplete, frameNr); + releaseObject(); + } + } + +private: + JavaVM* mVm; + jobject mObject; + + void releaseObject() { + if (mObject) { + getenv(mVm)->DeleteGlobalRef(mObject); + mObject = nullptr; + } + } +}; + +static void android_view_ThreadedRenderer_rotateProcessStatsBuffer(JNIEnv* env, jobject clazz) { + RenderProxy::rotateProcessStatsBuffer(); +} + +static void android_view_ThreadedRenderer_setProcessStatsBuffer(JNIEnv* env, jobject clazz, + jint fd) { + RenderProxy::setProcessStatsBuffer(fd); +} + +static jint android_view_ThreadedRenderer_getRenderThreadTid(JNIEnv* env, jobject clazz, + jlong proxyPtr) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + return proxy->getRenderThreadTid(); +} + +static jlong android_view_ThreadedRenderer_createRootRenderNode(JNIEnv* env, jobject clazz) { + RootRenderNode* node = new RootRenderNode(std::make_unique<JvmErrorReporter>(env)); + node->incStrong(0); + node->setName("RootRenderNode"); + return reinterpret_cast<jlong>(node); +} + +static jlong android_view_ThreadedRenderer_createProxy(JNIEnv* env, jobject clazz, + jboolean translucent, jboolean isWideGamut, jlong rootRenderNodePtr) { + RootRenderNode* rootRenderNode = reinterpret_cast<RootRenderNode*>(rootRenderNodePtr); + ContextFactoryImpl factory(rootRenderNode); + RenderProxy* proxy = new RenderProxy(translucent, rootRenderNode, &factory); + proxy->setWideGamut(isWideGamut); + return (jlong) proxy; +} + +static void android_view_ThreadedRenderer_deleteProxy(JNIEnv* env, jobject clazz, + jlong proxyPtr) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + delete proxy; +} + +static jboolean android_view_ThreadedRenderer_loadSystemProperties(JNIEnv* env, jobject clazz, + jlong proxyPtr) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + return proxy->loadSystemProperties(); +} + +static void android_view_ThreadedRenderer_setName(JNIEnv* env, jobject clazz, + jlong proxyPtr, jstring jname) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + const char* name = env->GetStringUTFChars(jname, NULL); + proxy->setName(name); + env->ReleaseStringUTFChars(jname, name); +} + +static void android_view_ThreadedRenderer_setSurface(JNIEnv* env, jobject clazz, + jlong proxyPtr, jobject jsurface, jboolean discardBuffer) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + ANativeWindow* window = nullptr; + if (jsurface) { + window = fromSurface(env, jsurface); + } + bool enableTimeout = true; + if (discardBuffer) { + // Currently only Surface#lockHardwareCanvas takes this path + enableTimeout = false; + proxy->setSwapBehavior(SwapBehavior::kSwap_discardBuffer); + } + proxy->setSurface(window, enableTimeout); + ANativeWindow_release(window); +} + +static jboolean android_view_ThreadedRenderer_pause(JNIEnv* env, jobject clazz, + jlong proxyPtr) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + return proxy->pause(); +} + +static void android_view_ThreadedRenderer_setStopped(JNIEnv* env, jobject clazz, + jlong proxyPtr, jboolean stopped) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + proxy->setStopped(stopped); +} + +static void android_view_ThreadedRenderer_setLightAlpha(JNIEnv* env, jobject clazz, jlong proxyPtr, + jfloat ambientShadowAlpha, jfloat spotShadowAlpha) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + proxy->setLightAlpha((uint8_t) (255 * ambientShadowAlpha), (uint8_t) (255 * spotShadowAlpha)); +} + +static void android_view_ThreadedRenderer_setLightGeometry(JNIEnv* env, jobject clazz, + jlong proxyPtr, jfloat lightX, jfloat lightY, jfloat lightZ, jfloat lightRadius) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + proxy->setLightGeometry((Vector3){lightX, lightY, lightZ}, lightRadius); +} + +static void android_view_ThreadedRenderer_setOpaque(JNIEnv* env, jobject clazz, + jlong proxyPtr, jboolean opaque) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + proxy->setOpaque(opaque); +} + +static void android_view_ThreadedRenderer_setWideGamut(JNIEnv* env, jobject clazz, + jlong proxyPtr, jboolean wideGamut) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + proxy->setWideGamut(wideGamut); +} + +static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz, + jlong proxyPtr, jlongArray frameInfo, jint frameInfoSize) { + LOG_ALWAYS_FATAL_IF(frameInfoSize != UI_THREAD_FRAME_INFO_SIZE, + "Mismatched size expectations, given %d expected %d", + frameInfoSize, UI_THREAD_FRAME_INFO_SIZE); + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + env->GetLongArrayRegion(frameInfo, 0, frameInfoSize, proxy->frameInfo()); + return proxy->syncAndDrawFrame(); +} + +static void android_view_ThreadedRenderer_destroy(JNIEnv* env, jobject clazz, + jlong proxyPtr, jlong rootNodePtr) { + RootRenderNode* rootRenderNode = reinterpret_cast<RootRenderNode*>(rootNodePtr); + rootRenderNode->destroy(); + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + proxy->destroy(); +} + +static void android_view_ThreadedRenderer_registerAnimatingRenderNode(JNIEnv* env, jobject clazz, + jlong rootNodePtr, jlong animatingNodePtr) { + RootRenderNode* rootRenderNode = reinterpret_cast<RootRenderNode*>(rootNodePtr); + RenderNode* animatingNode = reinterpret_cast<RenderNode*>(animatingNodePtr); + rootRenderNode->attachAnimatingNode(animatingNode); +} + +static void android_view_ThreadedRenderer_registerVectorDrawableAnimator(JNIEnv* env, jobject clazz, + jlong rootNodePtr, jlong animatorPtr) { + RootRenderNode* rootRenderNode = reinterpret_cast<RootRenderNode*>(rootNodePtr); + PropertyValuesAnimatorSet* animator = reinterpret_cast<PropertyValuesAnimatorSet*>(animatorPtr); + rootRenderNode->addVectorDrawableAnimator(animator); +} + +static void android_view_ThreadedRenderer_invokeFunctor(JNIEnv* env, jobject clazz, + jlong functorPtr, jboolean waitForCompletion) { + Functor* functor = reinterpret_cast<Functor*>(functorPtr); + RenderProxy::invokeFunctor(functor, waitForCompletion); +} + +static jlong android_view_ThreadedRenderer_createTextureLayer(JNIEnv* env, jobject clazz, + jlong proxyPtr) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + DeferredLayerUpdater* layer = proxy->createTextureLayer(); + return reinterpret_cast<jlong>(layer); +} + +static void android_view_ThreadedRenderer_buildLayer(JNIEnv* env, jobject clazz, + jlong proxyPtr, jlong nodePtr) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + RenderNode* node = reinterpret_cast<RenderNode*>(nodePtr); + proxy->buildLayer(node); +} + +static jboolean android_view_ThreadedRenderer_copyLayerInto(JNIEnv* env, jobject clazz, + jlong proxyPtr, jlong layerPtr, jlong bitmapPtr) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerPtr); + SkBitmap bitmap; + bitmap::toBitmap(bitmapPtr).getSkBitmap(&bitmap); + return proxy->copyLayerInto(layer, bitmap); +} + +static void android_view_ThreadedRenderer_pushLayerUpdate(JNIEnv* env, jobject clazz, + jlong proxyPtr, jlong layerPtr) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerPtr); + proxy->pushLayerUpdate(layer); +} + +static void android_view_ThreadedRenderer_cancelLayerUpdate(JNIEnv* env, jobject clazz, + jlong proxyPtr, jlong layerPtr) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerPtr); + proxy->cancelLayerUpdate(layer); +} + +static void android_view_ThreadedRenderer_detachSurfaceTexture(JNIEnv* env, jobject clazz, + jlong proxyPtr, jlong layerPtr) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerPtr); + proxy->detachSurfaceTexture(layer); +} + +static void android_view_ThreadedRenderer_destroyHardwareResources(JNIEnv* env, jobject clazz, + jlong proxyPtr) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + proxy->destroyHardwareResources(); +} + +static void android_view_ThreadedRenderer_trimMemory(JNIEnv* env, jobject clazz, + jint level) { + RenderProxy::trimMemory(level); +} + +static void android_view_ThreadedRenderer_overrideProperty(JNIEnv* env, jobject clazz, + jstring name, jstring value) { + const char* nameCharArray = env->GetStringUTFChars(name, NULL); + const char* valueCharArray = env->GetStringUTFChars(value, NULL); + RenderProxy::overrideProperty(nameCharArray, valueCharArray); + env->ReleaseStringUTFChars(name, nameCharArray); + env->ReleaseStringUTFChars(name, valueCharArray); +} + +static void android_view_ThreadedRenderer_fence(JNIEnv* env, jobject clazz, + jlong proxyPtr) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + proxy->fence(); +} + +static void android_view_ThreadedRenderer_stopDrawing(JNIEnv* env, jobject clazz, + jlong proxyPtr) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + proxy->stopDrawing(); +} + +static void android_view_ThreadedRenderer_notifyFramePending(JNIEnv* env, jobject clazz, + jlong proxyPtr) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + proxy->notifyFramePending(); +} + +static void android_view_ThreadedRenderer_dumpProfileInfo(JNIEnv* env, jobject clazz, + jlong proxyPtr, jobject javaFileDescriptor, jint dumpFlags) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + int fd = jniGetFDFromFileDescriptor(env, javaFileDescriptor); + proxy->dumpProfileInfo(fd, dumpFlags); +} + +static void android_view_ThreadedRenderer_addRenderNode(JNIEnv* env, jobject clazz, + jlong proxyPtr, jlong renderNodePtr, jboolean placeFront) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + proxy->addRenderNode(renderNode, placeFront); +} + +static void android_view_ThreadedRenderer_removeRenderNode(JNIEnv* env, jobject clazz, + jlong proxyPtr, jlong renderNodePtr) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + proxy->removeRenderNode(renderNode); +} + +static void android_view_ThreadedRendererd_drawRenderNode(JNIEnv* env, jobject clazz, + jlong proxyPtr, jlong renderNodePtr) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + proxy->drawRenderNode(renderNode); +} + +static void android_view_ThreadedRenderer_setContentDrawBounds(JNIEnv* env, + jobject clazz, jlong proxyPtr, jint left, jint top, jint right, jint bottom) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + proxy->setContentDrawBounds(left, top, right, bottom); +} + +class JGlobalRefHolder { +public: + JGlobalRefHolder(JavaVM* vm, jobject object) : mVm(vm), mObject(object) {} + + virtual ~JGlobalRefHolder() { + getenv(mVm)->DeleteGlobalRef(mObject); + mObject = nullptr; + } + + jobject object() { return mObject; } + JavaVM* vm() { return mVm; } + +private: + JGlobalRefHolder(const JGlobalRefHolder&) = delete; + void operator=(const JGlobalRefHolder&) = delete; + + JavaVM* mVm; + jobject mObject; +}; + +static void android_view_ThreadedRenderer_setPictureCapturedCallbackJNI(JNIEnv* env, + jobject clazz, jlong proxyPtr, jobject pictureCallback) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + if (!pictureCallback) { + proxy->setPictureCapturedCallback(nullptr); + } else { + JavaVM* vm = nullptr; + LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Unable to get Java VM"); + auto globalCallbackRef = std::make_shared<JGlobalRefHolder>(vm, + env->NewGlobalRef(pictureCallback)); + proxy->setPictureCapturedCallback([globalCallbackRef](sk_sp<SkPicture>&& picture) { + JNIEnv* env = getenv(globalCallbackRef->vm()); + Picture* wrapper = new Picture{std::move(picture)}; + env->CallStaticVoidMethod(gHardwareRenderer.clazz, + gHardwareRenderer.invokePictureCapturedCallback, + static_cast<jlong>(reinterpret_cast<intptr_t>(wrapper)), + globalCallbackRef->object()); + }); + } +} + +static void android_view_ThreadedRenderer_setFrameCallback(JNIEnv* env, + jobject clazz, jlong proxyPtr, jobject frameCallback) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + if (!frameCallback) { + proxy->setFrameCallback(nullptr); + } else { + JavaVM* vm = nullptr; + LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Unable to get Java VM"); + auto globalCallbackRef = std::make_shared<JGlobalRefHolder>(vm, + env->NewGlobalRef(frameCallback)); + proxy->setFrameCallback([globalCallbackRef](int64_t frameNr) { + JNIEnv* env = getenv(globalCallbackRef->vm()); + env->CallVoidMethod(globalCallbackRef->object(), gFrameDrawingCallback.onFrameDraw, + static_cast<jlong>(frameNr)); + }); + } +} + +static void android_view_ThreadedRenderer_setFrameCompleteCallback(JNIEnv* env, + jobject clazz, jlong proxyPtr, jobject callback) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + if (!callback) { + proxy->setFrameCompleteCallback(nullptr); + } else { + sp<FrameCompleteWrapper> wrapper = new FrameCompleteWrapper{env, callback}; + proxy->setFrameCompleteCallback([wrapper](int64_t frameNr) { + wrapper->onFrameComplete(frameNr); + }); + } +} + +static jint android_view_ThreadedRenderer_copySurfaceInto(JNIEnv* env, + jobject clazz, jobject jsurface, jint left, jint top, + jint right, jint bottom, jlong bitmapPtr) { + SkBitmap bitmap; + bitmap::toBitmap(bitmapPtr).getSkBitmap(&bitmap); + ANativeWindow* window = fromSurface(env, jsurface); + jint result = RenderProxy::copySurfaceInto(window, left, top, right, bottom, &bitmap); + ANativeWindow_release(window); + return result; +} + +class ContextFactory : public IContextFactory { +public: + virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) { + return new AnimationContext(clock); + } +}; + +static jobject android_view_ThreadedRenderer_createHardwareBitmapFromRenderNode(JNIEnv* env, + jobject clazz, jlong renderNodePtr, jint jwidth, jint jheight) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + if (jwidth <= 0 || jheight <= 0) { + ALOGW("Invalid width %d or height %d", jwidth, jheight); + return nullptr; + } + + uint32_t width = jwidth; + uint32_t height = jheight; + + // Create an ImageReader wired up to a BufferItemConsumer + AImageReader* rawReader; + media_status_t result = + AImageReader_newWithUsage(width, height, AIMAGE_FORMAT_RGBA_8888, + AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE, 2, &rawReader); + std::unique_ptr<AImageReader, decltype(&AImageReader_delete)> reader(rawReader, + AImageReader_delete); + + if (result != AMEDIA_OK) { + ALOGW("Error creating image reader!"); + return nullptr; + } + + // Note that ownership of this window is maintained by AImageReader, so we + // shouldn't need to wrap around a smart pointer. + ANativeWindow* window; + result = AImageReader_getWindow(rawReader, &window); + + if (result != AMEDIA_OK) { + ALOGW("Error retrieving the native window!"); + return nullptr; + } + + // Render into the surface + { + ContextFactory factory; + RenderProxy proxy{true, renderNode, &factory}; + proxy.setSwapBehavior(SwapBehavior::kSwap_discardBuffer); + proxy.setSurface(window); + // Shadows can't be used via this interface, so just set the light source + // to all 0s. + proxy.setLightAlpha(0, 0); + proxy.setLightGeometry((Vector3){0, 0, 0}, 0); + nsecs_t vsync = systemTime(SYSTEM_TIME_MONOTONIC); + UiFrameInfoBuilder(proxy.frameInfo()) + .setVsync(vsync, vsync) + .addFlag(FrameInfoFlags::SurfaceCanvas); + proxy.syncAndDrawFrame(); + } + + AImage* rawImage; + result = AImageReader_acquireNextImage(rawReader, &rawImage); + std::unique_ptr<AImage, decltype(&AImage_delete)> image(rawImage, AImage_delete); + if (result != AMEDIA_OK) { + ALOGW("Error reading image: %d!", result); + return nullptr; + } + + AHardwareBuffer* buffer; + result = AImage_getHardwareBuffer(rawImage, &buffer); + + AHardwareBuffer_Desc desc; + AHardwareBuffer_describe(buffer, &desc); + + if (desc.width != width || desc.height != height) { + ALOGW("AHardwareBuffer size mismatch, got %dx%d expected %dx%d", desc.width, desc.height, + width, height); + // Continue I guess? + } + + sk_sp<SkColorSpace> cs = uirenderer::DataSpaceToColorSpace( + static_cast<android_dataspace>(ANativeWindow_getBuffersDataSpace(window))); + if (cs == nullptr) { + // nullptr is treated as SRGB in Skia, thus explicitly use SRGB in order to make sure + // the returned bitmap has a color space. + cs = SkColorSpace::MakeSRGB(); + } + sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, cs); + return bitmap::createBitmap(env, bitmap.release(), + android::bitmap::kBitmapCreateFlag_Premultiplied); +} + +static void android_view_ThreadedRenderer_disableVsync(JNIEnv*, jclass) { + RenderProxy::disableVsync(); +} + +static void android_view_ThreadedRenderer_setHighContrastText(JNIEnv*, jclass, jboolean enable) { + Properties::enableHighContrastText = enable; +} + +static void android_view_ThreadedRenderer_hackySetRTAnimationsEnabled(JNIEnv*, jclass, + jboolean enable) { + Properties::enableRTAnimations = enable; +} + +static void android_view_ThreadedRenderer_setDebuggingEnabled(JNIEnv*, jclass, jboolean enable) { + Properties::debuggingEnabled = enable; +} + +static void android_view_ThreadedRenderer_setIsolatedProcess(JNIEnv*, jclass, jboolean isolated) { + Properties::isolatedProcess = isolated; +} + +static void android_view_ThreadedRenderer_setContextPriority(JNIEnv*, jclass, + jint contextPriority) { + Properties::contextPriority = contextPriority; +} + +static void android_view_ThreadedRenderer_allocateBuffers(JNIEnv* env, jobject clazz, + jlong proxyPtr) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + proxy->allocateBuffers(); +} + +static void android_view_ThreadedRenderer_setForceDark(JNIEnv* env, jobject clazz, + jlong proxyPtr, jboolean enable) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + proxy->setForceDark(enable); +} + +static void android_view_ThreadedRenderer_preload(JNIEnv*, jclass) { + RenderProxy::preload(); +} + +// ---------------------------------------------------------------------------- +// HardwareRendererObserver +// ---------------------------------------------------------------------------- + +static void android_view_ThreadedRenderer_addObserver(JNIEnv* env, jclass clazz, + jlong proxyPtr, jlong observerPtr) { + HardwareRendererObserver* observer = reinterpret_cast<HardwareRendererObserver*>(observerPtr); + renderthread::RenderProxy* renderProxy = + reinterpret_cast<renderthread::RenderProxy*>(proxyPtr); + + renderProxy->addFrameMetricsObserver(observer); +} + +static void android_view_ThreadedRenderer_removeObserver(JNIEnv* env, jclass clazz, + jlong proxyPtr, jlong observerPtr) { + HardwareRendererObserver* observer = reinterpret_cast<HardwareRendererObserver*>(observerPtr); + renderthread::RenderProxy* renderProxy = + reinterpret_cast<renderthread::RenderProxy*>(proxyPtr); + + renderProxy->removeFrameMetricsObserver(observer); +} + +// ---------------------------------------------------------------------------- +// Shaders +// ---------------------------------------------------------------------------- + +static void android_view_ThreadedRenderer_setupShadersDiskCache(JNIEnv* env, jobject clazz, + jstring diskCachePath, jstring skiaDiskCachePath) { + const char* cacheArray = env->GetStringUTFChars(diskCachePath, NULL); + android::egl_set_cache_filename(cacheArray); + env->ReleaseStringUTFChars(diskCachePath, cacheArray); + + const char* skiaCacheArray = env->GetStringUTFChars(skiaDiskCachePath, NULL); + uirenderer::skiapipeline::ShaderCache::get().setFilename(skiaCacheArray); + env->ReleaseStringUTFChars(skiaDiskCachePath, skiaCacheArray); +} + +// ---------------------------------------------------------------------------- +// JNI Glue +// ---------------------------------------------------------------------------- + +const char* const kClassPathName = "android/graphics/HardwareRenderer"; + +static const JNINativeMethod gMethods[] = { + { "nRotateProcessStatsBuffer", "()V", (void*) android_view_ThreadedRenderer_rotateProcessStatsBuffer }, + { "nSetProcessStatsBuffer", "(I)V", (void*) android_view_ThreadedRenderer_setProcessStatsBuffer }, + { "nGetRenderThreadTid", "(J)I", (void*) android_view_ThreadedRenderer_getRenderThreadTid }, + { "nCreateRootRenderNode", "()J", (void*) android_view_ThreadedRenderer_createRootRenderNode }, + { "nCreateProxy", "(ZZJ)J", (void*) android_view_ThreadedRenderer_createProxy }, + { "nDeleteProxy", "(J)V", (void*) android_view_ThreadedRenderer_deleteProxy }, + { "nLoadSystemProperties", "(J)Z", (void*) android_view_ThreadedRenderer_loadSystemProperties }, + { "nSetName", "(JLjava/lang/String;)V", (void*) android_view_ThreadedRenderer_setName }, + { "nSetSurface", "(JLandroid/view/Surface;Z)V", (void*) android_view_ThreadedRenderer_setSurface }, + { "nPause", "(J)Z", (void*) android_view_ThreadedRenderer_pause }, + { "nSetStopped", "(JZ)V", (void*) android_view_ThreadedRenderer_setStopped }, + { "nSetLightAlpha", "(JFF)V", (void*) android_view_ThreadedRenderer_setLightAlpha }, + { "nSetLightGeometry", "(JFFFF)V", (void*) android_view_ThreadedRenderer_setLightGeometry }, + { "nSetOpaque", "(JZ)V", (void*) android_view_ThreadedRenderer_setOpaque }, + { "nSetWideGamut", "(JZ)V", (void*) android_view_ThreadedRenderer_setWideGamut }, + { "nSyncAndDrawFrame", "(J[JI)I", (void*) android_view_ThreadedRenderer_syncAndDrawFrame }, + { "nDestroy", "(JJ)V", (void*) android_view_ThreadedRenderer_destroy }, + { "nRegisterAnimatingRenderNode", "(JJ)V", (void*) android_view_ThreadedRenderer_registerAnimatingRenderNode }, + { "nRegisterVectorDrawableAnimator", "(JJ)V", (void*) android_view_ThreadedRenderer_registerVectorDrawableAnimator }, + { "nInvokeFunctor", "(JZ)V", (void*) android_view_ThreadedRenderer_invokeFunctor }, + { "nCreateTextureLayer", "(J)J", (void*) android_view_ThreadedRenderer_createTextureLayer }, + { "nBuildLayer", "(JJ)V", (void*) android_view_ThreadedRenderer_buildLayer }, + { "nCopyLayerInto", "(JJJ)Z", (void*) android_view_ThreadedRenderer_copyLayerInto }, + { "nPushLayerUpdate", "(JJ)V", (void*) android_view_ThreadedRenderer_pushLayerUpdate }, + { "nCancelLayerUpdate", "(JJ)V", (void*) android_view_ThreadedRenderer_cancelLayerUpdate }, + { "nDetachSurfaceTexture", "(JJ)V", (void*) android_view_ThreadedRenderer_detachSurfaceTexture }, + { "nDestroyHardwareResources", "(J)V", (void*) android_view_ThreadedRenderer_destroyHardwareResources }, + { "nTrimMemory", "(I)V", (void*) android_view_ThreadedRenderer_trimMemory }, + { "nOverrideProperty", "(Ljava/lang/String;Ljava/lang/String;)V", (void*) android_view_ThreadedRenderer_overrideProperty }, + { "nFence", "(J)V", (void*) android_view_ThreadedRenderer_fence }, + { "nStopDrawing", "(J)V", (void*) android_view_ThreadedRenderer_stopDrawing }, + { "nNotifyFramePending", "(J)V", (void*) android_view_ThreadedRenderer_notifyFramePending }, + { "nDumpProfileInfo", "(JLjava/io/FileDescriptor;I)V", (void*) android_view_ThreadedRenderer_dumpProfileInfo }, + { "setupShadersDiskCache", "(Ljava/lang/String;Ljava/lang/String;)V", + (void*) android_view_ThreadedRenderer_setupShadersDiskCache }, + { "nAddRenderNode", "(JJZ)V", (void*) android_view_ThreadedRenderer_addRenderNode}, + { "nRemoveRenderNode", "(JJ)V", (void*) android_view_ThreadedRenderer_removeRenderNode}, + { "nDrawRenderNode", "(JJ)V", (void*) android_view_ThreadedRendererd_drawRenderNode}, + { "nSetContentDrawBounds", "(JIIII)V", (void*)android_view_ThreadedRenderer_setContentDrawBounds}, + { "nSetPictureCaptureCallback", "(JLandroid/graphics/HardwareRenderer$PictureCapturedCallback;)V", + (void*) android_view_ThreadedRenderer_setPictureCapturedCallbackJNI }, + { "nSetFrameCallback", "(JLandroid/graphics/HardwareRenderer$FrameDrawingCallback;)V", + (void*)android_view_ThreadedRenderer_setFrameCallback}, + { "nSetFrameCompleteCallback", "(JLandroid/graphics/HardwareRenderer$FrameCompleteCallback;)V", + (void*)android_view_ThreadedRenderer_setFrameCompleteCallback }, + { "nAddObserver", "(JJ)V", (void*)android_view_ThreadedRenderer_addObserver }, + { "nRemoveObserver", "(JJ)V", (void*)android_view_ThreadedRenderer_removeObserver }, + { "nCopySurfaceInto", "(Landroid/view/Surface;IIIIJ)I", + (void*)android_view_ThreadedRenderer_copySurfaceInto }, + { "nCreateHardwareBitmap", "(JII)Landroid/graphics/Bitmap;", + (void*)android_view_ThreadedRenderer_createHardwareBitmapFromRenderNode }, + { "disableVsync", "()V", (void*)android_view_ThreadedRenderer_disableVsync }, + { "nSetHighContrastText", "(Z)V", (void*)android_view_ThreadedRenderer_setHighContrastText }, + { "nHackySetRTAnimationsEnabled", "(Z)V", + (void*)android_view_ThreadedRenderer_hackySetRTAnimationsEnabled }, + { "nSetDebuggingEnabled", "(Z)V", (void*)android_view_ThreadedRenderer_setDebuggingEnabled }, + { "nSetIsolatedProcess", "(Z)V", (void*)android_view_ThreadedRenderer_setIsolatedProcess }, + { "nSetContextPriority", "(I)V", (void*)android_view_ThreadedRenderer_setContextPriority }, + { "nAllocateBuffers", "(J)V", (void*)android_view_ThreadedRenderer_allocateBuffers }, + { "nSetForceDark", "(JZ)V", (void*)android_view_ThreadedRenderer_setForceDark }, + { "preload", "()V", (void*)android_view_ThreadedRenderer_preload }, +}; + +static JavaVM* mJvm = nullptr; + +static void attachRenderThreadToJvm(const char* name) { + LOG_ALWAYS_FATAL_IF(!mJvm, "No jvm but we set the hook??"); + + JavaVMAttachArgs args; + args.version = JNI_VERSION_1_4; + args.name = name; + args.group = NULL; + JNIEnv* env; + mJvm->AttachCurrentThreadAsDaemon(&env, (void*) &args); +} + +int register_android_view_ThreadedRenderer(JNIEnv* env) { + env->GetJavaVM(&mJvm); + RenderThread::setOnStartHook(&attachRenderThreadToJvm); + + jclass hardwareRenderer = FindClassOrDie(env, + "android/graphics/HardwareRenderer"); + gHardwareRenderer.clazz = reinterpret_cast<jclass>(env->NewGlobalRef(hardwareRenderer)); + gHardwareRenderer.invokePictureCapturedCallback = GetStaticMethodIDOrDie(env, hardwareRenderer, + "invokePictureCapturedCallback", + "(JLandroid/graphics/HardwareRenderer$PictureCapturedCallback;)V"); + + jclass frameCallbackClass = FindClassOrDie(env, + "android/graphics/HardwareRenderer$FrameDrawingCallback"); + gFrameDrawingCallback.onFrameDraw = GetMethodIDOrDie(env, frameCallbackClass, + "onFrameDraw", "(J)V"); + + jclass frameCompleteClass = FindClassOrDie(env, + "android/graphics/HardwareRenderer$FrameCompleteCallback"); + gFrameCompleteCallback.onFrameComplete = GetMethodIDOrDie(env, frameCompleteClass, + "onFrameComplete", "(J)V"); + + void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE); + fromSurface = (ANW_fromSurface)dlsym(handle_, "ANativeWindow_fromSurface"); + LOG_ALWAYS_FATAL_IF(fromSurface == nullptr, + "Failed to find required symbol ANativeWindow_fromSurface!"); + + return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods)); +} + +}; // namespace android diff --git a/libs/hwui/jni/android_graphics_HardwareRendererObserver.cpp b/libs/hwui/jni/android_graphics_HardwareRendererObserver.cpp new file mode 100644 index 000000000000..5b3e65648981 --- /dev/null +++ b/libs/hwui/jni/android_graphics_HardwareRendererObserver.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "android_graphics_HardwareRendererObserver.h" + +#include "graphics_jni_helpers.h" +#include "nativehelper/jni_macros.h" + +#include <array> + +namespace android { + +struct { + jmethodID callback; +} gHardwareRendererObserverClassInfo; + +static JNIEnv* getenv(JavaVM* vm) { + JNIEnv* env; + if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { + LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", vm); + } + return env; +} + +HardwareRendererObserver::HardwareRendererObserver(JavaVM *vm, jobject observer) : mVm(vm) { + mObserverWeak = getenv(mVm)->NewWeakGlobalRef(observer); + LOG_ALWAYS_FATAL_IF(mObserverWeak == nullptr, + "unable to create frame stats observer reference"); +} + +HardwareRendererObserver::~HardwareRendererObserver() { + JNIEnv* env = getenv(mVm); + env->DeleteWeakGlobalRef(mObserverWeak); +} + +bool HardwareRendererObserver::getNextBuffer(JNIEnv* env, jlongArray metrics, int* dropCount) { + jsize bufferSize = env->GetArrayLength(reinterpret_cast<jarray>(metrics)); + LOG_ALWAYS_FATAL_IF(bufferSize != HardwareRendererObserver::kBufferSize, + "Mismatched Java/Native FrameMetrics data format."); + + FrameMetricsNotification& elem = mRingBuffer[mNextInQueue]; + if (elem.hasData.load()) { + env->SetLongArrayRegion(metrics, 0, kBufferSize, elem.buffer); + *dropCount = elem.dropCount; + mNextInQueue = (mNextInQueue + 1) % kRingSize; + elem.hasData = false; + return true; + } + + return false; +} + +void HardwareRendererObserver::notify(const int64_t* stats) { + FrameMetricsNotification& elem = mRingBuffer[mNextFree]; + + if (!elem.hasData.load()) { + memcpy(elem.buffer, stats, kBufferSize * sizeof(stats[0])); + + elem.dropCount = mDroppedReports; + mDroppedReports = 0; + mNextFree = (mNextFree + 1) % kRingSize; + elem.hasData = true; + + JNIEnv* env = getenv(mVm); + jobject target = env->NewLocalRef(mObserverWeak); + if (target != nullptr) { + env->CallVoidMethod(target, gHardwareRendererObserverClassInfo.callback); + env->DeleteLocalRef(target); + } + } else { + mDroppedReports++; + } +} + +static jlong android_graphics_HardwareRendererObserver_createObserver(JNIEnv* env, + jobject observerObj) { + JavaVM* vm = nullptr; + if (env->GetJavaVM(&vm) != JNI_OK) { + LOG_ALWAYS_FATAL("Unable to get Java VM"); + return 0; + } + + HardwareRendererObserver* observer = new HardwareRendererObserver(vm, observerObj); + return reinterpret_cast<jlong>(observer); +} + +static jint android_graphics_HardwareRendererObserver_getNextBuffer(JNIEnv* env, jobject, + jlong observerPtr, + jlongArray metrics) { + HardwareRendererObserver* observer = reinterpret_cast<HardwareRendererObserver*>(observerPtr); + int dropCount = 0; + if (observer->getNextBuffer(env, metrics, &dropCount)) { + return dropCount; + } else { + return -1; + } +} + +static const std::array gMethods = { + MAKE_JNI_NATIVE_METHOD("nCreateObserver", "()J", + android_graphics_HardwareRendererObserver_createObserver), + MAKE_JNI_NATIVE_METHOD("nGetNextBuffer", "(J[J)I", + android_graphics_HardwareRendererObserver_getNextBuffer), +}; + +int register_android_graphics_HardwareRendererObserver(JNIEnv* env) { + + jclass observerClass = FindClassOrDie(env, "android/graphics/HardwareRendererObserver"); + gHardwareRendererObserverClassInfo.callback = GetMethodIDOrDie(env, observerClass, + "notifyDataAvailable", "()V"); + + return RegisterMethodsOrDie(env, "android/graphics/HardwareRendererObserver", + gMethods.data(), gMethods.size()); + +} + +} // namespace android
\ No newline at end of file diff --git a/libs/hwui/jni/android_graphics_HardwareRendererObserver.h b/libs/hwui/jni/android_graphics_HardwareRendererObserver.h new file mode 100644 index 000000000000..62111fd7d7a1 --- /dev/null +++ b/libs/hwui/jni/android_graphics_HardwareRendererObserver.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "jni.h" + +#include <FrameInfo.h> +#include <FrameMetricsObserver.h> + +namespace android { + +/* + * Implements JNI layer for hwui frame metrics reporting. + */ +class HardwareRendererObserver : public uirenderer::FrameMetricsObserver { +public: + HardwareRendererObserver(JavaVM *vm, jobject observer); + ~HardwareRendererObserver(); + + /** + * Retrieves frame metrics for the oldest frame that the renderer has retained. The renderer + * will retain a buffer until it has been retrieved, via this method, or its internal storage + * is exhausted at which point it informs the caller of how many frames it has failed to store + * since the last time this method was invoked. + * @param env java env required to populate the provided buffer array + * @param metrics output parameter that represents the buffer of metrics that is to be filled + * @param dropCount output parameter that is updated to reflect the number of buffers that were + discarded since the last successful invocation of this method. + * @return true if there was data to populate the array and false otherwise. If false then + * neither the metrics buffer or dropCount will be modified. + */ + bool getNextBuffer(JNIEnv* env, jlongArray metrics, int* dropCount); + + void notify(const int64_t* stats) override; + +private: + static constexpr int kBufferSize = static_cast<int>(uirenderer::FrameInfoIndex::NumIndexes); + static constexpr int kRingSize = 3; + + class FrameMetricsNotification { + public: + FrameMetricsNotification() {} + + std::atomic_bool hasData = false; + int64_t buffer[kBufferSize]; + int dropCount = 0; + private: + // non-copyable + FrameMetricsNotification(const FrameMetricsNotification&) = delete; + FrameMetricsNotification& operator=(const FrameMetricsNotification& ) = delete; + }; + + JavaVM* const mVm; + jweak mObserverWeak; + + int mNextFree = 0; + int mNextInQueue = 0; + FrameMetricsNotification mRingBuffer[kRingSize]; + + int mDroppedReports = 0; +}; + +} // namespace android diff --git a/libs/hwui/jni/android_graphics_Matrix.cpp b/libs/hwui/jni/android_graphics_Matrix.cpp new file mode 100644 index 000000000000..7338ef24cb58 --- /dev/null +++ b/libs/hwui/jni/android_graphics_Matrix.cpp @@ -0,0 +1,396 @@ +/* libs/android_runtime/android/graphics/Matrix.cpp +** +** Copyright 2006, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#include "GraphicsJNI.h" +#include "Matrix.h" +#include "SkMatrix.h" + +namespace android { + +static_assert(sizeof(SkMatrix) == 40, "Unexpected sizeof(SkMatrix), " + "update size in Matrix.java#NATIVE_ALLOCATION_SIZE and here"); +static_assert(SK_SCALAR_IS_FLOAT, "SK_SCALAR_IS_FLOAT is false, " + "only float scalar is supported"); + +class SkMatrixGlue { +public: + + // ---------------- Regular JNI ----------------------------- + + static void finalizer(jlong objHandle) { + SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle); + delete obj; + } + + static jlong getNativeFinalizer(JNIEnv* env, jobject clazz) { + return static_cast<jlong>(reinterpret_cast<uintptr_t>(&finalizer)); + } + + static jlong create(JNIEnv* env, jobject clazz, jlong srcHandle) { + const SkMatrix* src = reinterpret_cast<SkMatrix*>(srcHandle); + SkMatrix* obj = new SkMatrix(); + if (src) + *obj = *src; + else + obj->reset(); + return reinterpret_cast<jlong>(obj); + } + + // ---------------- @FastNative ----------------------------- + + static void mapPoints(JNIEnv* env, jobject clazz, jlong matrixHandle, + jfloatArray dst, jint dstIndex, jfloatArray src, jint srcIndex, + jint ptCount, jboolean isPts) { + SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle); + SkASSERT(ptCount >= 0); + AutoJavaFloatArray autoSrc(env, src, srcIndex + (ptCount << 1), + kRO_JNIAccess); + AutoJavaFloatArray autoDst(env, dst, dstIndex + (ptCount << 1), + kRW_JNIAccess); + float* srcArray = autoSrc.ptr() + srcIndex; + float* dstArray = autoDst.ptr() + dstIndex; + if (isPts) + matrix->mapPoints((SkPoint*) dstArray, (const SkPoint*) srcArray, + ptCount); + else + matrix->mapVectors((SkVector*) dstArray, (const SkVector*) srcArray, + ptCount); + } + + static jboolean mapRect__RectFRectF(JNIEnv* env, jobject clazz, + jlong matrixHandle, jobjectArray dst, jobject src) { + SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle); + SkRect dst_, src_; + GraphicsJNI::jrectf_to_rect(env, src, &src_); + jboolean rectStaysRect = matrix->mapRect(&dst_, src_); + GraphicsJNI::rect_to_jrectf(dst_, env, dst); + return rectStaysRect ? JNI_TRUE : JNI_FALSE; + } + + static jboolean setRectToRect(JNIEnv* env, jobject clazz, + jlong matrixHandle, jobject src, jobject dst, jint stfHandle) { + SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle); + SkMatrix::ScaleToFit stf = static_cast<SkMatrix::ScaleToFit>(stfHandle); + SkRect src_; + GraphicsJNI::jrectf_to_rect(env, src, &src_); + SkRect dst_; + GraphicsJNI::jrectf_to_rect(env, dst, &dst_); + return matrix->setRectToRect(src_, dst_, stf) ? JNI_TRUE : JNI_FALSE; + } + + static jboolean setPolyToPoly(JNIEnv* env, jobject clazz, + jlong matrixHandle, jfloatArray jsrc, jint srcIndex, + jfloatArray jdst, jint dstIndex, jint ptCount) { + SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle); + SkASSERT(srcIndex >= 0); + SkASSERT(dstIndex >= 0); + SkASSERT((unsigned )ptCount <= 4); + + AutoJavaFloatArray autoSrc(env, jsrc, srcIndex + (ptCount << 1), + kRO_JNIAccess); + AutoJavaFloatArray autoDst(env, jdst, dstIndex + (ptCount << 1), + kRW_JNIAccess); + float* src = autoSrc.ptr() + srcIndex; + float* dst = autoDst.ptr() + dstIndex; + bool result; + + result = matrix->setPolyToPoly((const SkPoint*) src, + (const SkPoint*) dst, ptCount); + return result ? JNI_TRUE : JNI_FALSE; + } + + static void getValues(JNIEnv* env, jobject clazz, jlong matrixHandle, + jfloatArray values) { + SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle); + AutoJavaFloatArray autoValues(env, values, 9, kRW_JNIAccess); + float* dst = autoValues.ptr(); + for (int i = 0; i < 9; i++) { + dst[i] = matrix->get(i); + } + } + + static void setValues(JNIEnv* env, jobject clazz, jlong matrixHandle, + jfloatArray values) { + SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle); + AutoJavaFloatArray autoValues(env, values, 9, kRO_JNIAccess); + const float* src = autoValues.ptr(); + + for (int i = 0; i < 9; i++) { + matrix->set(i, src[i]); + } + } + + // ---------------- @CriticalNative ----------------------------- + + static jboolean isIdentity(CRITICAL_JNI_PARAMS_COMMA jlong objHandle) { + SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle); + return obj->isIdentity() ? JNI_TRUE : JNI_FALSE; + } + + static jboolean isAffine(CRITICAL_JNI_PARAMS_COMMA jlong objHandle) { + SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle); + return obj->asAffine(NULL) ? JNI_TRUE : JNI_FALSE; + } + + static jboolean rectStaysRect(CRITICAL_JNI_PARAMS_COMMA jlong objHandle) { + SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle); + return obj->rectStaysRect() ? JNI_TRUE : JNI_FALSE; + } + + static void reset(CRITICAL_JNI_PARAMS_COMMA jlong objHandle) { + SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle); + obj->reset(); + } + + static void set(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jlong otherHandle) { + SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle); + SkMatrix* other = reinterpret_cast<SkMatrix*>(otherHandle); + *obj = *other; + } + + static void setTranslate(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jfloat dx, jfloat dy) { + SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle); + obj->setTranslate(dx, dy); + } + + static void setScale__FFFF(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jfloat sx, jfloat sy, jfloat px, + jfloat py) { + SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle); + obj->setScale(sx, sy, px, py); + } + + static void setScale__FF(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jfloat sx, jfloat sy) { + SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle); + obj->setScale(sx, sy); + } + + static void setRotate__FFF(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jfloat degrees, jfloat px, + jfloat py) { + SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle); + obj->setRotate(degrees, px, py); + } + + static void setRotate__F(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jfloat degrees) { + SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle); + obj->setRotate(degrees); + } + + static void setSinCos__FFFF(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jfloat sinValue, + jfloat cosValue, jfloat px, jfloat py) { + SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle); + obj->setSinCos(sinValue, cosValue, px, py); + } + + static void setSinCos__FF(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jfloat sinValue, + jfloat cosValue) { + SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle); + obj->setSinCos(sinValue, cosValue); + } + + static void setSkew__FFFF(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jfloat kx, jfloat ky, jfloat px, + jfloat py) { + SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle); + obj->setSkew(kx, ky, px, py); + } + + static void setSkew__FF(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jfloat kx, jfloat ky) { + SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle); + obj->setSkew(kx, ky); + } + + static void setConcat(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jlong aHandle, jlong bHandle) { + SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle); + SkMatrix* a = reinterpret_cast<SkMatrix*>(aHandle); + SkMatrix* b = reinterpret_cast<SkMatrix*>(bHandle); + obj->setConcat(*a, *b); + } + + static void preTranslate(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jfloat dx, jfloat dy) { + SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle); + obj->preTranslate(dx, dy); + } + + static void preScale__FFFF(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jfloat sx, jfloat sy, jfloat px, + jfloat py) { + SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle); + obj->preScale(sx, sy, px, py); + } + + static void preScale__FF(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jfloat sx, jfloat sy) { + SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle); + obj->preScale(sx, sy); + } + + static void preRotate__FFF(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jfloat degrees, jfloat px, + jfloat py) { + SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle); + obj->preRotate(degrees, px, py); + } + + static void preRotate__F(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jfloat degrees) { + SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle); + obj->preRotate(degrees); + } + + static void preSkew__FFFF(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jfloat kx, jfloat ky, jfloat px, + jfloat py) { + SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle); + obj->preSkew(kx, ky, px, py); + } + + static void preSkew__FF(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jfloat kx, jfloat ky) { + SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle); + obj->preSkew(kx, ky); + } + + static void preConcat(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jlong otherHandle) { + SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle); + SkMatrix* other = reinterpret_cast<SkMatrix*>(otherHandle); + obj->preConcat(*other); + } + + static void postTranslate(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jfloat dx, jfloat dy) { + SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle); + obj->postTranslate(dx, dy); + } + + static void postScale__FFFF(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jfloat sx, jfloat sy, + jfloat px, jfloat py) { + SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle); + obj->postScale(sx, sy, px, py); + } + + static void postScale__FF(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jfloat sx, jfloat sy) { + SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle); + obj->postScale(sx, sy); + } + + static void postRotate__FFF(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jfloat degrees, jfloat px, + jfloat py) { + SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle); + obj->postRotate(degrees, px, py); + } + + static void postRotate__F(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jfloat degrees) { + SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle); + obj->postRotate(degrees); + } + + static void postSkew__FFFF(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jfloat kx, jfloat ky, jfloat px, + jfloat py) { + SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle); + obj->postSkew(kx, ky, px, py); + } + + static void postSkew__FF(CRITICAL_JNI_PARAMS_COMMA jlong matrixHandle, jfloat kx, jfloat ky) { + SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle); + matrix->postSkew(kx, ky); + } + + static void postConcat(CRITICAL_JNI_PARAMS_COMMA jlong matrixHandle, jlong otherHandle) { + SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle); + SkMatrix* other = reinterpret_cast<SkMatrix*>(otherHandle); + matrix->postConcat(*other); + } + + static jboolean invert(CRITICAL_JNI_PARAMS_COMMA jlong matrixHandle, jlong inverseHandle) { + SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle); + SkMatrix* inverse = reinterpret_cast<SkMatrix*>(inverseHandle); + return matrix->invert(inverse); + } + + static jfloat mapRadius(CRITICAL_JNI_PARAMS_COMMA jlong matrixHandle, jfloat radius) { + SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle); + float result; + result = SkScalarToFloat(matrix->mapRadius(radius)); + return static_cast<jfloat>(result); + } + + static jboolean equals(CRITICAL_JNI_PARAMS_COMMA jlong aHandle, jlong bHandle) { + const SkMatrix* a = reinterpret_cast<SkMatrix*>(aHandle); + const SkMatrix* b = reinterpret_cast<SkMatrix*>(bHandle); + return *a == *b; + } +}; + +static const JNINativeMethod methods[] = { + {"nGetNativeFinalizer", "()J", (void*) SkMatrixGlue::getNativeFinalizer}, + {"nCreate","(J)J", (void*) SkMatrixGlue::create}, + + // ------- @FastNative below here --------------- + {"nMapPoints","(J[FI[FIIZ)V", (void*) SkMatrixGlue::mapPoints}, + {"nMapRect","(JLandroid/graphics/RectF;Landroid/graphics/RectF;)Z", + (void*) SkMatrixGlue::mapRect__RectFRectF}, + {"nSetRectToRect","(JLandroid/graphics/RectF;Landroid/graphics/RectF;I)Z", + (void*) SkMatrixGlue::setRectToRect}, + {"nSetPolyToPoly","(J[FI[FII)Z", (void*) SkMatrixGlue::setPolyToPoly}, + {"nGetValues","(J[F)V", (void*) SkMatrixGlue::getValues}, + {"nSetValues","(J[F)V", (void*) SkMatrixGlue::setValues}, + + // ------- @CriticalNative below here --------------- + {"nIsIdentity","(J)Z", (void*) SkMatrixGlue::isIdentity}, + {"nIsAffine","(J)Z", (void*) SkMatrixGlue::isAffine}, + {"nRectStaysRect","(J)Z", (void*) SkMatrixGlue::rectStaysRect}, + {"nReset","(J)V", (void*) SkMatrixGlue::reset}, + {"nSet","(JJ)V", (void*) SkMatrixGlue::set}, + {"nSetTranslate","(JFF)V", (void*) SkMatrixGlue::setTranslate}, + {"nSetScale","(JFFFF)V", (void*) SkMatrixGlue::setScale__FFFF}, + {"nSetScale","(JFF)V", (void*) SkMatrixGlue::setScale__FF}, + {"nSetRotate","(JFFF)V", (void*) SkMatrixGlue::setRotate__FFF}, + {"nSetRotate","(JF)V", (void*) SkMatrixGlue::setRotate__F}, + {"nSetSinCos","(JFFFF)V", (void*) SkMatrixGlue::setSinCos__FFFF}, + {"nSetSinCos","(JFF)V", (void*) SkMatrixGlue::setSinCos__FF}, + {"nSetSkew","(JFFFF)V", (void*) SkMatrixGlue::setSkew__FFFF}, + {"nSetSkew","(JFF)V", (void*) SkMatrixGlue::setSkew__FF}, + {"nSetConcat","(JJJ)V", (void*) SkMatrixGlue::setConcat}, + {"nPreTranslate","(JFF)V", (void*) SkMatrixGlue::preTranslate}, + {"nPreScale","(JFFFF)V", (void*) SkMatrixGlue::preScale__FFFF}, + {"nPreScale","(JFF)V", (void*) SkMatrixGlue::preScale__FF}, + {"nPreRotate","(JFFF)V", (void*) SkMatrixGlue::preRotate__FFF}, + {"nPreRotate","(JF)V", (void*) SkMatrixGlue::preRotate__F}, + {"nPreSkew","(JFFFF)V", (void*) SkMatrixGlue::preSkew__FFFF}, + {"nPreSkew","(JFF)V", (void*) SkMatrixGlue::preSkew__FF}, + {"nPreConcat","(JJ)V", (void*) SkMatrixGlue::preConcat}, + {"nPostTranslate","(JFF)V", (void*) SkMatrixGlue::postTranslate}, + {"nPostScale","(JFFFF)V", (void*) SkMatrixGlue::postScale__FFFF}, + {"nPostScale","(JFF)V", (void*) SkMatrixGlue::postScale__FF}, + {"nPostRotate","(JFFF)V", (void*) SkMatrixGlue::postRotate__FFF}, + {"nPostRotate","(JF)V", (void*) SkMatrixGlue::postRotate__F}, + {"nPostSkew","(JFFFF)V", (void*) SkMatrixGlue::postSkew__FFFF}, + {"nPostSkew","(JFF)V", (void*) SkMatrixGlue::postSkew__FF}, + {"nPostConcat","(JJ)V", (void*) SkMatrixGlue::postConcat}, + {"nInvert","(JJ)Z", (void*) SkMatrixGlue::invert}, + {"nMapRadius","(JF)F", (void*) SkMatrixGlue::mapRadius}, + {"nEquals", "(JJ)Z", (void*) SkMatrixGlue::equals} +}; + +static jfieldID sNativeInstanceField; + +int register_android_graphics_Matrix(JNIEnv* env) { + int result = RegisterMethodsOrDie(env, "android/graphics/Matrix", methods, NELEM(methods)); + + jclass clazz = FindClassOrDie(env, "android/graphics/Matrix"); + sNativeInstanceField = GetFieldIDOrDie(env, clazz, "native_instance", "J"); + + return result; +} + +SkMatrix* android_graphics_Matrix_getSkMatrix(JNIEnv* env, jobject matrixObj) { + return reinterpret_cast<SkMatrix*>(env->GetLongField(matrixObj, sNativeInstanceField)); +} + +} diff --git a/libs/hwui/jni/android_graphics_Matrix.h b/libs/hwui/jni/android_graphics_Matrix.h new file mode 100644 index 000000000000..fe90d2ef945d --- /dev/null +++ b/libs/hwui/jni/android_graphics_Matrix.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _ANDROID_GRAPHICS_MATRIX_H_ +#define _ANDROID_GRAPHICS_MATRIX_H_ + +#include "jni.h" +#include "SkMatrix.h" + +namespace android { + +/* Gets the underlying SkMatrix from a Matrix object. */ +SkMatrix* android_graphics_Matrix_getSkMatrix(JNIEnv* env, jobject matrixObj); + +} // namespace android + +#endif // _ANDROID_GRAPHICS_MATRIX_H_ diff --git a/libs/hwui/jni/android_graphics_Picture.cpp b/libs/hwui/jni/android_graphics_Picture.cpp new file mode 100644 index 000000000000..403efb2ab9c9 --- /dev/null +++ b/libs/hwui/jni/android_graphics_Picture.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "CreateJavaOutputStreamAdaptor.h" +#include "GraphicsJNI.h" +#include "Picture.h" +#include "SkCanvas.h" +#include "SkStream.h" + +#include <array> +#include "nativehelper/jni_macros.h" + +namespace android { + +static jlong android_graphics_Picture_newPicture(JNIEnv* env, jobject, jlong srcHandle) { + const Picture* src = reinterpret_cast<Picture*>(srcHandle); + return reinterpret_cast<jlong>(new Picture(src)); +} + +static jlong android_graphics_Picture_deserialize(JNIEnv* env, jobject, jobject jstream, + jbyteArray jstorage) { + Picture* picture = NULL; + SkStream* strm = CreateJavaInputStreamAdaptor(env, jstream, jstorage); + if (strm) { + picture = Picture::CreateFromStream(strm); + delete strm; + } + return reinterpret_cast<jlong>(picture); +} + +static void android_graphics_Picture_killPicture(JNIEnv* env, jobject, jlong pictureHandle) { + Picture* picture = reinterpret_cast<Picture*>(pictureHandle); + SkASSERT(picture); + delete picture; +} + +static void android_graphics_Picture_draw(JNIEnv* env, jobject, jlong canvasHandle, + jlong pictureHandle) { + Canvas* canvas = reinterpret_cast<Canvas*>(canvasHandle); + Picture* picture = reinterpret_cast<Picture*>(pictureHandle); + SkASSERT(canvas); + SkASSERT(picture); + picture->draw(canvas); +} + +static jboolean android_graphics_Picture_serialize(JNIEnv* env, jobject, jlong pictureHandle, + jobject jstream, jbyteArray jstorage) { + Picture* picture = reinterpret_cast<Picture*>(pictureHandle); + SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage); + + if (NULL != strm) { + picture->serialize(strm); + delete strm; + return JNI_TRUE; + } + return JNI_FALSE; +} + +static jint android_graphics_Picture_getWidth(JNIEnv* env, jobject, jlong pictureHandle) { + Picture* pict = reinterpret_cast<Picture*>(pictureHandle); + return static_cast<jint>(pict->width()); +} + +static jint android_graphics_Picture_getHeight(JNIEnv* env, jobject, jlong pictureHandle) { + Picture* pict = reinterpret_cast<Picture*>(pictureHandle); + return static_cast<jint>(pict->height()); +} + +static jlong android_graphics_Picture_beginRecording(JNIEnv* env, jobject, jlong pictHandle, + jint w, jint h) { + Picture* pict = reinterpret_cast<Picture*>(pictHandle); + Canvas* canvas = pict->beginRecording(w, h); + return reinterpret_cast<jlong>(canvas); +} + +static void android_graphics_Picture_endRecording(JNIEnv* env, jobject, jlong pictHandle) { + Picture* pict = reinterpret_cast<Picture*>(pictHandle); + pict->endRecording(); +} + +static const std::array gMethods = { + MAKE_JNI_NATIVE_METHOD("nativeGetWidth", "(J)I", android_graphics_Picture_getWidth), + MAKE_JNI_NATIVE_METHOD("nativeGetHeight", "(J)I", android_graphics_Picture_getHeight), + MAKE_JNI_NATIVE_METHOD("nativeConstructor", "(J)J", android_graphics_Picture_newPicture), + MAKE_JNI_NATIVE_METHOD("nativeCreateFromStream", "(Ljava/io/InputStream;[B)J", android_graphics_Picture_deserialize), + MAKE_JNI_NATIVE_METHOD("nativeBeginRecording", "(JII)J", android_graphics_Picture_beginRecording), + MAKE_JNI_NATIVE_METHOD("nativeEndRecording", "(J)V", android_graphics_Picture_endRecording), + MAKE_JNI_NATIVE_METHOD("nativeDraw", "(JJ)V", android_graphics_Picture_draw), + MAKE_JNI_NATIVE_METHOD("nativeWriteToStream", "(JLjava/io/OutputStream;[B)Z", android_graphics_Picture_serialize), + MAKE_JNI_NATIVE_METHOD("nativeDestructor","(J)V", android_graphics_Picture_killPicture) +}; + +int register_android_graphics_Picture(JNIEnv* env) { + return RegisterMethodsOrDie(env, "android/graphics/Picture", gMethods.data(), gMethods.size()); +} + +}; // namespace android diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp new file mode 100644 index 000000000000..85c802b40459 --- /dev/null +++ b/libs/hwui/jni/android_graphics_RenderNode.cpp @@ -0,0 +1,759 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define ATRACE_TAG ATRACE_TAG_VIEW +#include "GraphicsJNI.h" + +#include <Animator.h> +#include <DamageAccumulator.h> +#include <Matrix.h> +#include <RenderNode.h> +#ifdef __ANDROID__ // Layoutlib does not support CanvasContext +#include <renderthread/CanvasContext.h> +#endif +#include <TreeInfo.h> +#include <hwui/Paint.h> +#include <utils/TraceUtils.h> + +namespace android { + +using namespace uirenderer; + +#define SET_AND_DIRTY(prop, val, dirtyFlag) \ + (reinterpret_cast<RenderNode*>(renderNodePtr)->mutateStagingProperties().prop(val) \ + ? (reinterpret_cast<RenderNode*>(renderNodePtr)->setPropertyFieldsDirty(dirtyFlag), true) \ + : false) + +// ---------------------------------------------------------------------------- +// DisplayList view properties +// ---------------------------------------------------------------------------- + +static void android_view_RenderNode_output(JNIEnv* env, jobject clazz, jlong renderNodePtr) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + renderNode->output(); +} + +static jint android_view_RenderNode_getUsageSize(JNIEnv* env, jobject clazz, jlong renderNodePtr) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + return renderNode->getUsageSize(); +} + +static jint android_view_RenderNode_getAllocatedSize(JNIEnv* env, jobject clazz, jlong renderNodePtr) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + return renderNode->getAllocatedSize(); +} + +static jlong android_view_RenderNode_create(JNIEnv* env, jobject, jstring name) { + RenderNode* renderNode = new RenderNode(); + renderNode->incStrong(0); + if (name != NULL) { + const char* textArray = env->GetStringUTFChars(name, NULL); + renderNode->setName(textArray); + env->ReleaseStringUTFChars(name, textArray); + } + return reinterpret_cast<jlong>(renderNode); +} + +static void releaseRenderNode(RenderNode* renderNode) { + renderNode->decStrong(0); +} + +static jlong android_view_RenderNode_getNativeFinalizer(JNIEnv* env, + jobject clazz) { + return static_cast<jlong>(reinterpret_cast<uintptr_t>(&releaseRenderNode)); +} + +static void android_view_RenderNode_setDisplayList(JNIEnv* env, + jobject clazz, jlong renderNodePtr, jlong displayListPtr) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + DisplayList* newData = reinterpret_cast<DisplayList*>(displayListPtr); + renderNode->setStagingDisplayList(newData); +} + +static jboolean android_view_RenderNode_isValid(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) { + return reinterpret_cast<RenderNode*>(renderNodePtr)->isValid(); +} + +// ---------------------------------------------------------------------------- +// RenderProperties - setters +// ---------------------------------------------------------------------------- + +static jboolean android_view_RenderNode_setLayerType(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, jint jlayerType) { + LayerType layerType = static_cast<LayerType>(jlayerType); + return SET_AND_DIRTY(mutateLayerProperties().setType, layerType, RenderNode::GENERIC); +} + +static jboolean android_view_RenderNode_setLayerPaint(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, jlong paintPtr) { + Paint* paint = reinterpret_cast<Paint*>(paintPtr); + return SET_AND_DIRTY(mutateLayerProperties().setFromPaint, paint, RenderNode::GENERIC); +} + +static jboolean android_view_RenderNode_setStaticMatrix(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, jlong matrixPtr) { + SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixPtr); + return SET_AND_DIRTY(setStaticMatrix, matrix, RenderNode::GENERIC); +} + +static jboolean android_view_RenderNode_setAnimationMatrix(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, jlong matrixPtr) { + SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixPtr); + return SET_AND_DIRTY(setAnimationMatrix, matrix, RenderNode::GENERIC); +} + +static jboolean android_view_RenderNode_setClipToBounds(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, + jboolean clipToBounds) { + return SET_AND_DIRTY(setClipToBounds, clipToBounds, RenderNode::GENERIC); +} + +static jboolean android_view_RenderNode_setClipBounds(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, + jint left, jint top, jint right, jint bottom) { + android::uirenderer::Rect clipBounds(left, top, right, bottom); + return SET_AND_DIRTY(setClipBounds, clipBounds, RenderNode::GENERIC); +} + +static jboolean android_view_RenderNode_setClipBoundsEmpty(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) { + return SET_AND_DIRTY(setClipBoundsEmpty,, RenderNode::GENERIC); +} + +static jboolean android_view_RenderNode_setProjectBackwards(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, + jboolean shouldProject) { + return SET_AND_DIRTY(setProjectBackwards, shouldProject, RenderNode::GENERIC); +} + +static jboolean android_view_RenderNode_setProjectionReceiver(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, + jboolean shouldRecieve) { + return SET_AND_DIRTY(setProjectionReceiver, shouldRecieve, RenderNode::GENERIC); +} + +static jboolean android_view_RenderNode_setOutlineRoundRect(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, + jint left, jint top, jint right, jint bottom, jfloat radius, jfloat alpha) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + renderNode->mutateStagingProperties().mutableOutline().setRoundRect(left, top, right, bottom, + radius, alpha); + renderNode->setPropertyFieldsDirty(RenderNode::GENERIC); + return true; +} + +static jboolean android_view_RenderNode_setOutlinePath(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, + jlong outlinePathPtr, jfloat alpha) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + SkPath* outlinePath = reinterpret_cast<SkPath*>(outlinePathPtr); + renderNode->mutateStagingProperties().mutableOutline().setPath(outlinePath, alpha); + renderNode->setPropertyFieldsDirty(RenderNode::GENERIC); + return true; +} + +static jboolean android_view_RenderNode_setOutlineEmpty(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + renderNode->mutateStagingProperties().mutableOutline().setEmpty(); + renderNode->setPropertyFieldsDirty(RenderNode::GENERIC); + return true; +} + +static jboolean android_view_RenderNode_setOutlineNone(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + renderNode->mutateStagingProperties().mutableOutline().setNone(); + renderNode->setPropertyFieldsDirty(RenderNode::GENERIC); + return true; +} + +static jboolean android_view_RenderNode_hasShadow(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + return renderNode->stagingProperties().hasShadow(); +} + +static jboolean android_view_RenderNode_setSpotShadowColor(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, jint shadowColor) { + return SET_AND_DIRTY(setSpotShadowColor, + static_cast<SkColor>(shadowColor), RenderNode::GENERIC); +} + +static jint android_view_RenderNode_getSpotShadowColor(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + return renderNode->stagingProperties().getSpotShadowColor(); +} + +static jboolean android_view_RenderNode_setAmbientShadowColor(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, + jint shadowColor) { + return SET_AND_DIRTY(setAmbientShadowColor, + static_cast<SkColor>(shadowColor), RenderNode::GENERIC); +} + +static jint android_view_RenderNode_getAmbientShadowColor(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + return renderNode->stagingProperties().getAmbientShadowColor(); +} + +static jboolean android_view_RenderNode_setClipToOutline(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, + jboolean clipToOutline) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + renderNode->mutateStagingProperties().mutableOutline().setShouldClip(clipToOutline); + renderNode->setPropertyFieldsDirty(RenderNode::GENERIC); + return true; +} + +static jboolean android_view_RenderNode_setRevealClip(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, jboolean shouldClip, + jfloat x, jfloat y, jfloat radius) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + renderNode->mutateStagingProperties().mutableRevealClip().set( + shouldClip, x, y, radius); + renderNode->setPropertyFieldsDirty(RenderNode::GENERIC); + return true; +} + +static jboolean android_view_RenderNode_setAlpha(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, float alpha) { + return SET_AND_DIRTY(setAlpha, alpha, RenderNode::ALPHA); +} + +static jboolean android_view_RenderNode_setHasOverlappingRendering(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, + bool hasOverlappingRendering) { + return SET_AND_DIRTY(setHasOverlappingRendering, hasOverlappingRendering, + RenderNode::GENERIC); +} + +static void android_view_RenderNode_setUsageHint(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, jint usageHint) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + renderNode->setUsageHint(static_cast<UsageHint>(usageHint)); +} + +static jboolean android_view_RenderNode_setElevation(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, float elevation) { + return SET_AND_DIRTY(setElevation, elevation, RenderNode::Z); +} + +static jboolean android_view_RenderNode_setTranslationX(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, float tx) { + return SET_AND_DIRTY(setTranslationX, tx, RenderNode::TRANSLATION_X | RenderNode::X); +} + +static jboolean android_view_RenderNode_setTranslationY(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, float ty) { + return SET_AND_DIRTY(setTranslationY, ty, RenderNode::TRANSLATION_Y | RenderNode::Y); +} + +static jboolean android_view_RenderNode_setTranslationZ(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, float tz) { + return SET_AND_DIRTY(setTranslationZ, tz, RenderNode::TRANSLATION_Z | RenderNode::Z); +} + +static jboolean android_view_RenderNode_setRotation(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, float rotation) { + return SET_AND_DIRTY(setRotation, rotation, RenderNode::ROTATION); +} + +static jboolean android_view_RenderNode_setRotationX(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, float rx) { + return SET_AND_DIRTY(setRotationX, rx, RenderNode::ROTATION_X); +} + +static jboolean android_view_RenderNode_setRotationY(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, float ry) { + return SET_AND_DIRTY(setRotationY, ry, RenderNode::ROTATION_Y); +} + +static jboolean android_view_RenderNode_setScaleX(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, float sx) { + return SET_AND_DIRTY(setScaleX, sx, RenderNode::SCALE_X); +} + +static jboolean android_view_RenderNode_setScaleY(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, float sy) { + return SET_AND_DIRTY(setScaleY, sy, RenderNode::SCALE_Y); +} + +static jboolean android_view_RenderNode_setPivotX(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, float px) { + return SET_AND_DIRTY(setPivotX, px, RenderNode::GENERIC); +} + +static jboolean android_view_RenderNode_setPivotY(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, float py) { + return SET_AND_DIRTY(setPivotY, py, RenderNode::GENERIC); +} + +static jboolean android_view_RenderNode_resetPivot(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) { + return SET_AND_DIRTY(resetPivot, /* void */, RenderNode::GENERIC); +} + +static jboolean android_view_RenderNode_setCameraDistance(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, float distance) { + return SET_AND_DIRTY(setCameraDistance, distance, RenderNode::GENERIC); +} + +static jboolean android_view_RenderNode_setLeft(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, int left) { + return SET_AND_DIRTY(setLeft, left, RenderNode::X); +} + +static jboolean android_view_RenderNode_setTop(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, int top) { + return SET_AND_DIRTY(setTop, top, RenderNode::Y); +} + +static jboolean android_view_RenderNode_setRight(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, int right) { + return SET_AND_DIRTY(setRight, right, RenderNode::X); +} + +static jboolean android_view_RenderNode_setBottom(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, int bottom) { + return SET_AND_DIRTY(setBottom, bottom, RenderNode::Y); +} + +static jint android_view_RenderNode_getLeft(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) { + return reinterpret_cast<RenderNode*>(renderNodePtr)->stagingProperties().getLeft(); +} + +static jint android_view_RenderNode_getTop(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) { + return reinterpret_cast<RenderNode*>(renderNodePtr)->stagingProperties().getTop(); +} + +static jint android_view_RenderNode_getRight(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) { + return reinterpret_cast<RenderNode*>(renderNodePtr)->stagingProperties().getRight(); +} + +static jint android_view_RenderNode_getBottom(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) { + return reinterpret_cast<RenderNode*>(renderNodePtr)->stagingProperties().getBottom(); +} + +static jboolean android_view_RenderNode_setLeftTopRightBottom(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, + int left, int top, int right, int bottom) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + if (renderNode->mutateStagingProperties().setLeftTopRightBottom(left, top, right, bottom)) { + renderNode->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + return true; + } + return false; +} + +static jboolean android_view_RenderNode_offsetLeftAndRight(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, jint offset) { + return SET_AND_DIRTY(offsetLeftRight, offset, RenderNode::X); +} + +static jboolean android_view_RenderNode_offsetTopAndBottom(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, jint offset) { + return SET_AND_DIRTY(offsetTopBottom, offset, RenderNode::Y); +} + +// ---------------------------------------------------------------------------- +// RenderProperties - getters +// ---------------------------------------------------------------------------- + +static jboolean android_view_RenderNode_hasOverlappingRendering(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + return renderNode->stagingProperties().hasOverlappingRendering(); +} + +static jboolean android_view_RenderNode_getAnimationMatrix(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, jlong outMatrixPtr) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + SkMatrix* outMatrix = reinterpret_cast<SkMatrix*>(outMatrixPtr); + + const SkMatrix* animationMatrix = renderNode->stagingProperties().getAnimationMatrix(); + + if (animationMatrix) { + *outMatrix = *animationMatrix; + return JNI_TRUE; + } + return JNI_FALSE; +} + +static jboolean android_view_RenderNode_getClipToBounds(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + return renderNode->stagingProperties().getClipToBounds(); +} + +static jboolean android_view_RenderNode_getClipToOutline(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + return renderNode->stagingProperties().getOutline().getShouldClip(); +} + +static jfloat android_view_RenderNode_getAlpha(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + return renderNode->stagingProperties().getAlpha(); +} + +static jfloat android_view_RenderNode_getCameraDistance(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + return renderNode->stagingProperties().getCameraDistance(); +} + +static jfloat android_view_RenderNode_getScaleX(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + return renderNode->stagingProperties().getScaleX(); +} + +static jfloat android_view_RenderNode_getScaleY(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + return renderNode->stagingProperties().getScaleY(); +} + +static jfloat android_view_RenderNode_getElevation(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + return renderNode->stagingProperties().getElevation(); +} + +static jfloat android_view_RenderNode_getTranslationX(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + return renderNode->stagingProperties().getTranslationX(); +} + +static jfloat android_view_RenderNode_getTranslationY(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + return renderNode->stagingProperties().getTranslationY(); +} + +static jfloat android_view_RenderNode_getTranslationZ(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + return renderNode->stagingProperties().getTranslationZ(); +} + +static jfloat android_view_RenderNode_getRotation(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + return renderNode->stagingProperties().getRotation(); +} + +static jfloat android_view_RenderNode_getRotationX(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + return renderNode->stagingProperties().getRotationX(); +} + +static jfloat android_view_RenderNode_getRotationY(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + return renderNode->stagingProperties().getRotationY(); +} + +static jboolean android_view_RenderNode_isPivotExplicitlySet(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + return renderNode->stagingProperties().isPivotExplicitlySet(); +} + +static jboolean android_view_RenderNode_hasIdentityMatrix(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + renderNode->mutateStagingProperties().updateMatrix(); + return !renderNode->stagingProperties().hasTransformMatrix(); +} + +static jint android_view_RenderNode_getLayerType(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + return static_cast<int>(renderNode->stagingProperties().layerProperties().type()); +} + +// ---------------------------------------------------------------------------- +// RenderProperties - computed getters +// ---------------------------------------------------------------------------- + +static void getTransformMatrix(jlong renderNodePtr, jlong outMatrixPtr) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + SkMatrix* outMatrix = reinterpret_cast<SkMatrix*>(outMatrixPtr); + + renderNode->mutateStagingProperties().updateMatrix(); + const SkMatrix* transformMatrix = renderNode->stagingProperties().getTransformMatrix(); + + if (transformMatrix) { + *outMatrix = *transformMatrix; + } else { + outMatrix->setIdentity(); + } +} + +static void android_view_RenderNode_getTransformMatrix(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, jlong outMatrixPtr) { + getTransformMatrix(renderNodePtr, outMatrixPtr); +} + +static void android_view_RenderNode_getInverseTransformMatrix(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, + jlong outMatrixPtr) { + // load transform matrix + getTransformMatrix(renderNodePtr, outMatrixPtr); + SkMatrix* outMatrix = reinterpret_cast<SkMatrix*>(outMatrixPtr); + + // return it inverted + if (!outMatrix->invert(outMatrix)) { + // failed to load inverse, pass back identity + outMatrix->setIdentity(); + } +} + +static jfloat android_view_RenderNode_getPivotX(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + renderNode->mutateStagingProperties().updateMatrix(); + return renderNode->stagingProperties().getPivotX(); +} + +static jfloat android_view_RenderNode_getPivotY(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + renderNode->mutateStagingProperties().updateMatrix(); + return renderNode->stagingProperties().getPivotY(); +} + +static jint android_view_RenderNode_getWidth(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) { + return reinterpret_cast<RenderNode*>(renderNodePtr)->stagingProperties().getWidth(); +} + +static jint android_view_RenderNode_getHeight(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) { + return reinterpret_cast<RenderNode*>(renderNodePtr)->stagingProperties().getHeight(); +} + +static jboolean android_view_RenderNode_setAllowForceDark(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, jboolean allow) { + return SET_AND_DIRTY(setAllowForceDark, allow, RenderNode::GENERIC); +} + +static jboolean android_view_RenderNode_getAllowForceDark(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) { + return reinterpret_cast<RenderNode*>(renderNodePtr)->stagingProperties().getAllowForceDark(); +} + +static jlong android_view_RenderNode_getUniqueId(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) { + return reinterpret_cast<RenderNode*>(renderNodePtr)->uniqueId(); +} + +// ---------------------------------------------------------------------------- +// RenderProperties - Animations +// ---------------------------------------------------------------------------- + +static void android_view_RenderNode_addAnimator(JNIEnv* env, jobject clazz, jlong renderNodePtr, + jlong animatorPtr) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + RenderPropertyAnimator* animator = reinterpret_cast<RenderPropertyAnimator*>(animatorPtr); + renderNode->addAnimator(animator); +} + +static void android_view_RenderNode_endAllAnimators(JNIEnv* env, jobject clazz, + jlong renderNodePtr) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + renderNode->animators().endAllStagingAnimators(); +} + +// ---------------------------------------------------------------------------- +// SurfaceView position callback +// ---------------------------------------------------------------------------- + +jmethodID gPositionListener_PositionChangedMethod; +jmethodID gPositionListener_PositionLostMethod; + +static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject, + jlong renderNodePtr, jobject listener) { + class PositionListenerTrampoline : public RenderNode::PositionListener { + public: + PositionListenerTrampoline(JNIEnv* env, jobject listener) { + env->GetJavaVM(&mVm); + mWeakRef = env->NewWeakGlobalRef(listener); + } + + virtual ~PositionListenerTrampoline() { + jnienv()->DeleteWeakGlobalRef(mWeakRef); + mWeakRef = nullptr; + } + + virtual void onPositionUpdated(RenderNode& node, const TreeInfo& info) override { + if (CC_UNLIKELY(!mWeakRef || !info.updateWindowPositions)) return; + + Matrix4 transform; + info.damageAccumulator->computeCurrentTransform(&transform); + const RenderProperties& props = node.properties(); + uirenderer::Rect bounds(props.getWidth(), props.getHeight()); + transform.mapRect(bounds); + + if (CC_LIKELY(transform.isPureTranslate())) { + // snap/round the computed bounds, so they match the rounding behavior + // of the clear done in SurfaceView#draw(). + bounds.snapGeometryToPixelBoundaries(false); + } else { + // Conservatively round out so the punched hole (in the ZOrderOnTop = true case) + // doesn't extend beyond the other window + bounds.roundOut(); + } + + if (mPreviousPosition == bounds) { + return; + } + mPreviousPosition = bounds; + +#ifdef __ANDROID__ // Layoutlib does not support CanvasContext + incStrong(0); + auto functor = std::bind( + std::mem_fn(&PositionListenerTrampoline::doUpdatePositionAsync), this, + (jlong) info.canvasContext.getFrameNumber(), + (jint) bounds.left, (jint) bounds.top, + (jint) bounds.right, (jint) bounds.bottom); + + info.canvasContext.enqueueFrameWork(std::move(functor)); +#endif + } + + virtual void onPositionLost(RenderNode& node, const TreeInfo* info) override { + if (CC_UNLIKELY(!mWeakRef || (info && !info->updateWindowPositions))) return; + + if (mPreviousPosition.isEmpty()) { + return; + } + mPreviousPosition.setEmpty(); + + ATRACE_NAME("SurfaceView position lost"); + JNIEnv* env = jnienv(); + jobject localref = env->NewLocalRef(mWeakRef); + if (CC_UNLIKELY(!localref)) { + jnienv()->DeleteWeakGlobalRef(mWeakRef); + mWeakRef = nullptr; + return; + } +#ifdef __ANDROID__ // Layoutlib does not support CanvasContext + // TODO: Remember why this is synchronous and then make a comment + env->CallVoidMethod(localref, gPositionListener_PositionLostMethod, + info ? info->canvasContext.getFrameNumber() : 0); +#endif + env->DeleteLocalRef(localref); + } + + private: + JNIEnv* jnienv() { + JNIEnv* env; + if (mVm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { + LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", mVm); + } + return env; + } + + void doUpdatePositionAsync(jlong frameNumber, jint left, jint top, + jint right, jint bottom) { + ATRACE_NAME("Update SurfaceView position"); + + JNIEnv* env = jnienv(); + jobject localref = env->NewLocalRef(mWeakRef); + if (CC_UNLIKELY(!localref)) { + env->DeleteWeakGlobalRef(mWeakRef); + mWeakRef = nullptr; + } else { + env->CallVoidMethod(localref, gPositionListener_PositionChangedMethod, + frameNumber, left, top, right, bottom); + env->DeleteLocalRef(localref); + } + + // We need to release ourselves here + decStrong(0); + } + + JavaVM* mVm; + jobject mWeakRef; + uirenderer::Rect mPreviousPosition; + }; + + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + renderNode->setPositionListener(new PositionListenerTrampoline(env, listener)); +} + +// ---------------------------------------------------------------------------- +// JNI Glue +// ---------------------------------------------------------------------------- + +const char* const kClassPathName = "android/graphics/RenderNode"; + +static const JNINativeMethod gMethods[] = { +// ---------------------------------------------------------------------------- +// Regular JNI +// ---------------------------------------------------------------------------- + { "nCreate", "(Ljava/lang/String;)J", (void*) android_view_RenderNode_create }, + { "nGetNativeFinalizer", "()J", (void*) android_view_RenderNode_getNativeFinalizer }, + { "nOutput", "(J)V", (void*) android_view_RenderNode_output }, + { "nGetUsageSize", "(J)I", (void*) android_view_RenderNode_getUsageSize }, + { "nGetAllocatedSize", "(J)I", (void*) android_view_RenderNode_getAllocatedSize }, + { "nAddAnimator", "(JJ)V", (void*) android_view_RenderNode_addAnimator }, + { "nEndAllAnimators", "(J)V", (void*) android_view_RenderNode_endAllAnimators }, + { "nRequestPositionUpdates", "(JLandroid/graphics/RenderNode$PositionUpdateListener;)V", (void*) android_view_RenderNode_requestPositionUpdates }, + { "nSetDisplayList", "(JJ)V", (void*) android_view_RenderNode_setDisplayList }, + + +// ---------------------------------------------------------------------------- +// Fast JNI via @CriticalNative annotation in RenderNode.java +// ---------------------------------------------------------------------------- + { "nSetDisplayList", "(JJ)V", (void*) android_view_RenderNode_setDisplayList }, + + +// ---------------------------------------------------------------------------- +// Critical JNI via @CriticalNative annotation in RenderNode.java +// ---------------------------------------------------------------------------- + { "nIsValid", "(J)Z", (void*) android_view_RenderNode_isValid }, + { "nSetLayerType", "(JI)Z", (void*) android_view_RenderNode_setLayerType }, + { "nGetLayerType", "(J)I", (void*) android_view_RenderNode_getLayerType }, + { "nSetLayerPaint", "(JJ)Z", (void*) android_view_RenderNode_setLayerPaint }, + { "nSetStaticMatrix", "(JJ)Z", (void*) android_view_RenderNode_setStaticMatrix }, + { "nSetAnimationMatrix", "(JJ)Z", (void*) android_view_RenderNode_setAnimationMatrix }, + { "nGetAnimationMatrix", "(JJ)Z", (void*) android_view_RenderNode_getAnimationMatrix }, + { "nSetClipToBounds", "(JZ)Z", (void*) android_view_RenderNode_setClipToBounds }, + { "nGetClipToBounds", "(J)Z", (void*) android_view_RenderNode_getClipToBounds }, + { "nSetClipBounds", "(JIIII)Z", (void*) android_view_RenderNode_setClipBounds }, + { "nSetClipBoundsEmpty", "(J)Z", (void*) android_view_RenderNode_setClipBoundsEmpty }, + { "nSetProjectBackwards", "(JZ)Z", (void*) android_view_RenderNode_setProjectBackwards }, + { "nSetProjectionReceiver","(JZ)Z", (void*) android_view_RenderNode_setProjectionReceiver }, + + { "nSetOutlineRoundRect", "(JIIIIFF)Z", (void*) android_view_RenderNode_setOutlineRoundRect }, + { "nSetOutlinePath", "(JJF)Z", (void*) android_view_RenderNode_setOutlinePath }, + { "nSetOutlineEmpty", "(J)Z", (void*) android_view_RenderNode_setOutlineEmpty }, + { "nSetOutlineNone", "(J)Z", (void*) android_view_RenderNode_setOutlineNone }, + { "nHasShadow", "(J)Z", (void*) android_view_RenderNode_hasShadow }, + { "nSetSpotShadowColor", "(JI)Z", (void*) android_view_RenderNode_setSpotShadowColor }, + { "nGetSpotShadowColor", "(J)I", (void*) android_view_RenderNode_getSpotShadowColor }, + { "nSetAmbientShadowColor","(JI)Z", (void*) android_view_RenderNode_setAmbientShadowColor }, + { "nGetAmbientShadowColor","(J)I", (void*) android_view_RenderNode_getAmbientShadowColor }, + { "nSetClipToOutline", "(JZ)Z", (void*) android_view_RenderNode_setClipToOutline }, + { "nSetRevealClip", "(JZFFF)Z", (void*) android_view_RenderNode_setRevealClip }, + + { "nSetAlpha", "(JF)Z", (void*) android_view_RenderNode_setAlpha }, + { "nSetHasOverlappingRendering", "(JZ)Z", + (void*) android_view_RenderNode_setHasOverlappingRendering }, + { "nSetUsageHint", "(JI)V", (void*) android_view_RenderNode_setUsageHint }, + { "nSetElevation", "(JF)Z", (void*) android_view_RenderNode_setElevation }, + { "nSetTranslationX", "(JF)Z", (void*) android_view_RenderNode_setTranslationX }, + { "nSetTranslationY", "(JF)Z", (void*) android_view_RenderNode_setTranslationY }, + { "nSetTranslationZ", "(JF)Z", (void*) android_view_RenderNode_setTranslationZ }, + { "nSetRotation", "(JF)Z", (void*) android_view_RenderNode_setRotation }, + { "nSetRotationX", "(JF)Z", (void*) android_view_RenderNode_setRotationX }, + { "nSetRotationY", "(JF)Z", (void*) android_view_RenderNode_setRotationY }, + { "nSetScaleX", "(JF)Z", (void*) android_view_RenderNode_setScaleX }, + { "nSetScaleY", "(JF)Z", (void*) android_view_RenderNode_setScaleY }, + { "nSetPivotX", "(JF)Z", (void*) android_view_RenderNode_setPivotX }, + { "nSetPivotY", "(JF)Z", (void*) android_view_RenderNode_setPivotY }, + { "nResetPivot", "(J)Z", (void*) android_view_RenderNode_resetPivot }, + { "nSetCameraDistance", "(JF)Z", (void*) android_view_RenderNode_setCameraDistance }, + { "nSetLeft", "(JI)Z", (void*) android_view_RenderNode_setLeft }, + { "nSetTop", "(JI)Z", (void*) android_view_RenderNode_setTop }, + { "nSetRight", "(JI)Z", (void*) android_view_RenderNode_setRight }, + { "nSetBottom", "(JI)Z", (void*) android_view_RenderNode_setBottom }, + { "nGetLeft", "(J)I", (void*) android_view_RenderNode_getLeft }, + { "nGetTop", "(J)I", (void*) android_view_RenderNode_getTop }, + { "nGetRight", "(J)I", (void*) android_view_RenderNode_getRight }, + { "nGetBottom", "(J)I", (void*) android_view_RenderNode_getBottom }, + { "nSetLeftTopRightBottom","(JIIII)Z", (void*) android_view_RenderNode_setLeftTopRightBottom }, + { "nOffsetLeftAndRight", "(JI)Z", (void*) android_view_RenderNode_offsetLeftAndRight }, + { "nOffsetTopAndBottom", "(JI)Z", (void*) android_view_RenderNode_offsetTopAndBottom }, + + { "nHasOverlappingRendering", "(J)Z", (void*) android_view_RenderNode_hasOverlappingRendering }, + { "nGetClipToOutline", "(J)Z", (void*) android_view_RenderNode_getClipToOutline }, + { "nGetAlpha", "(J)F", (void*) android_view_RenderNode_getAlpha }, + { "nGetCameraDistance", "(J)F", (void*) android_view_RenderNode_getCameraDistance }, + { "nGetScaleX", "(J)F", (void*) android_view_RenderNode_getScaleX }, + { "nGetScaleY", "(J)F", (void*) android_view_RenderNode_getScaleY }, + { "nGetElevation", "(J)F", (void*) android_view_RenderNode_getElevation }, + { "nGetTranslationX", "(J)F", (void*) android_view_RenderNode_getTranslationX }, + { "nGetTranslationY", "(J)F", (void*) android_view_RenderNode_getTranslationY }, + { "nGetTranslationZ", "(J)F", (void*) android_view_RenderNode_getTranslationZ }, + { "nGetRotation", "(J)F", (void*) android_view_RenderNode_getRotation }, + { "nGetRotationX", "(J)F", (void*) android_view_RenderNode_getRotationX }, + { "nGetRotationY", "(J)F", (void*) android_view_RenderNode_getRotationY }, + { "nIsPivotExplicitlySet", "(J)Z", (void*) android_view_RenderNode_isPivotExplicitlySet }, + { "nHasIdentityMatrix", "(J)Z", (void*) android_view_RenderNode_hasIdentityMatrix }, + + { "nGetTransformMatrix", "(JJ)V", (void*) android_view_RenderNode_getTransformMatrix }, + { "nGetInverseTransformMatrix","(JJ)V", (void*) android_view_RenderNode_getInverseTransformMatrix }, + + { "nGetPivotX", "(J)F", (void*) android_view_RenderNode_getPivotX }, + { "nGetPivotY", "(J)F", (void*) android_view_RenderNode_getPivotY }, + { "nGetWidth", "(J)I", (void*) android_view_RenderNode_getWidth }, + { "nGetHeight", "(J)I", (void*) android_view_RenderNode_getHeight }, + { "nSetAllowForceDark", "(JZ)Z", (void*) android_view_RenderNode_setAllowForceDark }, + { "nGetAllowForceDark", "(J)Z", (void*) android_view_RenderNode_getAllowForceDark }, + { "nGetUniqueId", "(J)J", (void*) android_view_RenderNode_getUniqueId }, +}; + +int register_android_view_RenderNode(JNIEnv* env) { + jclass clazz = FindClassOrDie(env, "android/graphics/RenderNode$PositionUpdateListener"); + gPositionListener_PositionChangedMethod = GetMethodIDOrDie(env, clazz, + "positionChanged", "(JIIII)V"); + gPositionListener_PositionLostMethod = GetMethodIDOrDie(env, clazz, + "positionLost", "(J)V"); + return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods)); +} + +}; + diff --git a/libs/hwui/jni/android_graphics_TextureLayer.cpp b/libs/hwui/jni/android_graphics_TextureLayer.cpp new file mode 100644 index 000000000000..bd20269d3751 --- /dev/null +++ b/libs/hwui/jni/android_graphics_TextureLayer.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <android/surface_texture_jni.h> +#include "graphics_jni_helpers.h" + +#include <hwui/Paint.h> +#include <SkMatrix.h> +#include <DeferredLayerUpdater.h> + +namespace android { + +using namespace uirenderer; + +static jboolean TextureLayer_prepare(JNIEnv* env, jobject clazz, + jlong layerUpdaterPtr, jint width, jint height, jboolean isOpaque) { + DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerUpdaterPtr); + bool changed = false; + changed |= layer->setSize(width, height); + changed |= layer->setBlend(!isOpaque); + return changed; +} + +static void TextureLayer_setLayerPaint(JNIEnv* env, jobject clazz, + jlong layerUpdaterPtr, jlong paintPtr) { + DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerUpdaterPtr); + if (layer) { + Paint* paint = reinterpret_cast<Paint*>(paintPtr); + layer->setPaint(paint); + } +} + +static void TextureLayer_setTransform(JNIEnv* env, jobject clazz, + jlong layerUpdaterPtr, jlong matrixPtr) { + DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerUpdaterPtr); + SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixPtr); + layer->setTransform(matrix); +} + +static void TextureLayer_setSurfaceTexture(JNIEnv* env, jobject clazz, + jlong layerUpdaterPtr, jobject surface) { + DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerUpdaterPtr); + ASurfaceTexture* surfaceTexture = ASurfaceTexture_fromSurfaceTexture(env, surface); + layer->setSurfaceTexture(AutoTextureRelease(surfaceTexture, &ASurfaceTexture_release)); +} + +static void TextureLayer_updateSurfaceTexture(JNIEnv* env, jobject clazz, + jlong layerUpdaterPtr) { + DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerUpdaterPtr); + layer->updateTexImage(); +} + +// ---------------------------------------------------------------------------- +// JNI Glue +// ---------------------------------------------------------------------------- + +const char* const kClassPathName = "android/view/TextureLayer"; + +static const JNINativeMethod gMethods[] = { + { "nPrepare", "(JIIZ)Z", (void*) TextureLayer_prepare }, + { "nSetLayerPaint", "(JJ)V", (void*) TextureLayer_setLayerPaint }, + { "nSetTransform", "(JJ)V", (void*) TextureLayer_setTransform }, + { "nSetSurfaceTexture", "(JLandroid/graphics/SurfaceTexture;)V", + (void*) TextureLayer_setSurfaceTexture }, + { "nUpdateSurfaceTexture", "(J)V", (void*) TextureLayer_updateSurfaceTexture }, +}; + +int register_android_view_TextureLayer(JNIEnv* env) { + return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods)); +} + +}; diff --git a/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp b/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp new file mode 100644 index 000000000000..764eff9a04be --- /dev/null +++ b/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "OpenGLRenderer" + +#include <Interpolator.h> +#include <cutils/log.h> + +#include "graphics_jni_helpers.h" + +namespace android { + +using namespace uirenderer; + +static jlong createAccelerateDecelerateInterpolator(JNIEnv* env, jobject clazz) { + return reinterpret_cast<jlong>(new AccelerateDecelerateInterpolator()); +} + +static jlong createAccelerateInterpolator(JNIEnv* env, jobject clazz, jfloat factor) { + return reinterpret_cast<jlong>(new AccelerateInterpolator(factor)); +} + +static jlong createAnticipateInterpolator(JNIEnv* env, jobject clazz, jfloat tension) { + return reinterpret_cast<jlong>(new AnticipateInterpolator(tension)); +} + +static jlong createAnticipateOvershootInterpolator(JNIEnv* env, jobject clazz, jfloat tension) { + return reinterpret_cast<jlong>(new AnticipateOvershootInterpolator(tension)); +} + +static jlong createBounceInterpolator(JNIEnv* env, jobject clazz) { + return reinterpret_cast<jlong>(new BounceInterpolator()); +} + +static jlong createCycleInterpolator(JNIEnv* env, jobject clazz, jfloat cycles) { + return reinterpret_cast<jlong>(new CycleInterpolator(cycles)); +} + +static jlong createDecelerateInterpolator(JNIEnv* env, jobject clazz, jfloat factor) { + return reinterpret_cast<jlong>(new DecelerateInterpolator(factor)); +} + +static jlong createLinearInterpolator(JNIEnv* env, jobject clazz) { + return reinterpret_cast<jlong>(new LinearInterpolator()); +} + +static jlong createOvershootInterpolator(JNIEnv* env, jobject clazz, jfloat tension) { + return reinterpret_cast<jlong>(new OvershootInterpolator(tension)); +} + +static jlong createPathInterpolator(JNIEnv* env, jobject clazz, jfloatArray jX, jfloatArray jY) { + jsize lenX = env->GetArrayLength(jX); + jsize lenY = env->GetArrayLength(jY); + LOG_ALWAYS_FATAL_IF(lenX != lenY || lenX <= 0, "Invalid path interpolator, x size: %d," + " y size: %d", lenX, lenY); + std::vector<float> x(lenX); + std::vector<float> y(lenY); + env->GetFloatArrayRegion(jX, 0, lenX, x.data()); + env->GetFloatArrayRegion(jY, 0, lenX, y.data()); + + return reinterpret_cast<jlong>(new PathInterpolator(std::move(x), std::move(y))); +} + +static jlong createLutInterpolator(JNIEnv* env, jobject clazz, jfloatArray jlut) { + jsize len = env->GetArrayLength(jlut); + if (len <= 0) { + return 0; + } + float* lut = new float[len]; + env->GetFloatArrayRegion(jlut, 0, len, lut); + return reinterpret_cast<jlong>(new LUTInterpolator(lut, len)); +} + +// ---------------------------------------------------------------------------- +// JNI Glue +// ---------------------------------------------------------------------------- + +const char* const kClassPathName = "android/graphics/animation/NativeInterpolatorFactory"; + +static const JNINativeMethod gMethods[] = { + { "createAccelerateDecelerateInterpolator", "()J", (void*) createAccelerateDecelerateInterpolator }, + { "createAccelerateInterpolator", "(F)J", (void*) createAccelerateInterpolator }, + { "createAnticipateInterpolator", "(F)J", (void*) createAnticipateInterpolator }, + { "createAnticipateOvershootInterpolator", "(F)J", (void*) createAnticipateOvershootInterpolator }, + { "createBounceInterpolator", "()J", (void*) createBounceInterpolator }, + { "createCycleInterpolator", "(F)J", (void*) createCycleInterpolator }, + { "createDecelerateInterpolator", "(F)J", (void*) createDecelerateInterpolator }, + { "createLinearInterpolator", "()J", (void*) createLinearInterpolator }, + { "createOvershootInterpolator", "(F)J", (void*) createOvershootInterpolator }, + { "createPathInterpolator", "([F[F)J", (void*) createPathInterpolator }, + { "createLutInterpolator", "([F)J", (void*) createLutInterpolator }, +}; + +int register_android_graphics_animation_NativeInterpolatorFactory(JNIEnv* env) { + return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods)); +} + + +} // namespace android diff --git a/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp b/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp new file mode 100644 index 000000000000..c6d26f853c1d --- /dev/null +++ b/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "OpenGLRenderer" + +#include <Animator.h> +#include <Interpolator.h> +#include <RenderProperties.h> + +#include "graphics_jni_helpers.h" + +namespace android { + +using namespace uirenderer; + +static struct { + jclass clazz; + + jmethodID callOnFinished; +} gRenderNodeAnimatorClassInfo; + +static JNIEnv* getEnv(JavaVM* vm) { + JNIEnv* env; + if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { + return 0; + } + return env; +} + +class AnimationListenerLifecycleChecker : public AnimationListener { +public: + virtual void onAnimationFinished(BaseRenderNodeAnimator* animator) { + LOG_ALWAYS_FATAL("Lifecycle failure, nStart(%p) wasn't called", animator); + } +}; + +static AnimationListenerLifecycleChecker sLifecycleChecker; + +class AnimationListenerBridge : public AnimationListener { +public: + // This holds a strong reference to a Java WeakReference<T> object. This avoids + // cyclic-references-of-doom. If you think "I know, just use NewWeakGlobalRef!" + // then you end up with basically a PhantomReference, which is totally not + // what we want. + AnimationListenerBridge(JNIEnv* env, jobject finishListener) { + mFinishListener = env->NewGlobalRef(finishListener); + env->GetJavaVM(&mJvm); + } + + virtual ~AnimationListenerBridge() { + if (mFinishListener) { + onAnimationFinished(NULL); + } + } + + virtual void onAnimationFinished(BaseRenderNodeAnimator*) { + LOG_ALWAYS_FATAL_IF(!mFinishListener, "Finished listener twice?"); + JNIEnv* env = getEnv(mJvm); + env->CallStaticVoidMethod( + gRenderNodeAnimatorClassInfo.clazz, + gRenderNodeAnimatorClassInfo.callOnFinished, + mFinishListener); + releaseJavaObject(); + } + +private: + void releaseJavaObject() { + JNIEnv* env = getEnv(mJvm); + env->DeleteGlobalRef(mFinishListener); + mFinishListener = NULL; + } + + JavaVM* mJvm; + jobject mFinishListener; +}; + +static inline RenderPropertyAnimator::RenderProperty toRenderProperty(jint property) { + LOG_ALWAYS_FATAL_IF(property < 0 || property > RenderPropertyAnimator::ALPHA, + "Invalid property %d", property); + return static_cast<RenderPropertyAnimator::RenderProperty>(property); +} + +static inline CanvasPropertyPaintAnimator::PaintField toPaintField(jint field) { + LOG_ALWAYS_FATAL_IF(field < 0 + || field > CanvasPropertyPaintAnimator::ALPHA, + "Invalid paint field %d", field); + return static_cast<CanvasPropertyPaintAnimator::PaintField>(field); +} + +static jlong createAnimator(JNIEnv* env, jobject clazz, + jint propertyRaw, jfloat finalValue) { + RenderPropertyAnimator::RenderProperty property = toRenderProperty(propertyRaw); + BaseRenderNodeAnimator* animator = new RenderPropertyAnimator(property, finalValue); + animator->setListener(&sLifecycleChecker); + return reinterpret_cast<jlong>( animator ); +} + +static jlong createCanvasPropertyFloatAnimator(JNIEnv* env, jobject clazz, + jlong canvasPropertyPtr, jfloat finalValue) { + CanvasPropertyPrimitive* canvasProperty = reinterpret_cast<CanvasPropertyPrimitive*>(canvasPropertyPtr); + BaseRenderNodeAnimator* animator = new CanvasPropertyPrimitiveAnimator(canvasProperty, finalValue); + animator->setListener(&sLifecycleChecker); + return reinterpret_cast<jlong>( animator ); +} + +static jlong createCanvasPropertyPaintAnimator(JNIEnv* env, jobject clazz, + jlong canvasPropertyPtr, jint paintFieldRaw, + jfloat finalValue) { + CanvasPropertyPaint* canvasProperty = reinterpret_cast<CanvasPropertyPaint*>(canvasPropertyPtr); + CanvasPropertyPaintAnimator::PaintField paintField = toPaintField(paintFieldRaw); + BaseRenderNodeAnimator* animator = new CanvasPropertyPaintAnimator( + canvasProperty, paintField, finalValue); + animator->setListener(&sLifecycleChecker); + return reinterpret_cast<jlong>( animator ); +} + +static jlong createRevealAnimator(JNIEnv* env, jobject clazz, + jint centerX, jint centerY, jfloat startRadius, jfloat endRadius) { + BaseRenderNodeAnimator* animator = new RevealAnimator(centerX, centerY, startRadius, endRadius); + animator->setListener(&sLifecycleChecker); + return reinterpret_cast<jlong>( animator ); +} + +static void setStartValue(JNIEnv* env, jobject clazz, jlong animatorPtr, jfloat startValue) { + BaseRenderNodeAnimator* animator = reinterpret_cast<BaseRenderNodeAnimator*>(animatorPtr); + animator->setStartValue(startValue); +} + +static void setDuration(JNIEnv* env, jobject clazz, jlong animatorPtr, jlong duration) { + LOG_ALWAYS_FATAL_IF(duration < 0, "Duration cannot be negative"); + BaseRenderNodeAnimator* animator = reinterpret_cast<BaseRenderNodeAnimator*>(animatorPtr); + animator->setDuration(duration); +} + +static jlong getDuration(JNIEnv* env, jobject clazz, jlong animatorPtr) { + BaseRenderNodeAnimator* animator = reinterpret_cast<BaseRenderNodeAnimator*>(animatorPtr); + return static_cast<jlong>(animator->duration()); +} + +static void setStartDelay(JNIEnv* env, jobject clazz, jlong animatorPtr, jlong startDelay) { + LOG_ALWAYS_FATAL_IF(startDelay < 0, "Start delay cannot be negative"); + BaseRenderNodeAnimator* animator = reinterpret_cast<BaseRenderNodeAnimator*>(animatorPtr); + animator->setStartDelay(startDelay); +} + +static void setInterpolator(JNIEnv* env, jobject clazz, jlong animatorPtr, jlong interpolatorPtr) { + BaseRenderNodeAnimator* animator = reinterpret_cast<BaseRenderNodeAnimator*>(animatorPtr); + Interpolator* interpolator = reinterpret_cast<Interpolator*>(interpolatorPtr); + animator->setInterpolator(interpolator); +} + +static void setAllowRunningAsync(JNIEnv* env, jobject clazz, jlong animatorPtr, jboolean mayRunAsync) { + BaseRenderNodeAnimator* animator = reinterpret_cast<BaseRenderNodeAnimator*>(animatorPtr); + animator->setAllowRunningAsync(mayRunAsync); +} + +static void setListener(JNIEnv* env, jobject clazz, jlong animatorPtr, jobject finishListener) { + BaseRenderNodeAnimator* animator = reinterpret_cast<BaseRenderNodeAnimator*>(animatorPtr); + animator->setListener(new AnimationListenerBridge(env, finishListener)); +} + +static void start(JNIEnv* env, jobject clazz, jlong animatorPtr) { + BaseRenderNodeAnimator* animator = reinterpret_cast<BaseRenderNodeAnimator*>(animatorPtr); + animator->start(); +} + +static void end(JNIEnv* env, jobject clazz, jlong animatorPtr) { + BaseRenderNodeAnimator* animator = reinterpret_cast<BaseRenderNodeAnimator*>(animatorPtr); + animator->cancel(); +} + +// ---------------------------------------------------------------------------- +// JNI Glue +// ---------------------------------------------------------------------------- + +const char* const kClassPathName = "android/graphics/animation/RenderNodeAnimator"; + +static const JNINativeMethod gMethods[] = { + { "nCreateAnimator", "(IF)J", (void*) createAnimator }, + { "nCreateCanvasPropertyFloatAnimator", "(JF)J", (void*) createCanvasPropertyFloatAnimator }, + { "nCreateCanvasPropertyPaintAnimator", "(JIF)J", (void*) createCanvasPropertyPaintAnimator }, + { "nCreateRevealAnimator", "(IIFF)J", (void*) createRevealAnimator }, + { "nSetStartValue", "(JF)V", (void*) setStartValue }, + { "nSetDuration", "(JJ)V", (void*) setDuration }, + { "nGetDuration", "(J)J", (void*) getDuration }, + { "nSetStartDelay", "(JJ)V", (void*) setStartDelay }, + { "nSetInterpolator", "(JJ)V", (void*) setInterpolator }, + { "nSetAllowRunningAsync", "(JZ)V", (void*) setAllowRunningAsync }, + { "nSetListener", "(JLandroid/graphics/animation/RenderNodeAnimator;)V", (void*) setListener}, + { "nStart", "(J)V", (void*) start}, + { "nEnd", "(J)V", (void*) end }, +}; + +int register_android_graphics_animation_RenderNodeAnimator(JNIEnv* env) { + sLifecycleChecker.incStrong(0); + gRenderNodeAnimatorClassInfo.clazz = FindClassOrDie(env, kClassPathName); + gRenderNodeAnimatorClassInfo.clazz = MakeGlobalRefOrDie(env, + gRenderNodeAnimatorClassInfo.clazz); + + gRenderNodeAnimatorClassInfo.callOnFinished = GetStaticMethodIDOrDie( + env, gRenderNodeAnimatorClassInfo.clazz, "callOnFinished", + "(Landroid/graphics/animation/RenderNodeAnimator;)V"); + + return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods)); +} + + +} // namespace android diff --git a/libs/hwui/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp b/libs/hwui/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp new file mode 100644 index 000000000000..b3121e7b0373 --- /dev/null +++ b/libs/hwui/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "android/log.h" + +#include "GraphicsJNI.h" + +#include "Animator.h" +#include "Interpolator.h" +#include "PropertyValuesAnimatorSet.h" +#include "PropertyValuesHolder.h" +#include "VectorDrawable.h" + +namespace android { +using namespace uirenderer; +using namespace VectorDrawable; + +static struct { + jclass clazz; + jmethodID callOnFinished; +} gVectorDrawableAnimatorClassInfo; + +static JNIEnv* getEnv(JavaVM* vm) { + JNIEnv* env; + if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { + return 0; + } + return env; +} + +static AnimationListener* createAnimationListener(JNIEnv* env, jobject finishListener, jint id) { + class AnimationListenerBridge : public AnimationListener { + public: + AnimationListenerBridge(JNIEnv* env, jobject finishListener, jint id) { + mFinishListener = env->NewGlobalRef(finishListener); + env->GetJavaVM(&mJvm); + mId = id; + } + + virtual ~AnimationListenerBridge() { + if (mFinishListener) { + onAnimationFinished(NULL); + } + } + + virtual void onAnimationFinished(BaseRenderNodeAnimator*) { + LOG_ALWAYS_FATAL_IF(!mFinishListener, "Finished listener twice?"); + JNIEnv* env = getEnv(mJvm); + env->CallStaticVoidMethod( + gVectorDrawableAnimatorClassInfo.clazz, + gVectorDrawableAnimatorClassInfo.callOnFinished, + mFinishListener, mId); + releaseJavaObject(); + } + + private: + void releaseJavaObject() { + JNIEnv* env = getEnv(mJvm); + env->DeleteGlobalRef(mFinishListener); + mFinishListener = NULL; + } + + JavaVM* mJvm; + jobject mFinishListener; + jint mId; + }; + return new AnimationListenerBridge(env, finishListener, id); +} + +static void addAnimator(JNIEnv*, jobject, jlong animatorSetPtr, jlong propertyHolderPtr, + jlong interpolatorPtr, jlong startDelay, jlong duration, jint repeatCount, + jint repeatMode) { + PropertyValuesAnimatorSet* set = reinterpret_cast<PropertyValuesAnimatorSet*>(animatorSetPtr); + PropertyValuesHolder* holder = reinterpret_cast<PropertyValuesHolder*>(propertyHolderPtr); + Interpolator* interpolator = reinterpret_cast<Interpolator*>(interpolatorPtr); + RepeatMode mode = static_cast<RepeatMode>(repeatMode); + set->addPropertyAnimator(holder, interpolator, startDelay, duration, repeatCount, mode); +} + +static jlong createAnimatorSet(JNIEnv*, jobject) { + PropertyValuesAnimatorSet* animatorSet = new PropertyValuesAnimatorSet(); + return reinterpret_cast<jlong>(animatorSet); +} + +static void setVectorDrawableTarget(JNIEnv*, jobject,jlong animatorPtr, jlong vectorDrawablePtr) { + VectorDrawable::Tree* tree = reinterpret_cast<VectorDrawable::Tree*>(vectorDrawablePtr); + PropertyValuesAnimatorSet* set = reinterpret_cast<PropertyValuesAnimatorSet*>(animatorPtr); + set->setVectorDrawable(tree); +} + +static jlong createGroupPropertyHolder(JNIEnv*, jobject, jlong nativePtr, jint propertyId, + jfloat startValue, jfloat endValue) { + VectorDrawable::Group* group = reinterpret_cast<VectorDrawable::Group*>(nativePtr); + GroupPropertyValuesHolder* newHolder = new GroupPropertyValuesHolder(group, propertyId, + startValue, endValue); + return reinterpret_cast<jlong>(newHolder); +} + +static jlong createPathDataPropertyHolder(JNIEnv*, jobject, jlong nativePtr, jlong startValuePtr, + jlong endValuePtr) { + VectorDrawable::Path* path = reinterpret_cast<VectorDrawable::Path*>(nativePtr); + PathData* startData = reinterpret_cast<PathData*>(startValuePtr); + PathData* endData = reinterpret_cast<PathData*>(endValuePtr); + PathDataPropertyValuesHolder* newHolder = new PathDataPropertyValuesHolder(path, + startData, endData); + return reinterpret_cast<jlong>(newHolder); +} + +static jlong createPathColorPropertyHolder(JNIEnv*, jobject, jlong nativePtr, jint propertyId, + int startValue, jint endValue) { + VectorDrawable::FullPath* fullPath = reinterpret_cast<VectorDrawable::FullPath*>(nativePtr); + FullPathColorPropertyValuesHolder* newHolder = new FullPathColorPropertyValuesHolder(fullPath, + propertyId, startValue, endValue); + return reinterpret_cast<jlong>(newHolder); +} + +static jlong createPathPropertyHolder(JNIEnv*, jobject, jlong nativePtr, jint propertyId, + float startValue, jfloat endValue) { + VectorDrawable::FullPath* fullPath = reinterpret_cast<VectorDrawable::FullPath*>(nativePtr); + FullPathPropertyValuesHolder* newHolder = new FullPathPropertyValuesHolder(fullPath, + propertyId, startValue, endValue); + return reinterpret_cast<jlong>(newHolder); +} + +static jlong createRootAlphaPropertyHolder(JNIEnv*, jobject, jlong nativePtr, jfloat startValue, + float endValue) { + VectorDrawable::Tree* tree = reinterpret_cast<VectorDrawable::Tree*>(nativePtr); + RootAlphaPropertyValuesHolder* newHolder = new RootAlphaPropertyValuesHolder(tree, + startValue, endValue); + return reinterpret_cast<jlong>(newHolder); +} +static void setFloatPropertyHolderData(JNIEnv* env, jobject, jlong propertyHolderPtr, + jfloatArray srcData, jint length) { + jfloat* propertyData = env->GetFloatArrayElements(srcData, nullptr); + PropertyValuesHolderImpl<float>* holder = + reinterpret_cast<PropertyValuesHolderImpl<float>*>(propertyHolderPtr); + holder->setPropertyDataSource(propertyData, length); + env->ReleaseFloatArrayElements(srcData, propertyData, JNI_ABORT); +} + +static void setIntPropertyHolderData(JNIEnv* env, jobject, jlong propertyHolderPtr, + jintArray srcData, jint length) { + jint* propertyData = env->GetIntArrayElements(srcData, nullptr); + PropertyValuesHolderImpl<int>* holder = + reinterpret_cast<PropertyValuesHolderImpl<int>*>(propertyHolderPtr); + holder->setPropertyDataSource(propertyData, length); + env->ReleaseIntArrayElements(srcData, propertyData, JNI_ABORT); +} + +static void start(JNIEnv* env, jobject, jlong animatorSetPtr, jobject finishListener, jint id) { + PropertyValuesAnimatorSet* set = reinterpret_cast<PropertyValuesAnimatorSet*>(animatorSetPtr); + AnimationListener* listener = createAnimationListener(env, finishListener, id); + set->start(listener); +} + +static void reverse(JNIEnv* env, jobject, jlong animatorSetPtr, jobject finishListener, jint id) { + PropertyValuesAnimatorSet* set = reinterpret_cast<PropertyValuesAnimatorSet*>(animatorSetPtr); + AnimationListener* listener = createAnimationListener(env, finishListener, id); + set->reverse(listener); +} + +static void end(JNIEnv*, jobject, jlong animatorSetPtr) { + PropertyValuesAnimatorSet* set = reinterpret_cast<PropertyValuesAnimatorSet*>(animatorSetPtr); + set->end(); +} + +static void reset(JNIEnv*, jobject, jlong animatorSetPtr) { + PropertyValuesAnimatorSet* set = reinterpret_cast<PropertyValuesAnimatorSet*>(animatorSetPtr); + set->reset(); +} + +static const JNINativeMethod gMethods[] = { + {"nCreateAnimatorSet", "()J", (void*)createAnimatorSet}, + {"nSetVectorDrawableTarget", "(JJ)V", (void*)setVectorDrawableTarget}, + {"nAddAnimator", "(JJJJJII)V", (void*)addAnimator}, + {"nSetPropertyHolderData", "(J[FI)V", (void*)setFloatPropertyHolderData}, + {"nSetPropertyHolderData", "(J[II)V", (void*)setIntPropertyHolderData}, + {"nStart", "(JLandroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimatorRT;I)V", (void*)start}, + {"nReverse", "(JLandroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimatorRT;I)V", (void*)reverse}, + + // ------------- @FastNative ------------------- + + {"nCreateGroupPropertyHolder", "(JIFF)J", (void*)createGroupPropertyHolder}, + {"nCreatePathDataPropertyHolder", "(JJJ)J", (void*)createPathDataPropertyHolder}, + {"nCreatePathColorPropertyHolder", "(JIII)J", (void*)createPathColorPropertyHolder}, + {"nCreatePathPropertyHolder", "(JIFF)J", (void*)createPathPropertyHolder}, + {"nCreateRootAlphaPropertyHolder", "(JFF)J", (void*)createRootAlphaPropertyHolder}, + {"nEnd", "(J)V", (void*)end}, + {"nReset", "(J)V", (void*)reset}, +}; + +const char* const kClassPathName = "android/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimatorRT"; +int register_android_graphics_drawable_AnimatedVectorDrawable(JNIEnv* env) { + gVectorDrawableAnimatorClassInfo.clazz = FindClassOrDie(env, kClassPathName); + gVectorDrawableAnimatorClassInfo.clazz = MakeGlobalRefOrDie(env, + gVectorDrawableAnimatorClassInfo.clazz); + + gVectorDrawableAnimatorClassInfo.callOnFinished = GetStaticMethodIDOrDie( + env, gVectorDrawableAnimatorClassInfo.clazz, "callOnFinished", + "(Landroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimatorRT;I)V"); + return RegisterMethodsOrDie(env, "android/graphics/drawable/AnimatedVectorDrawable", + gMethods, NELEM(gMethods)); +} + +}; // namespace android diff --git a/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp b/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp new file mode 100644 index 000000000000..8a262969614e --- /dev/null +++ b/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp @@ -0,0 +1,423 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "GraphicsJNI.h" + +#include "PathParser.h" +#include "VectorDrawable.h" + +#include <hwui/Paint.h> + +namespace android { +using namespace uirenderer; +using namespace uirenderer::VectorDrawable; + +/** + * VectorDrawable's pre-draw construction. + */ +static jlong createTree(JNIEnv*, jobject, jlong groupPtr) { + VectorDrawable::Group* rootGroup = reinterpret_cast<VectorDrawable::Group*>(groupPtr); + VectorDrawable::Tree* tree = new VectorDrawable::Tree(rootGroup); + return reinterpret_cast<jlong>(tree); +} + +static jlong createTreeFromCopy(JNIEnv*, jobject, jlong treePtr, jlong groupPtr) { + VectorDrawable::Group* rootGroup = reinterpret_cast<VectorDrawable::Group*>(groupPtr); + VectorDrawable::Tree* treeToCopy = reinterpret_cast<VectorDrawable::Tree*>(treePtr); + VectorDrawable::Tree* tree = new VectorDrawable::Tree(treeToCopy, rootGroup); + return reinterpret_cast<jlong>(tree); +} + +static jlong createEmptyFullPath(JNIEnv*, jobject) { + VectorDrawable::FullPath* newPath = new VectorDrawable::FullPath(); + return reinterpret_cast<jlong>(newPath); +} + +static jlong createFullPath(JNIEnv*, jobject, jlong srcFullPathPtr) { + VectorDrawable::FullPath* srcFullPath = + reinterpret_cast<VectorDrawable::FullPath*>(srcFullPathPtr); + VectorDrawable::FullPath* newPath = new VectorDrawable::FullPath(*srcFullPath); + return reinterpret_cast<jlong>(newPath); +} + +static jlong createEmptyClipPath(JNIEnv*, jobject) { + VectorDrawable::ClipPath* newPath = new VectorDrawable::ClipPath(); + return reinterpret_cast<jlong>(newPath); +} + +static jlong createClipPath(JNIEnv*, jobject, jlong srcClipPathPtr) { + VectorDrawable::ClipPath* srcClipPath = + reinterpret_cast<VectorDrawable::ClipPath*>(srcClipPathPtr); + VectorDrawable::ClipPath* newPath = new VectorDrawable::ClipPath(*srcClipPath); + return reinterpret_cast<jlong>(newPath); +} + +static jlong createEmptyGroup(JNIEnv*, jobject) { + VectorDrawable::Group* newGroup = new VectorDrawable::Group(); + return reinterpret_cast<jlong>(newGroup); +} + +static jlong createGroup(JNIEnv*, jobject, jlong srcGroupPtr) { + VectorDrawable::Group* srcGroup = reinterpret_cast<VectorDrawable::Group*>(srcGroupPtr); + VectorDrawable::Group* newGroup = new VectorDrawable::Group(*srcGroup); + return reinterpret_cast<jlong>(newGroup); +} + +static void setNodeName(JNIEnv* env, jobject, jlong nodePtr, jstring nameStr) { + VectorDrawable::Node* node = reinterpret_cast<VectorDrawable::Node*>(nodePtr); + const char* nodeName = env->GetStringUTFChars(nameStr, NULL); + node->setName(nodeName); + env->ReleaseStringUTFChars(nameStr, nodeName); +} + +static void addChild(JNIEnv*, jobject, jlong groupPtr, jlong childPtr) { + VectorDrawable::Group* group = reinterpret_cast<VectorDrawable::Group*>(groupPtr); + VectorDrawable::Node* child = reinterpret_cast<VectorDrawable::Node*>(childPtr); + group->addChild(child); +} + +static void setAllowCaching(JNIEnv*, jobject, jlong treePtr, jboolean allowCaching) { + VectorDrawable::Tree* tree = reinterpret_cast<VectorDrawable::Tree*>(treePtr); + tree->setAllowCaching(allowCaching); +} + +static void setAntiAlias(JNIEnv*, jobject, jlong treePtr, jboolean aa) { + VectorDrawable::Tree* tree = reinterpret_cast<VectorDrawable::Tree*>(treePtr); + tree->setAntiAlias(aa); +} + +/** + * Draw + */ +static int draw(JNIEnv* env, jobject, jlong treePtr, jlong canvasPtr, + jlong colorFilterPtr, jobject jrect, jboolean needsMirroring, jboolean canReuseCache) { + VectorDrawable::Tree* tree = reinterpret_cast<VectorDrawable::Tree*>(treePtr); + Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr); + SkRect rect; + GraphicsJNI::jrect_to_rect(env, jrect, &rect); + SkColorFilter* colorFilter = reinterpret_cast<SkColorFilter*>(colorFilterPtr); + return tree->draw(canvas, colorFilter, rect, needsMirroring, canReuseCache); +} + +/** + * Setters and getters for updating staging properties that can happen both pre-draw and post draw. + */ +static void setTreeViewportSize(JNIEnv*, jobject, jlong treePtr, + jfloat viewportWidth, jfloat viewportHeight) { + VectorDrawable::Tree* tree = reinterpret_cast<VectorDrawable::Tree*>(treePtr); + tree->mutateStagingProperties()->setViewportSize(viewportWidth, viewportHeight); +} + +static jboolean setRootAlpha(JNIEnv*, jobject, jlong treePtr, jfloat alpha) { + VectorDrawable::Tree* tree = reinterpret_cast<VectorDrawable::Tree*>(treePtr); + return tree->mutateStagingProperties()->setRootAlpha(alpha); +} + +static jfloat getRootAlpha(JNIEnv*, jobject, jlong treePtr) { + VectorDrawable::Tree* tree = reinterpret_cast<VectorDrawable::Tree*>(treePtr); + return tree->stagingProperties().getRootAlpha(); +} + +static void updateFullPathPropertiesAndStrokeStyles(JNIEnv*, jobject, jlong fullPathPtr, + jfloat strokeWidth, jint strokeColor, jfloat strokeAlpha, jint fillColor, jfloat fillAlpha, + jfloat trimPathStart, jfloat trimPathEnd, jfloat trimPathOffset, jfloat strokeMiterLimit, + jint strokeLineCap, jint strokeLineJoin, jint fillType) { + VectorDrawable::FullPath* fullPath = reinterpret_cast<VectorDrawable::FullPath*>(fullPathPtr); + fullPath->mutateStagingProperties()->updateProperties(strokeWidth, strokeColor, strokeAlpha, + fillColor, fillAlpha, trimPathStart, trimPathEnd, trimPathOffset, strokeMiterLimit, + strokeLineCap, strokeLineJoin, fillType); +} + +static void updateFullPathFillGradient(JNIEnv*, jobject, jlong pathPtr, jlong fillGradientPtr) { + VectorDrawable::FullPath* path = reinterpret_cast<VectorDrawable::FullPath*>(pathPtr); + SkShader* fillShader = reinterpret_cast<SkShader*>(fillGradientPtr); + path->mutateStagingProperties()->setFillGradient(fillShader); +} + +static void updateFullPathStrokeGradient(JNIEnv*, jobject, jlong pathPtr, jlong strokeGradientPtr) { + VectorDrawable::FullPath* path = reinterpret_cast<VectorDrawable::FullPath*>(pathPtr); + SkShader* strokeShader = reinterpret_cast<SkShader*>(strokeGradientPtr); + path->mutateStagingProperties()->setStrokeGradient(strokeShader); +} + +static jboolean getFullPathProperties(JNIEnv* env, jobject, jlong fullPathPtr, + jbyteArray outProperties, jint length) { + VectorDrawable::FullPath* fullPath = reinterpret_cast<VectorDrawable::FullPath*>(fullPathPtr); + int8_t pathProperties[length]; + bool success = fullPath->stagingProperties()->copyProperties(pathProperties, length); + env->SetByteArrayRegion(outProperties, 0, length, reinterpret_cast<int8_t*>(&pathProperties)); + return success; +} + +static jboolean getGroupProperties(JNIEnv* env, jobject, jlong groupPtr, + jfloatArray outProperties, jint length) { + VectorDrawable::Group* group = reinterpret_cast<VectorDrawable::Group*>(groupPtr); + float groupProperties[length]; + bool success = group->stagingProperties()->copyProperties(groupProperties, length); + env->SetFloatArrayRegion(outProperties, 0, length, reinterpret_cast<float*>(&groupProperties)); + return success; +} + +static void updateGroupProperties(JNIEnv*, jobject, jlong groupPtr, jfloat rotate, jfloat pivotX, + jfloat pivotY, jfloat scaleX, jfloat scaleY, jfloat translateX, jfloat translateY) { + VectorDrawable::Group* group = reinterpret_cast<VectorDrawable::Group*>(groupPtr); + group->mutateStagingProperties()->updateProperties(rotate, pivotX, pivotY, scaleX, scaleY, + translateX, translateY); +} + +static void setPathString(JNIEnv* env, jobject, jlong pathPtr, jstring inputStr, + jint stringLength) { + VectorDrawable::Path* path = reinterpret_cast<VectorDrawable::Path*>(pathPtr); + const char* pathString = env->GetStringUTFChars(inputStr, NULL); + + PathParser::ParseResult result; + PathData data; + PathParser::getPathDataFromAsciiString(&data, &result, pathString, stringLength); + if (result.failureOccurred) { + doThrowIAE(env, result.failureMessage.c_str()); + } + path->mutateStagingProperties()->setData(data); + env->ReleaseStringUTFChars(inputStr, pathString); +} + +/** + * Setters and getters that should only be called from animation thread for animation purpose. + */ +static jfloat getRotation(JNIEnv*, jobject, jlong groupPtr) { + VectorDrawable::Group* group = reinterpret_cast<VectorDrawable::Group*>(groupPtr); + return group->stagingProperties()->getRotation(); +} + +static void setRotation(JNIEnv*, jobject, jlong groupPtr, jfloat rotation) { + VectorDrawable::Group* group = reinterpret_cast<VectorDrawable::Group*>(groupPtr); + group->mutateStagingProperties()->setRotation(rotation); +} + +static jfloat getPivotX(JNIEnv*, jobject, jlong groupPtr) { + VectorDrawable::Group* group = reinterpret_cast<VectorDrawable::Group*>(groupPtr); + return group->stagingProperties()->getPivotX(); +} + +static void setPivotX(JNIEnv*, jobject, jlong groupPtr, jfloat pivotX) { + VectorDrawable::Group* group = reinterpret_cast<VectorDrawable::Group*>(groupPtr); + group->mutateStagingProperties()->setPivotX(pivotX); +} + +static jfloat getPivotY(JNIEnv*, jobject, jlong groupPtr) { + VectorDrawable::Group* group = reinterpret_cast<VectorDrawable::Group*>(groupPtr); + return group->stagingProperties()->getPivotY(); +} + +static void setPivotY(JNIEnv*, jobject, jlong groupPtr, jfloat pivotY) { + VectorDrawable::Group* group = reinterpret_cast<VectorDrawable::Group*>(groupPtr); + group->mutateStagingProperties()->setPivotY(pivotY); +} + +static jfloat getScaleX(JNIEnv*, jobject, jlong groupPtr) { + VectorDrawable::Group* group = reinterpret_cast<VectorDrawable::Group*>(groupPtr); + return group->stagingProperties()->getScaleX(); +} + +static void setScaleX(JNIEnv*, jobject, jlong groupPtr, jfloat scaleX) { + VectorDrawable::Group* group = reinterpret_cast<VectorDrawable::Group*>(groupPtr); + group->mutateStagingProperties()->setScaleX(scaleX); +} + +static jfloat getScaleY(JNIEnv*, jobject, jlong groupPtr) { + VectorDrawable::Group* group = reinterpret_cast<VectorDrawable::Group*>(groupPtr); + return group->stagingProperties()->getScaleY(); +} + +static void setScaleY(JNIEnv*, jobject, jlong groupPtr, jfloat scaleY) { + VectorDrawable::Group* group = reinterpret_cast<VectorDrawable::Group*>(groupPtr); + group->mutateStagingProperties()->setScaleY(scaleY); +} + +static jfloat getTranslateX(JNIEnv*, jobject, jlong groupPtr) { + VectorDrawable::Group* group = reinterpret_cast<VectorDrawable::Group*>(groupPtr); + return group->stagingProperties()->getTranslateX(); +} + +static void setTranslateX(JNIEnv*, jobject, jlong groupPtr, jfloat translateX) { + VectorDrawable::Group* group = reinterpret_cast<VectorDrawable::Group*>(groupPtr); + group->mutateStagingProperties()->setTranslateX(translateX); +} + +static jfloat getTranslateY(JNIEnv*, jobject, jlong groupPtr) { + VectorDrawable::Group* group = reinterpret_cast<VectorDrawable::Group*>(groupPtr); + return group->stagingProperties()->getTranslateY(); +} + +static void setTranslateY(JNIEnv*, jobject, jlong groupPtr, jfloat translateY) { + VectorDrawable::Group* group = reinterpret_cast<VectorDrawable::Group*>(groupPtr); + group->mutateStagingProperties()->setTranslateY(translateY); +} + +static void setPathData(JNIEnv*, jobject, jlong pathPtr, jlong pathDataPtr) { + VectorDrawable::Path* path = reinterpret_cast<VectorDrawable::Path*>(pathPtr); + PathData* pathData = reinterpret_cast<PathData*>(pathDataPtr); + path->mutateStagingProperties()->setData(*pathData); +} + +static jfloat getStrokeWidth(JNIEnv*, jobject, jlong fullPathPtr) { + VectorDrawable::FullPath* fullPath = reinterpret_cast<VectorDrawable::FullPath*>(fullPathPtr); + return fullPath->stagingProperties()->getStrokeWidth(); +} + +static void setStrokeWidth(JNIEnv*, jobject, jlong fullPathPtr, jfloat strokeWidth) { + VectorDrawable::FullPath* fullPath = reinterpret_cast<VectorDrawable::FullPath*>(fullPathPtr); + fullPath->mutateStagingProperties()->setStrokeWidth(strokeWidth); +} + +static jint getStrokeColor(JNIEnv*, jobject, jlong fullPathPtr) { + VectorDrawable::FullPath* fullPath = reinterpret_cast<VectorDrawable::FullPath*>(fullPathPtr); + return fullPath->stagingProperties()->getStrokeColor(); +} + +static void setStrokeColor(JNIEnv*, jobject, jlong fullPathPtr, jint strokeColor) { + VectorDrawable::FullPath* fullPath = reinterpret_cast<VectorDrawable::FullPath*>(fullPathPtr); + fullPath->mutateStagingProperties()->setStrokeColor(strokeColor); +} + +static jfloat getStrokeAlpha(JNIEnv*, jobject, jlong fullPathPtr) { + VectorDrawable::FullPath* fullPath = reinterpret_cast<VectorDrawable::FullPath*>(fullPathPtr); + return fullPath->stagingProperties()->getStrokeAlpha(); +} + +static void setStrokeAlpha(JNIEnv*, jobject, jlong fullPathPtr, jfloat strokeAlpha) { + VectorDrawable::FullPath* fullPath = reinterpret_cast<VectorDrawable::FullPath*>(fullPathPtr); + fullPath->mutateStagingProperties()->setStrokeAlpha(strokeAlpha); +} + +static jint getFillColor(JNIEnv*, jobject, jlong fullPathPtr) { + VectorDrawable::FullPath* fullPath = reinterpret_cast<VectorDrawable::FullPath*>(fullPathPtr); + return fullPath->stagingProperties()->getFillColor(); +} + +static void setFillColor(JNIEnv*, jobject, jlong fullPathPtr, jint fillColor) { + VectorDrawable::FullPath* fullPath = reinterpret_cast<VectorDrawable::FullPath*>(fullPathPtr); + fullPath->mutateStagingProperties()->setFillColor(fillColor); +} + +static jfloat getFillAlpha(JNIEnv*, jobject, jlong fullPathPtr) { + VectorDrawable::FullPath* fullPath = reinterpret_cast<VectorDrawable::FullPath*>(fullPathPtr); + return fullPath->stagingProperties()->getFillAlpha(); +} + +static void setFillAlpha(JNIEnv*, jobject, jlong fullPathPtr, jfloat fillAlpha) { + VectorDrawable::FullPath* fullPath = reinterpret_cast<VectorDrawable::FullPath*>(fullPathPtr); + fullPath->mutateStagingProperties()->setFillAlpha(fillAlpha); +} + +static jfloat getTrimPathStart(JNIEnv*, jobject, jlong fullPathPtr) { + VectorDrawable::FullPath* fullPath = reinterpret_cast<VectorDrawable::FullPath*>(fullPathPtr); + return fullPath->stagingProperties()->getTrimPathStart(); +} + +static void setTrimPathStart(JNIEnv*, jobject, jlong fullPathPtr, jfloat trimPathStart) { + VectorDrawable::FullPath* fullPath = reinterpret_cast<VectorDrawable::FullPath*>(fullPathPtr); + fullPath->mutateStagingProperties()->setTrimPathStart(trimPathStart); +} + +static jfloat getTrimPathEnd(JNIEnv*, jobject, jlong fullPathPtr) { + VectorDrawable::FullPath* fullPath = reinterpret_cast<VectorDrawable::FullPath*>(fullPathPtr); + return fullPath->stagingProperties()->getTrimPathEnd(); +} + +static void setTrimPathEnd(JNIEnv*, jobject, jlong fullPathPtr, jfloat trimPathEnd) { + VectorDrawable::FullPath* fullPath = reinterpret_cast<VectorDrawable::FullPath*>(fullPathPtr); + fullPath->mutateStagingProperties()->setTrimPathEnd(trimPathEnd); +} + +static jfloat getTrimPathOffset(JNIEnv*, jobject, jlong fullPathPtr) { + VectorDrawable::FullPath* fullPath = reinterpret_cast<VectorDrawable::FullPath*>(fullPathPtr); + return fullPath->stagingProperties()->getTrimPathOffset(); +} + +static void setTrimPathOffset(JNIEnv*, jobject, jlong fullPathPtr, jfloat trimPathOffset) { + VectorDrawable::FullPath* fullPath = reinterpret_cast<VectorDrawable::FullPath*>(fullPathPtr); + fullPath->mutateStagingProperties()->setTrimPathOffset(trimPathOffset); +} + +static const JNINativeMethod gMethods[] = { + {"nDraw", "(JJJLandroid/graphics/Rect;ZZ)I", (void*)draw}, + {"nGetFullPathProperties", "(J[BI)Z", (void*)getFullPathProperties}, + {"nGetGroupProperties", "(J[FI)Z", (void*)getGroupProperties}, + {"nSetPathString", "(JLjava/lang/String;I)V", (void*)setPathString}, + {"nSetName", "(JLjava/lang/String;)V", (void*)setNodeName}, + + // ------------- @FastNative ---------------- + + {"nCreateTree", "(J)J", (void*)createTree}, + {"nCreateTreeFromCopy", "(JJ)J", (void*)createTreeFromCopy}, + {"nSetRendererViewportSize", "(JFF)V", (void*)setTreeViewportSize}, + {"nSetRootAlpha", "(JF)Z", (void*)setRootAlpha}, + {"nGetRootAlpha", "(J)F", (void*)getRootAlpha}, + {"nSetAntiAlias", "(JZ)V", (void*)setAntiAlias}, + {"nSetAllowCaching", "(JZ)V", (void*)setAllowCaching}, + + {"nCreateFullPath", "()J", (void*)createEmptyFullPath}, + {"nCreateFullPath", "(J)J", (void*)createFullPath}, + {"nUpdateFullPathProperties", "(JFIFIFFFFFIII)V", (void*)updateFullPathPropertiesAndStrokeStyles}, + {"nUpdateFullPathFillGradient", "(JJ)V", (void*)updateFullPathFillGradient}, + {"nUpdateFullPathStrokeGradient", "(JJ)V", (void*)updateFullPathStrokeGradient}, + + {"nCreateClipPath", "()J", (void*)createEmptyClipPath}, + {"nCreateClipPath", "(J)J", (void*)createClipPath}, + {"nCreateGroup", "()J", (void*)createEmptyGroup}, + {"nCreateGroup", "(J)J", (void*)createGroup}, + {"nUpdateGroupProperties", "(JFFFFFFF)V", (void*)updateGroupProperties}, + + {"nAddChild", "(JJ)V", (void*)addChild}, + {"nGetRotation", "(J)F", (void*)getRotation}, + {"nSetRotation", "(JF)V", (void*)setRotation}, + {"nGetPivotX", "(J)F", (void*)getPivotX}, + {"nSetPivotX", "(JF)V", (void*)setPivotX}, + {"nGetPivotY", "(J)F", (void*)getPivotY}, + {"nSetPivotY", "(JF)V", (void*)setPivotY}, + {"nGetScaleX", "(J)F", (void*)getScaleX}, + {"nSetScaleX", "(JF)V", (void*)setScaleX}, + {"nGetScaleY", "(J)F", (void*)getScaleY}, + {"nSetScaleY", "(JF)V", (void*)setScaleY}, + {"nGetTranslateX", "(J)F", (void*)getTranslateX}, + {"nSetTranslateX", "(JF)V", (void*)setTranslateX}, + {"nGetTranslateY", "(J)F", (void*)getTranslateY}, + {"nSetTranslateY", "(JF)V", (void*)setTranslateY}, + + {"nSetPathData", "(JJ)V", (void*)setPathData}, + {"nGetStrokeWidth", "(J)F", (void*)getStrokeWidth}, + {"nSetStrokeWidth", "(JF)V", (void*)setStrokeWidth}, + {"nGetStrokeColor", "(J)I", (void*)getStrokeColor}, + {"nSetStrokeColor", "(JI)V", (void*)setStrokeColor}, + {"nGetStrokeAlpha", "(J)F", (void*)getStrokeAlpha}, + {"nSetStrokeAlpha", "(JF)V", (void*)setStrokeAlpha}, + {"nGetFillColor", "(J)I", (void*)getFillColor}, + {"nSetFillColor", "(JI)V", (void*)setFillColor}, + {"nGetFillAlpha", "(J)F", (void*)getFillAlpha}, + {"nSetFillAlpha", "(JF)V", (void*)setFillAlpha}, + {"nGetTrimPathStart", "(J)F", (void*)getTrimPathStart}, + {"nSetTrimPathStart", "(JF)V", (void*)setTrimPathStart}, + {"nGetTrimPathEnd", "(J)F", (void*)getTrimPathEnd}, + {"nSetTrimPathEnd", "(JF)V", (void*)setTrimPathEnd}, + {"nGetTrimPathOffset", "(J)F", (void*)getTrimPathOffset}, + {"nSetTrimPathOffset", "(JF)V", (void*)setTrimPathOffset}, +}; + +int register_android_graphics_drawable_VectorDrawable(JNIEnv* env) { + return RegisterMethodsOrDie(env, "android/graphics/drawable/VectorDrawable", gMethods, NELEM(gMethods)); +} + +}; // namespace android diff --git a/libs/hwui/jni/android_nio_utils.cpp b/libs/hwui/jni/android_nio_utils.cpp new file mode 100644 index 000000000000..c2b09c1d15d7 --- /dev/null +++ b/libs/hwui/jni/android_nio_utils.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "android_nio_utils.h" + +#include <nativehelper/JNIHelp.h> + +namespace android { + +AutoBufferPointer::AutoBufferPointer(JNIEnv* env, jobject nioBuffer, jboolean commit) + : fEnv(env), fCommit(commit) { + jlong pointer = jniGetNioBufferPointer(fEnv, nioBuffer); + if (pointer != 0L) { + // Buffer is backed by a direct buffer. + fArray = nullptr; + fElements = nullptr; + fPointer = reinterpret_cast<void*>(pointer); + } else { + // Buffer is backed by a managed array. + jint byteOffset = jniGetNioBufferBaseArrayOffset(fEnv, nioBuffer); + fArray = jniGetNioBufferBaseArray(fEnv, nioBuffer); + fElements = fEnv->GetPrimitiveArrayCritical(fArray, /* isCopy= */ nullptr); + fPointer = reinterpret_cast<void*>(reinterpret_cast<char*>(fElements) + byteOffset); + } +} + +AutoBufferPointer::~AutoBufferPointer() { + if (nullptr != fArray) { + fEnv->ReleasePrimitiveArrayCritical(fArray, fElements, fCommit ? 0 : JNI_ABORT); + } +} + +} // namespace android diff --git a/libs/hwui/jni/android_nio_utils.h b/libs/hwui/jni/android_nio_utils.h new file mode 100644 index 000000000000..4aaa0a78c276 --- /dev/null +++ b/libs/hwui/jni/android_nio_utils.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _ANDROID_NIO_UTILS_H_ +#define _ANDROID_NIO_UTILS_H_ + +#include <nativehelper/JNIHelp.h> + +namespace android { + +/** + * Class providing scoped access to the memory backing a java.nio.Buffer instance. + * + * Instances of this class should only be allocated on the stack as heap allocation is not + * supported. + * + * Instances of this class do not create any global references for performance reasons. + */ +class AutoBufferPointer final { +public: + /** Constructor for an AutoBufferPointer instance. + * + * @param env The current JNI env + * @param nioBuffer Instance of a java.nio.Buffer whose memory will be accessed. + * @param commit JNI_TRUE if the underlying memory will be updated and should be + * copied back to the managed heap. JNI_FALSE if the data will + * not be modified or the modifications may be discarded. + * + * The commit parameter is only applicable if the buffer is backed by a managed heap + * array and the runtime had to provide a copy of the data rather than the original data. + */ + AutoBufferPointer(JNIEnv* env, jobject nioBuffer, jboolean commit); + + /** Destructor for an AutoBufferPointer instance. + * + * Releases critical managed heap array pointer if acquired. + */ + ~AutoBufferPointer(); + + /** + * Returns a pointer to the current position of the buffer provided to the constructor. This + * pointer is only valid whilst the AutoBufferPointer instance remains in scope. + */ + void* pointer() const { return fPointer; } + +private: + JNIEnv* const fEnv; + void* fPointer; // Pointer to current buffer position when constructed. + void* fElements; // Pointer to array element 0 (null if buffer is direct, may be + // within fArray or point to a copy of the array). + jarray fArray; // Pointer to array on managed heap. + const jboolean fCommit; // Flag to commit data to source (when fElements is a copy of fArray). + + // Unsupported constructors and operators. + AutoBufferPointer() = delete; + AutoBufferPointer(AutoBufferPointer&) = delete; + AutoBufferPointer& operator=(AutoBufferPointer&) = delete; + static void* operator new(size_t); + static void* operator new[](size_t); + static void* operator new(size_t, void*); + static void* operator new[](size_t, void*); + static void operator delete(void*, size_t); + static void operator delete[](void*, size_t); +}; + +} /* namespace android */ + +#endif // _ANDROID_NIO_UTILS_H_ diff --git a/libs/hwui/jni/android_util_PathParser.cpp b/libs/hwui/jni/android_util_PathParser.cpp new file mode 100644 index 000000000000..df5e9cd44ed0 --- /dev/null +++ b/libs/hwui/jni/android_util_PathParser.cpp @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "GraphicsJNI.h" + +#include <PathParser.h> +#include <SkPath.h> +#include <utils/VectorDrawableUtils.h> + +#include <android/log.h> + +namespace android { + +using namespace uirenderer; + +static void parseStringForPath(JNIEnv* env, jobject, jlong skPathHandle, jstring inputPathStr, + jint strLength) { + const char* pathString = env->GetStringUTFChars(inputPathStr, NULL); + SkPath* skPath = reinterpret_cast<SkPath*>(skPathHandle); + + PathParser::ParseResult result; + PathParser::parseAsciiStringForSkPath(skPath, &result, pathString, strLength); + env->ReleaseStringUTFChars(inputPathStr, pathString); + if (result.failureOccurred) { + doThrowIAE(env, result.failureMessage.c_str()); + } +} + +static long createEmptyPathData(JNIEnv*, jobject) { + PathData* pathData = new PathData(); + return reinterpret_cast<jlong>(pathData); +} + +static long createPathData(JNIEnv*, jobject, jlong pathDataPtr) { + PathData* pathData = reinterpret_cast<PathData*>(pathDataPtr); + PathData* newPathData = new PathData(*pathData); + return reinterpret_cast<jlong>(newPathData); +} + +static long createPathDataFromStringPath(JNIEnv* env, jobject, jstring inputStr, jint strLength) { + const char* pathString = env->GetStringUTFChars(inputStr, NULL); + PathData* pathData = new PathData(); + PathParser::ParseResult result; + PathParser::getPathDataFromAsciiString(pathData, &result, pathString, strLength); + env->ReleaseStringUTFChars(inputStr, pathString); + if (!result.failureOccurred) { + return reinterpret_cast<jlong>(pathData); + } else { + delete pathData; + doThrowIAE(env, result.failureMessage.c_str()); + return NULL; + } +} + +static bool interpolatePathData(JNIEnv*, jobject, jlong outPathDataPtr, jlong fromPathDataPtr, + jlong toPathDataPtr, jfloat fraction) { + PathData* outPathData = reinterpret_cast<PathData*>(outPathDataPtr); + PathData* fromPathData = reinterpret_cast<PathData*>(fromPathDataPtr); + PathData* toPathData = reinterpret_cast<PathData*>(toPathDataPtr); + return VectorDrawableUtils::interpolatePathData(outPathData, *fromPathData, + *toPathData, fraction); +} + +static void deletePathData(JNIEnv*, jobject, jlong pathDataHandle) { + PathData* pathData = reinterpret_cast<PathData*>(pathDataHandle); + delete pathData; +} + +static bool canMorphPathData(JNIEnv*, jobject, jlong fromPathDataPtr, jlong toPathDataPtr) { + PathData* fromPathData = reinterpret_cast<PathData*>(fromPathDataPtr); + PathData* toPathData = reinterpret_cast<PathData*>(toPathDataPtr); + return VectorDrawableUtils::canMorph(*fromPathData, *toPathData); +} + +static void setPathData(JNIEnv*, jobject, jlong outPathDataPtr, jlong fromPathDataPtr) { + PathData* fromPathData = reinterpret_cast<PathData*>(fromPathDataPtr); + PathData* outPathData = reinterpret_cast<PathData*>(outPathDataPtr); + *outPathData = *fromPathData; +} + +static void setSkPathFromPathData(JNIEnv*, jobject, jlong outPathPtr, jlong pathDataPtr) { + PathData* pathData = reinterpret_cast<PathData*>(pathDataPtr); + SkPath* skPath = reinterpret_cast<SkPath*>(outPathPtr); + VectorDrawableUtils::verbsToPath(skPath, *pathData); +} + +static const JNINativeMethod gMethods[] = { + {"nParseStringForPath", "(JLjava/lang/String;I)V", (void*)parseStringForPath}, + {"nCreatePathDataFromString", "(Ljava/lang/String;I)J", (void*)createPathDataFromStringPath}, + + // ---------------- @FastNative ----------------- + + {"nCreateEmptyPathData", "()J", (void*)createEmptyPathData}, + {"nCreatePathData", "(J)J", (void*)createPathData}, + {"nInterpolatePathData", "(JJJF)Z", (void*)interpolatePathData}, + {"nFinalize", "(J)V", (void*)deletePathData}, + {"nCanMorph", "(JJ)Z", (void*)canMorphPathData}, + {"nSetPathData", "(JJ)V", (void*)setPathData}, + {"nCreatePathFromPathData", "(JJ)V", (void*)setSkPathFromPathData}, +}; + +int register_android_util_PathParser(JNIEnv* env) { + return RegisterMethodsOrDie(env, "android/util/PathParser", gMethods, NELEM(gMethods)); +} +}; diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp new file mode 100644 index 000000000000..5714cd1d0390 --- /dev/null +++ b/libs/hwui/jni/fonts/Font.cpp @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#undef LOG_TAG +#define LOG_TAG "Minikin" + +#include "SkData.h" +#include "SkFontMgr.h" +#include "SkRefCnt.h" +#include "SkTypeface.h" +#include "GraphicsJNI.h" +#include <nativehelper/ScopedUtfChars.h> +#include "Utils.h" +#include "FontUtils.h" + +#include <hwui/MinikinSkia.h> +#include <hwui/Typeface.h> +#include <minikin/FontFamily.h> +#include <ui/FatVector.h> + +#include <memory> + +namespace android { + +struct NativeFontBuilder { + std::vector<minikin::FontVariation> axes; +}; + +static inline NativeFontBuilder* toBuilder(jlong ptr) { + return reinterpret_cast<NativeFontBuilder*>(ptr); +} + +static void releaseFont(jlong font) { + delete reinterpret_cast<FontWrapper*>(font); +} + +static void release_global_ref(const void* /*data*/, void* context) { + JNIEnv* env = GraphicsJNI::getJNIEnv(); + bool needToAttach = (env == nullptr); + if (needToAttach) { + env = GraphicsJNI::attachJNIEnv("release_font_data"); + if (env == nullptr) { + ALOGE("failed to attach to thread to release global ref."); + return; + } + } + + jobject obj = reinterpret_cast<jobject>(context); + env->DeleteGlobalRef(obj); +} + +// Regular JNI +static jlong Font_Builder_initBuilder(JNIEnv*, jobject) { + return reinterpret_cast<jlong>(new NativeFontBuilder()); +} + +// Critical Native +static void Font_Builder_addAxis(CRITICAL_JNI_PARAMS_COMMA jlong builderPtr, jint tag, jfloat value) { + toBuilder(builderPtr)->axes.emplace_back(static_cast<minikin::AxisTag>(tag), value); +} + +// Regular JNI +static jlong Font_Builder_build(JNIEnv* env, jobject clazz, jlong builderPtr, jobject buffer, + jstring filePath, jint weight, jboolean italic, jint ttcIndex) { + NPE_CHECK_RETURN_ZERO(env, buffer); + std::unique_ptr<NativeFontBuilder> builder(toBuilder(builderPtr)); + const void* fontPtr = env->GetDirectBufferAddress(buffer); + if (fontPtr == nullptr) { + jniThrowException(env, "java/lang/IllegalArgumentException", "Not a direct buffer"); + return 0; + } + jlong fontSize = env->GetDirectBufferCapacity(buffer); + if (fontSize <= 0) { + jniThrowException(env, "java/lang/IllegalArgumentException", + "buffer size must not be zero or negative"); + return 0; + } + ScopedUtfChars fontPath(env, filePath); + jobject fontRef = MakeGlobalRefOrDie(env, buffer); + sk_sp<SkData> data(SkData::MakeWithProc(fontPtr, fontSize, + release_global_ref, reinterpret_cast<void*>(fontRef))); + + FatVector<SkFontArguments::Axis, 2> skiaAxes; + for (const auto& axis : builder->axes) { + skiaAxes.emplace_back(SkFontArguments::Axis{axis.axisTag, axis.value}); + } + + std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(std::move(data))); + + SkFontArguments params; + params.setCollectionIndex(ttcIndex); + params.setAxes(skiaAxes.data(), skiaAxes.size()); + + sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault()); + sk_sp<SkTypeface> face(fm->makeFromStream(std::move(fontData), params)); + if (face == nullptr) { + jniThrowException(env, "java/lang/IllegalArgumentException", + "Failed to create internal object. maybe invalid font data."); + return 0; + } + std::shared_ptr<minikin::MinikinFont> minikinFont = + std::make_shared<MinikinFontSkia>(std::move(face), fontPtr, fontSize, + std::string_view(fontPath.c_str(), fontPath.size()), + ttcIndex, builder->axes); + minikin::Font font = minikin::Font::Builder(minikinFont).setWeight(weight) + .setSlant(static_cast<minikin::FontStyle::Slant>(italic)).build(); + return reinterpret_cast<jlong>(new FontWrapper(std::move(font))); +} + +// Critical Native +static jlong Font_Builder_getReleaseNativeFont(CRITICAL_JNI_PARAMS) { + return reinterpret_cast<jlong>(releaseFont); +} + +/////////////////////////////////////////////////////////////////////////////// + +static const JNINativeMethod gFontBuilderMethods[] = { + { "nInitBuilder", "()J", (void*) Font_Builder_initBuilder }, + { "nAddAxis", "(JIF)V", (void*) Font_Builder_addAxis }, + { "nBuild", "(JLjava/nio/ByteBuffer;Ljava/lang/String;IZI)J", (void*) Font_Builder_build }, + { "nGetReleaseNativeFont", "()J", (void*) Font_Builder_getReleaseNativeFont }, +}; + +int register_android_graphics_fonts_Font(JNIEnv* env) { + return RegisterMethodsOrDie(env, "android/graphics/fonts/Font$Builder", gFontBuilderMethods, + NELEM(gFontBuilderMethods)); +} + +} diff --git a/libs/hwui/jni/fonts/FontFamily.cpp b/libs/hwui/jni/fonts/FontFamily.cpp new file mode 100644 index 000000000000..df619d9f1406 --- /dev/null +++ b/libs/hwui/jni/fonts/FontFamily.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#undef LOG_TAG +#define LOG_TAG "Minikin" + +#include "graphics_jni_helpers.h" +#include <nativehelper/ScopedUtfChars.h> + +#include "FontUtils.h" + +#include <minikin/FontFamily.h> +#include <minikin/LocaleList.h> + +#include <memory> + +namespace android { + +struct NativeFamilyBuilder { + std::vector<minikin::Font> fonts; +}; + +static inline NativeFamilyBuilder* toBuilder(jlong ptr) { + return reinterpret_cast<NativeFamilyBuilder*>(ptr); +} + +static inline FontWrapper* toFontWrapper(jlong ptr) { + return reinterpret_cast<FontWrapper*>(ptr); +} + +static void releaseFontFamily(jlong family) { + delete reinterpret_cast<FontFamilyWrapper*>(family); +} + +// Regular JNI +static jlong FontFamily_Builder_initBuilder(JNIEnv*, jobject) { + return reinterpret_cast<jlong>(new NativeFamilyBuilder()); +} + +// Critical Native +static void FontFamily_Builder_addFont(CRITICAL_JNI_PARAMS_COMMA jlong builderPtr, jlong fontPtr) { + toBuilder(builderPtr)->fonts.push_back(toFontWrapper(fontPtr)->font); +} + +// Regular JNI +static jlong FontFamily_Builder_build(JNIEnv* env, jobject clazz, jlong builderPtr, + jstring langTags, jint variant, jboolean isCustomFallback) { + std::unique_ptr<NativeFamilyBuilder> builder(toBuilder(builderPtr)); + uint32_t localeId; + if (langTags == nullptr) { + localeId = minikin::registerLocaleList(""); + } else { + ScopedUtfChars str(env, langTags); + localeId = minikin::registerLocaleList(str.c_str()); + } + std::shared_ptr<minikin::FontFamily> family = std::make_shared<minikin::FontFamily>( + localeId, static_cast<minikin::FamilyVariant>(variant), std::move(builder->fonts), + isCustomFallback); + if (family->getCoverage().length() == 0) { + // No coverage means minikin rejected given font for some reasons. + jniThrowException(env, "java/lang/IllegalArgumentException", + "Failed to create internal object. maybe invalid font data"); + return 0; + } + return reinterpret_cast<jlong>(new FontFamilyWrapper(std::move(family))); +} + +// CriticalNative +static jlong FontFamily_Builder_GetReleaseFunc(CRITICAL_JNI_PARAMS) { + return reinterpret_cast<jlong>(releaseFontFamily); +} + +/////////////////////////////////////////////////////////////////////////////// + +static const JNINativeMethod gFontFamilyBuilderMethods[] = { + { "nInitBuilder", "()J", (void*) FontFamily_Builder_initBuilder }, + { "nAddFont", "(JJ)V", (void*) FontFamily_Builder_addFont }, + { "nBuild", "(JLjava/lang/String;IZ)J", (void*) FontFamily_Builder_build }, + + { "nGetReleaseNativeFamily", "()J", (void*) FontFamily_Builder_GetReleaseFunc }, +}; + +int register_android_graphics_fonts_FontFamily(JNIEnv* env) { + return RegisterMethodsOrDie(env, "android/graphics/fonts/FontFamily$Builder", + gFontFamilyBuilderMethods, NELEM(gFontFamilyBuilderMethods)); +} + +} diff --git a/libs/hwui/jni/graphics_jni_helpers.h b/libs/hwui/jni/graphics_jni_helpers.h new file mode 100644 index 000000000000..b97cc6a10179 --- /dev/null +++ b/libs/hwui/jni/graphics_jni_helpers.h @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHICS_JNI_HELPERS +#define GRAPHICS_JNI_HELPERS + +#include <log/log.h> +#include <nativehelper/JNIHelp.h> +#include <nativehelper/scoped_local_ref.h> +#include <nativehelper/scoped_utf_chars.h> +#include <string> + +// Host targets (layoutlib) do not differentiate between regular and critical native methods, +// and they need all the JNI methods to have JNIEnv* and jclass/jobject as their first two arguments. +// The following macro allows to have those arguments when compiling for host while omitting them when +// compiling for Android. +#ifdef __ANDROID__ +#define CRITICAL_JNI_PARAMS +#define CRITICAL_JNI_PARAMS_COMMA +#else +#define CRITICAL_JNI_PARAMS JNIEnv*, jclass +#define CRITICAL_JNI_PARAMS_COMMA JNIEnv*, jclass, +#endif + +namespace android { + +// Defines some helpful functions. + +static inline jclass FindClassOrDie(JNIEnv* env, const char* class_name) { + jclass clazz = env->FindClass(class_name); + LOG_ALWAYS_FATAL_IF(clazz == NULL, "Unable to find class %s", class_name); + return clazz; +} + +static inline jfieldID GetFieldIDOrDie(JNIEnv* env, jclass clazz, const char* field_name, + const char* field_signature) { + jfieldID res = env->GetFieldID(clazz, field_name, field_signature); + LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find static field %s", field_name); + return res; +} + +static inline jmethodID GetMethodIDOrDie(JNIEnv* env, jclass clazz, const char* method_name, + const char* method_signature) { + jmethodID res = env->GetMethodID(clazz, method_name, method_signature); + LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find method %s", method_name); + return res; +} + +static inline jfieldID GetStaticFieldIDOrDie(JNIEnv* env, jclass clazz, const char* field_name, + const char* field_signature) { + jfieldID res = env->GetStaticFieldID(clazz, field_name, field_signature); + LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find static field %s", field_name); + return res; +} + +static inline jmethodID GetStaticMethodIDOrDie(JNIEnv* env, jclass clazz, const char* method_name, + const char* method_signature) { + jmethodID res = env->GetStaticMethodID(clazz, method_name, method_signature); + LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find static method %s", method_name); + return res; +} + +template <typename T> +static inline T MakeGlobalRefOrDie(JNIEnv* env, T in) { + jobject res = env->NewGlobalRef(in); + LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to create global reference."); + return static_cast<T>(res); +} + +static inline int RegisterMethodsOrDie(JNIEnv* env, const char* className, + const JNINativeMethod* gMethods, int numMethods) { + int res = jniRegisterNativeMethods(env, className, gMethods, numMethods); + LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods."); + return res; +} + +/** + * Read the specified field from jobject, and convert to std::string. + * If the field cannot be obtained, return defaultValue. + */ +static inline std::string getStringField(JNIEnv* env, jobject obj, jfieldID fieldId, + const char* defaultValue) { + ScopedLocalRef<jstring> strObj(env, jstring(env->GetObjectField(obj, fieldId))); + if (strObj != nullptr) { + ScopedUtfChars chars(env, strObj.get()); + return std::string(chars.c_str()); + } + return std::string(defaultValue); +} + +} // namespace android + +#endif // GRAPHICS_JNI_HELPERS diff --git a/libs/hwui/jni/pdf/PdfDocument.cpp b/libs/hwui/jni/pdf/PdfDocument.cpp new file mode 100644 index 000000000000..d21eb3f6a208 --- /dev/null +++ b/libs/hwui/jni/pdf/PdfDocument.cpp @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "GraphicsJNI.h" +#include <vector> + +#include "CreateJavaOutputStreamAdaptor.h" + +#include "SkPDFDocument.h" +#include "SkPicture.h" +#include "SkPictureRecorder.h" +#include "SkRect.h" +#include "SkStream.h" + +#include <hwui/Canvas.h> + +namespace android { + +struct PageRecord { + + PageRecord(int width, int height, const SkRect& contentRect) + : mPictureRecorder(new SkPictureRecorder()) + , mPicture(NULL) + , mWidth(width) + , mHeight(height) { + mContentRect = contentRect; + } + + ~PageRecord() { + delete mPictureRecorder; + if (NULL != mPicture) { + mPicture->unref(); + } + } + + SkPictureRecorder* mPictureRecorder; + SkPicture* mPicture; + const int mWidth; + const int mHeight; + SkRect mContentRect; +}; + +class PdfDocument { +public: + PdfDocument() { + mCurrentPage = NULL; + } + + SkCanvas* startPage(int width, int height, + int contentLeft, int contentTop, int contentRight, int contentBottom) { + assert(mCurrentPage == NULL); + + SkRect contentRect = SkRect::MakeLTRB( + contentLeft, contentTop, contentRight, contentBottom); + PageRecord* page = new PageRecord(width, height, contentRect); + mPages.push_back(page); + mCurrentPage = page; + + SkCanvas* canvas = page->mPictureRecorder->beginRecording( + SkRect::MakeWH(contentRect.width(), contentRect.height())); + + return canvas; + } + + void finishPage() { + assert(mCurrentPage != NULL); + assert(mCurrentPage->mPictureRecorder != NULL); + assert(mCurrentPage->mPicture == NULL); + mCurrentPage->mPicture = mCurrentPage->mPictureRecorder->finishRecordingAsPicture().release(); + delete mCurrentPage->mPictureRecorder; + mCurrentPage->mPictureRecorder = NULL; + mCurrentPage = NULL; + } + + void write(SkWStream* stream) { + sk_sp<SkDocument> document = SkPDF::MakeDocument(stream); + for (unsigned i = 0; i < mPages.size(); i++) { + PageRecord* page = mPages[i]; + + SkCanvas* canvas = document->beginPage(page->mWidth, page->mHeight, + &(page->mContentRect)); + canvas->drawPicture(page->mPicture); + + document->endPage(); + } + document->close(); + } + + void close() { + assert(NULL == mCurrentPage); + for (unsigned i = 0; i < mPages.size(); i++) { + delete mPages[i]; + } + } + +private: + ~PdfDocument() { + close(); + } + + std::vector<PageRecord*> mPages; + PageRecord* mCurrentPage; +}; + +static jlong nativeCreateDocument(JNIEnv* env, jobject thiz) { + return reinterpret_cast<jlong>(new PdfDocument()); +} + +static jlong nativeStartPage(JNIEnv* env, jobject thiz, jlong documentPtr, + jint pageWidth, jint pageHeight, + jint contentLeft, jint contentTop, jint contentRight, jint contentBottom) { + PdfDocument* document = reinterpret_cast<PdfDocument*>(documentPtr); + SkCanvas* canvas = document->startPage(pageWidth, pageHeight, + contentLeft, contentTop, contentRight, contentBottom); + return reinterpret_cast<jlong>(Canvas::create_canvas(canvas)); +} + +static void nativeFinishPage(JNIEnv* env, jobject thiz, jlong documentPtr) { + PdfDocument* document = reinterpret_cast<PdfDocument*>(documentPtr); + document->finishPage(); +} + +static void nativeWriteTo(JNIEnv* env, jobject thiz, jlong documentPtr, jobject out, + jbyteArray chunk) { + PdfDocument* document = reinterpret_cast<PdfDocument*>(documentPtr); + SkWStream* skWStream = CreateJavaOutputStreamAdaptor(env, out, chunk); + document->write(skWStream); + delete skWStream; +} + +static void nativeClose(JNIEnv* env, jobject thiz, jlong documentPtr) { + PdfDocument* document = reinterpret_cast<PdfDocument*>(documentPtr); + document->close(); +} + +static const JNINativeMethod gPdfDocument_Methods[] = { + {"nativeCreateDocument", "()J", (void*) nativeCreateDocument}, + {"nativeStartPage", "(JIIIIII)J", (void*) nativeStartPage}, + {"nativeFinishPage", "(J)V", (void*) nativeFinishPage}, + {"nativeWriteTo", "(JLjava/io/OutputStream;[B)V", (void*) nativeWriteTo}, + {"nativeClose", "(J)V", (void*) nativeClose} +}; + +int register_android_graphics_pdf_PdfDocument(JNIEnv* env) { + return RegisterMethodsOrDie( + env, "android/graphics/pdf/PdfDocument", gPdfDocument_Methods, + NELEM(gPdfDocument_Methods)); +} + +}; diff --git a/libs/hwui/jni/pdf/PdfEditor.cpp b/libs/hwui/jni/pdf/PdfEditor.cpp new file mode 100644 index 000000000000..828d6e3992b6 --- /dev/null +++ b/libs/hwui/jni/pdf/PdfEditor.cpp @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#undef LOG_TAG +#define LOG_TAG "PdfEditor" + +#include <sys/types.h> +#include <unistd.h> + +#include <vector> + +#include <log/log.h> +#include <utils/Log.h> + +#include "PdfUtils.h" + +#include "graphics_jni_helpers.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" +#include "fpdfview.h" +#include "fpdf_edit.h" +#include "fpdf_save.h" +#include "fpdf_transformpage.h" +#pragma GCC diagnostic pop + +#include "SkMatrix.h" + +namespace android { + +enum PageBox {PAGE_BOX_MEDIA, PAGE_BOX_CROP}; + +static struct { + jfieldID x; + jfieldID y; +} gPointClassInfo; + +static struct { + jfieldID left; + jfieldID top; + jfieldID right; + jfieldID bottom; +} gRectClassInfo; + +static jint nativeRemovePage(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex) { + FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr); + + FPDFPage_Delete(document, pageIndex); + return FPDF_GetPageCount(document); +} + +struct PdfToFdWriter : FPDF_FILEWRITE { + int dstFd; +}; + +static bool writeAllBytes(const int fd, const void* buffer, const size_t byteCount) { + char* writeBuffer = static_cast<char*>(const_cast<void*>(buffer)); + size_t remainingBytes = byteCount; + while (remainingBytes > 0) { + ssize_t writtenByteCount = write(fd, writeBuffer, remainingBytes); + if (writtenByteCount == -1) { + if (errno == EINTR) { + continue; + } + ALOGE("Error writing to buffer: %d", errno); + return false; + } + remainingBytes -= writtenByteCount; + writeBuffer += writtenByteCount; + } + return true; +} + +static int writeBlock(FPDF_FILEWRITE* owner, const void* buffer, unsigned long size) { + const PdfToFdWriter* writer = reinterpret_cast<PdfToFdWriter*>(owner); + const bool success = writeAllBytes(writer->dstFd, buffer, size); + if (!success) { + ALOGE("Cannot write to file descriptor. Error:%d", errno); + return 0; + } + return 1; +} + +static void nativeWrite(JNIEnv* env, jclass thiz, jlong documentPtr, jint fd) { + FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr); + PdfToFdWriter writer; + writer.dstFd = fd; + writer.WriteBlock = &writeBlock; + const bool success = FPDF_SaveAsCopy(document, &writer, FPDF_NO_INCREMENTAL); + if (!success) { + jniThrowExceptionFmt(env, "java/io/IOException", + "cannot write to fd. Error: %d", errno); + } +} + +static void nativeSetTransformAndClip(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex, + jlong transformPtr, jint clipLeft, jint clipTop, jint clipRight, jint clipBottom) { + FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr); + + FPDF_PAGE* page = (FPDF_PAGE*) FPDF_LoadPage(document, pageIndex); + if (!page) { + jniThrowException(env, "java/lang/IllegalStateException", + "cannot open page"); + return; + } + + double width = 0; + double height = 0; + + const int result = FPDF_GetPageSizeByIndex(document, pageIndex, &width, &height); + if (!result) { + jniThrowException(env, "java/lang/IllegalStateException", + "cannot get page size"); + return; + } + + // PDF's coordinate system origin is left-bottom while in graphics it + // is the top-left. So, translate the PDF coordinates to ours. + SkMatrix reflectOnX = SkMatrix::MakeScale(1, -1); + SkMatrix moveUp = SkMatrix::MakeTrans(0, FPDF_GetPageHeight(page)); + SkMatrix coordinateChange = SkMatrix::Concat(moveUp, reflectOnX); + + // Apply the transformation what was created in our coordinates. + SkMatrix matrix = SkMatrix::Concat(*reinterpret_cast<SkMatrix*>(transformPtr), + coordinateChange); + + // Translate the result back to PDF coordinates. + matrix.setConcat(coordinateChange, matrix); + + SkScalar transformValues[6]; + if (!matrix.asAffine(transformValues)) { + FPDF_ClosePage(page); + + jniThrowException(env, "java/lang/IllegalArgumentException", + "transform matrix has perspective. Only affine matrices are allowed."); + return; + } + + FS_MATRIX transform = {transformValues[SkMatrix::kAScaleX], transformValues[SkMatrix::kASkewY], + transformValues[SkMatrix::kASkewX], transformValues[SkMatrix::kAScaleY], + transformValues[SkMatrix::kATransX], + transformValues[SkMatrix::kATransY]}; + + FS_RECTF clip = {(float) clipLeft, (float) clipTop, (float) clipRight, (float) clipBottom}; + + FPDFPage_TransFormWithClip(page, &transform, &clip); + + FPDF_ClosePage(page); +} + +static void nativeGetPageSize(JNIEnv* env, jclass thiz, jlong documentPtr, + jint pageIndex, jobject outSize) { + FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr); + + FPDF_PAGE page = FPDF_LoadPage(document, pageIndex); + if (!page) { + jniThrowException(env, "java/lang/IllegalStateException", + "cannot open page"); + return; + } + + double width = 0; + double height = 0; + + const int result = FPDF_GetPageSizeByIndex(document, pageIndex, &width, &height); + if (!result) { + jniThrowException(env, "java/lang/IllegalStateException", + "cannot get page size"); + return; + } + + env->SetIntField(outSize, gPointClassInfo.x, width); + env->SetIntField(outSize, gPointClassInfo.y, height); + + FPDF_ClosePage(page); +} + +static bool nativeGetPageBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex, + PageBox pageBox, jobject outBox) { + FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr); + + FPDF_PAGE page = FPDF_LoadPage(document, pageIndex); + if (!page) { + jniThrowException(env, "java/lang/IllegalStateException", + "cannot open page"); + return false; + } + + float left; + float top; + float right; + float bottom; + + const FPDF_BOOL success = (pageBox == PAGE_BOX_MEDIA) + ? FPDFPage_GetMediaBox(page, &left, &top, &right, &bottom) + : FPDFPage_GetCropBox(page, &left, &top, &right, &bottom); + + FPDF_ClosePage(page); + + if (!success) { + return false; + } + + env->SetIntField(outBox, gRectClassInfo.left, (int) left); + env->SetIntField(outBox, gRectClassInfo.top, (int) top); + env->SetIntField(outBox, gRectClassInfo.right, (int) right); + env->SetIntField(outBox, gRectClassInfo.bottom, (int) bottom); + + return true; +} + +static jboolean nativeGetPageMediaBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex, + jobject outMediaBox) { + const bool success = nativeGetPageBox(env, thiz, documentPtr, pageIndex, PAGE_BOX_MEDIA, + outMediaBox); + return success ? JNI_TRUE : JNI_FALSE; +} + +static jboolean nativeGetPageCropBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex, + jobject outMediaBox) { + const bool success = nativeGetPageBox(env, thiz, documentPtr, pageIndex, PAGE_BOX_CROP, + outMediaBox); + return success ? JNI_TRUE : JNI_FALSE; +} + +static void nativeSetPageBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex, + PageBox pageBox, jobject box) { + FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr); + + FPDF_PAGE page = FPDF_LoadPage(document, pageIndex); + if (!page) { + jniThrowException(env, "java/lang/IllegalStateException", + "cannot open page"); + return; + } + + const int left = env->GetIntField(box, gRectClassInfo.left); + const int top = env->GetIntField(box, gRectClassInfo.top); + const int right = env->GetIntField(box, gRectClassInfo.right); + const int bottom = env->GetIntField(box, gRectClassInfo.bottom); + + if (pageBox == PAGE_BOX_MEDIA) { + FPDFPage_SetMediaBox(page, left, top, right, bottom); + } else { + FPDFPage_SetCropBox(page, left, top, right, bottom); + } + + FPDF_ClosePage(page); +} + +static void nativeSetPageMediaBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex, + jobject mediaBox) { + nativeSetPageBox(env, thiz, documentPtr, pageIndex, PAGE_BOX_MEDIA, mediaBox); +} + +static void nativeSetPageCropBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex, + jobject mediaBox) { + nativeSetPageBox(env, thiz, documentPtr, pageIndex, PAGE_BOX_CROP, mediaBox); +} + +static const JNINativeMethod gPdfEditor_Methods[] = { + {"nativeOpen", "(IJ)J", (void*) nativeOpen}, + {"nativeClose", "(J)V", (void*) nativeClose}, + {"nativeGetPageCount", "(J)I", (void*) nativeGetPageCount}, + {"nativeRemovePage", "(JI)I", (void*) nativeRemovePage}, + {"nativeWrite", "(JI)V", (void*) nativeWrite}, + {"nativeSetTransformAndClip", "(JIJIIII)V", (void*) nativeSetTransformAndClip}, + {"nativeGetPageSize", "(JILandroid/graphics/Point;)V", (void*) nativeGetPageSize}, + {"nativeScaleForPrinting", "(J)Z", (void*) nativeScaleForPrinting}, + {"nativeGetPageMediaBox", "(JILandroid/graphics/Rect;)Z", (void*) nativeGetPageMediaBox}, + {"nativeSetPageMediaBox", "(JILandroid/graphics/Rect;)V", (void*) nativeSetPageMediaBox}, + {"nativeGetPageCropBox", "(JILandroid/graphics/Rect;)Z", (void*) nativeGetPageCropBox}, + {"nativeSetPageCropBox", "(JILandroid/graphics/Rect;)V", (void*) nativeSetPageCropBox} +}; + +int register_android_graphics_pdf_PdfEditor(JNIEnv* env) { + const int result = RegisterMethodsOrDie( + env, "android/graphics/pdf/PdfEditor", gPdfEditor_Methods, + NELEM(gPdfEditor_Methods)); + + jclass pointClass = FindClassOrDie(env, "android/graphics/Point"); + gPointClassInfo.x = GetFieldIDOrDie(env, pointClass, "x", "I"); + gPointClassInfo.y = GetFieldIDOrDie(env, pointClass, "y", "I"); + + jclass rectClass = FindClassOrDie(env, "android/graphics/Rect"); + gRectClassInfo.left = GetFieldIDOrDie(env, rectClass, "left", "I"); + gRectClassInfo.top = GetFieldIDOrDie(env, rectClass, "top", "I"); + gRectClassInfo.right = GetFieldIDOrDie(env, rectClass, "right", "I"); + gRectClassInfo.bottom = GetFieldIDOrDie(env, rectClass, "bottom", "I"); + + return result; +}; + +}; diff --git a/libs/hwui/jni/pdf/PdfRenderer.cpp b/libs/hwui/jni/pdf/PdfRenderer.cpp new file mode 100644 index 000000000000..cc1f96197c74 --- /dev/null +++ b/libs/hwui/jni/pdf/PdfRenderer.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "PdfUtils.h" + +#include "GraphicsJNI.h" +#include "SkBitmap.h" +#include "SkMatrix.h" +#include "fpdfview.h" + +#include <vector> +#include <utils/Log.h> +#include <unistd.h> +#include <sys/types.h> +#include <unistd.h> + +namespace android { + +static const int RENDER_MODE_FOR_DISPLAY = 1; +static const int RENDER_MODE_FOR_PRINT = 2; + +static struct { + jfieldID x; + jfieldID y; +} gPointClassInfo; + +static jlong nativeOpenPageAndGetSize(JNIEnv* env, jclass thiz, jlong documentPtr, + jint pageIndex, jobject outSize) { + FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr); + + FPDF_PAGE page = FPDF_LoadPage(document, pageIndex); + if (!page) { + jniThrowException(env, "java/lang/IllegalStateException", + "cannot load page"); + return -1; + } + + double width = 0; + double height = 0; + + int result = FPDF_GetPageSizeByIndex(document, pageIndex, &width, &height); + if (!result) { + jniThrowException(env, "java/lang/IllegalStateException", + "cannot get page size"); + return -1; + } + + env->SetIntField(outSize, gPointClassInfo.x, width); + env->SetIntField(outSize, gPointClassInfo.y, height); + + return reinterpret_cast<jlong>(page); +} + +static void nativeClosePage(JNIEnv* env, jclass thiz, jlong pagePtr) { + FPDF_PAGE page = reinterpret_cast<FPDF_PAGE>(pagePtr); + FPDF_ClosePage(page); +} + +static void nativeRenderPage(JNIEnv* env, jclass thiz, jlong documentPtr, jlong pagePtr, + jlong bitmapPtr, jint clipLeft, jint clipTop, jint clipRight, jint clipBottom, + jlong transformPtr, jint renderMode) { + FPDF_PAGE page = reinterpret_cast<FPDF_PAGE>(pagePtr); + + SkBitmap skBitmap; + bitmap::toBitmap(bitmapPtr).getSkBitmap(&skBitmap); + + const int stride = skBitmap.width() * 4; + + FPDF_BITMAP bitmap = FPDFBitmap_CreateEx(skBitmap.width(), skBitmap.height(), + FPDFBitmap_BGRA, skBitmap.getPixels(), stride); + + int renderFlags = FPDF_REVERSE_BYTE_ORDER; + if (renderMode == RENDER_MODE_FOR_DISPLAY) { + renderFlags |= FPDF_LCD_TEXT; + } else if (renderMode == RENDER_MODE_FOR_PRINT) { + renderFlags |= FPDF_PRINTING; + } + + SkMatrix matrix = *reinterpret_cast<SkMatrix*>(transformPtr); + SkScalar transformValues[6]; + if (!matrix.asAffine(transformValues)) { + jniThrowException(env, "java/lang/IllegalArgumentException", + "transform matrix has perspective. Only affine matrices are allowed."); + return; + } + + FS_MATRIX transform = {transformValues[SkMatrix::kAScaleX], transformValues[SkMatrix::kASkewY], + transformValues[SkMatrix::kASkewX], transformValues[SkMatrix::kAScaleY], + transformValues[SkMatrix::kATransX], + transformValues[SkMatrix::kATransY]}; + + FS_RECTF clip = {(float) clipLeft, (float) clipTop, (float) clipRight, (float) clipBottom}; + + FPDF_RenderPageBitmapWithMatrix(bitmap, page, &transform, &clip, renderFlags); + + skBitmap.notifyPixelsChanged(); +} + +static const JNINativeMethod gPdfRenderer_Methods[] = { + {"nativeCreate", "(IJ)J", (void*) nativeOpen}, + {"nativeClose", "(J)V", (void*) nativeClose}, + {"nativeGetPageCount", "(J)I", (void*) nativeGetPageCount}, + {"nativeScaleForPrinting", "(J)Z", (void*) nativeScaleForPrinting}, + {"nativeRenderPage", "(JJJIIIIJI)V", (void*) nativeRenderPage}, + {"nativeOpenPageAndGetSize", "(JILandroid/graphics/Point;)J", (void*) nativeOpenPageAndGetSize}, + {"nativeClosePage", "(J)V", (void*) nativeClosePage} +}; + +int register_android_graphics_pdf_PdfRenderer(JNIEnv* env) { + int result = RegisterMethodsOrDie( + env, "android/graphics/pdf/PdfRenderer", gPdfRenderer_Methods, + NELEM(gPdfRenderer_Methods)); + + jclass clazz = FindClassOrDie(env, "android/graphics/Point"); + gPointClassInfo.x = GetFieldIDOrDie(env, clazz, "x", "I"); + gPointClassInfo.y = GetFieldIDOrDie(env, clazz, "y", "I"); + + return result; +}; + +}; diff --git a/libs/hwui/jni/pdf/PdfUtils.cpp b/libs/hwui/jni/pdf/PdfUtils.cpp new file mode 100644 index 000000000000..06d202828b85 --- /dev/null +++ b/libs/hwui/jni/pdf/PdfUtils.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "PdfUtils.h" + +#include "jni.h" +#include <nativehelper/JNIHelp.h> + +#include "fpdfview.h" + +#undef LOG_TAG +#define LOG_TAG "PdfUtils" +#include <utils/Log.h> + +namespace android { + +static int sUnmatchedPdfiumInitRequestCount = 0; + +int getBlock(void* param, unsigned long position, unsigned char* outBuffer, + unsigned long size) { + const int fd = reinterpret_cast<intptr_t>(param); + const int readCount = pread(fd, outBuffer, size, position); + if (readCount < 0) { + ALOGE("Cannot read from file descriptor. Error:%d", errno); + return 0; + } + return 1; +} + +// Check if the last pdfium command failed and if so, forward the error to java via an exception. If +// this function returns true an exception is pending. +bool forwardPdfiumError(JNIEnv* env) { + long error = FPDF_GetLastError(); + switch (error) { + case FPDF_ERR_SUCCESS: + return false; + case FPDF_ERR_FILE: + jniThrowException(env, "java/io/IOException", "file not found or cannot be opened"); + break; + case FPDF_ERR_FORMAT: + jniThrowException(env, "java/io/IOException", "file not in PDF format or corrupted"); + break; + case FPDF_ERR_PASSWORD: + jniThrowException(env, "java/lang/SecurityException", + "password required or incorrect password"); + break; + case FPDF_ERR_SECURITY: + jniThrowException(env, "java/lang/SecurityException", "unsupported security scheme"); + break; + case FPDF_ERR_PAGE: + jniThrowException(env, "java/io/IOException", "page not found or content error"); + break; +#ifdef PDF_ENABLE_XFA + case FPDF_ERR_XFALOAD: + jniThrowException(env, "java/lang/Exception", "load XFA error"); + break; + case FPDF_ERR_XFALAYOUT: + jniThrowException(env, "java/lang/Exception", "layout XFA error"); + break; +#endif // PDF_ENABLE_XFA + case FPDF_ERR_UNKNOWN: + default: + jniThrowExceptionFmt(env, "java/lang/Exception", "unknown error %d", error); + } + + return true; +} + +static void initializeLibraryIfNeeded(JNIEnv* env) { + if (sUnmatchedPdfiumInitRequestCount == 0) { + FPDF_InitLibrary(); + } + + sUnmatchedPdfiumInitRequestCount++; +} + +static void destroyLibraryIfNeeded(JNIEnv* env, bool handleError) { + if (sUnmatchedPdfiumInitRequestCount == 1) { + FPDF_DestroyLibrary(); + } + + sUnmatchedPdfiumInitRequestCount--; +} + +jlong nativeOpen(JNIEnv* env, jclass thiz, jint fd, jlong size) { + initializeLibraryIfNeeded(env); + + FPDF_FILEACCESS loader; + loader.m_FileLen = size; + loader.m_Param = reinterpret_cast<void*>(intptr_t(fd)); + loader.m_GetBlock = &getBlock; + + FPDF_DOCUMENT document = FPDF_LoadCustomDocument(&loader, NULL); + if (!document) { + forwardPdfiumError(env); + destroyLibraryIfNeeded(env, false); + return -1; + } + + return reinterpret_cast<jlong>(document); +} + +void nativeClose(JNIEnv* env, jclass thiz, jlong documentPtr) { + FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr); + FPDF_CloseDocument(document); + + destroyLibraryIfNeeded(env, true); +} + +jint nativeGetPageCount(JNIEnv* env, jclass thiz, jlong documentPtr) { + FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr); + + return FPDF_GetPageCount(document); +} + +jboolean nativeScaleForPrinting(JNIEnv* env, jclass thiz, jlong documentPtr) { + FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr); + FPDF_BOOL printScaling = FPDF_VIEWERREF_GetPrintScaling(document); + + return printScaling ? JNI_TRUE : JNI_FALSE; +} + +}; diff --git a/libs/hwui/jni/pdf/PdfUtils.h b/libs/hwui/jni/pdf/PdfUtils.h new file mode 100644 index 000000000000..65327382e899 --- /dev/null +++ b/libs/hwui/jni/pdf/PdfUtils.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PDF_UTILS_H_ +#define PDF_UTILS_H_ + +#include "jni.h" + +namespace android { + +int getBlock(void* param, unsigned long position, unsigned char* outBuffer, + unsigned long size); + +jlong nativeOpen(JNIEnv* env, jclass thiz, jint fd, jlong size); +void nativeClose(JNIEnv* env, jclass thiz, jlong documentPtr); + +jint nativeGetPageCount(JNIEnv* env, jclass thiz, jlong documentPtr); +jboolean nativeScaleForPrinting(JNIEnv* env, jclass thiz, jlong documentPtr); + +}; + +#endif /* PDF_UTILS_H_ */ diff --git a/libs/hwui/jni/scoped_nullable_primitive_array.h b/libs/hwui/jni/scoped_nullable_primitive_array.h new file mode 100644 index 000000000000..77f4c9d14f07 --- /dev/null +++ b/libs/hwui/jni/scoped_nullable_primitive_array.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SCOPED_NULLABLE_PRIMITIVE_ARRAY_H +#define SCOPED_NULLABLE_PRIMITIVE_ARRAY_H + +#include <jni.h> + +namespace android { + +#define ARRAY_TRAITS(ARRAY_TYPE, POINTER_TYPE, NAME) \ +class NAME ## ArrayTraits { \ +public: \ + static constexpr void getArrayRegion(JNIEnv* env, ARRAY_TYPE array, size_t start, \ + size_t len, POINTER_TYPE out) { \ + env->Get ## NAME ## ArrayRegion(array, start, len, out); \ + } \ + \ + static constexpr POINTER_TYPE getArrayElements(JNIEnv* env, ARRAY_TYPE array) { \ + return env->Get ## NAME ## ArrayElements(array, nullptr); \ + } \ + \ + static constexpr void releaseArrayElements(JNIEnv* env, ARRAY_TYPE array, \ + POINTER_TYPE buffer, jint mode) { \ + env->Release ## NAME ## ArrayElements(array, buffer, mode); \ + } \ +}; \ + +ARRAY_TRAITS(jbooleanArray, jboolean*, Boolean) +ARRAY_TRAITS(jbyteArray, jbyte*, Byte) +ARRAY_TRAITS(jcharArray, jchar*, Char) +ARRAY_TRAITS(jdoubleArray, jdouble*, Double) +ARRAY_TRAITS(jfloatArray, jfloat*, Float) +ARRAY_TRAITS(jintArray, jint*, Int) +ARRAY_TRAITS(jlongArray, jlong*, Long) +ARRAY_TRAITS(jshortArray, jshort*, Short) + +#undef ARRAY_TRAITS + +template<typename JavaArrayType, typename PrimitiveType, class Traits, size_t preallocSize = 10> +class ScopedArrayRO { +public: + ScopedArrayRO(JNIEnv* env, JavaArrayType javaArray) : mEnv(env), mJavaArray(javaArray) { + if (mJavaArray == nullptr) { + mSize = 0; + mRawArray = nullptr; + } else { + mSize = mEnv->GetArrayLength(mJavaArray); + if (mSize <= preallocSize) { + Traits::getArrayRegion(mEnv, mJavaArray, 0, mSize, mBuffer); + mRawArray = mBuffer; + } else { + mRawArray = Traits::getArrayElements(mEnv, mJavaArray); + } + } + } + + ~ScopedArrayRO() { + if (mRawArray != nullptr && mRawArray != mBuffer) { + Traits::releaseArrayElements(mEnv, mJavaArray, mRawArray, JNI_ABORT); + } + } + + const PrimitiveType* get() const { return mRawArray; } + const PrimitiveType& operator[](size_t n) const { return mRawArray[n]; } + size_t size() const { return mSize; } + +private: + JNIEnv* const mEnv; + JavaArrayType mJavaArray; + PrimitiveType* mRawArray; + size_t mSize; + PrimitiveType mBuffer[preallocSize]; + DISALLOW_COPY_AND_ASSIGN(ScopedArrayRO); +}; + +// ScopedNullable***ArrayRO provide convenient read-only access to Java array from JNI code. +// These accept nullptr. In that case, get() returns nullptr and size() returns 0. +using ScopedNullableBooleanArrayRO = ScopedArrayRO<jbooleanArray, jboolean, BooleanArrayTraits>; +using ScopedNullableByteArrayRO = ScopedArrayRO<jbyteArray, jbyte, ByteArrayTraits>; +using ScopedNullableCharArrayRO = ScopedArrayRO<jcharArray, jchar, CharArrayTraits>; +using ScopedNullableDoubleArrayRO = ScopedArrayRO<jdoubleArray, jdouble, DoubleArrayTraits>; +using ScopedNullableFloatArrayRO = ScopedArrayRO<jfloatArray, jfloat, FloatArrayTraits>; +using ScopedNullableIntArrayRO = ScopedArrayRO<jintArray, jint, IntArrayTraits>; +using ScopedNullableLongArrayRO = ScopedArrayRO<jlongArray, jlong, LongArrayTraits>; +using ScopedNullableShortArrayRO = ScopedArrayRO<jshortArray, jshort, ShortArrayTraits>; + +} // namespace android + +#endif // SCOPED_NULLABLE_PRIMITIVE_ARRAY_H diff --git a/libs/hwui/jni/text/LineBreaker.cpp b/libs/hwui/jni/text/LineBreaker.cpp new file mode 100644 index 000000000000..69865171a09d --- /dev/null +++ b/libs/hwui/jni/text/LineBreaker.cpp @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#undef LOG_TAG +#define LOG_TAG "LineBreaker" + +#include "utils/misc.h" +#include "utils/Log.h" +#include "graphics_jni_helpers.h" +#include <nativehelper/ScopedStringChars.h> +#include <nativehelper/ScopedPrimitiveArray.h> +#include "scoped_nullable_primitive_array.h" +#include <cstdint> +#include <vector> +#include <list> +#include <algorithm> + +#include "SkPaint.h" +#include "SkTypeface.h" +#include <hwui/MinikinSkia.h> +#include <hwui/MinikinUtils.h> +#include <hwui/Paint.h> +#include <minikin/FontCollection.h> +#include <minikin/AndroidLineBreakerHelper.h> +#include <minikin/MinikinFont.h> + +namespace android { + +static inline std::vector<float> jintArrayToFloatVector(JNIEnv* env, jintArray javaArray) { + if (javaArray == nullptr) { + return std::vector<float>(); + } else { + ScopedIntArrayRO intArr(env, javaArray); + return std::vector<float>(intArr.get(), intArr.get() + intArr.size()); + } +} + +static inline minikin::android::StaticLayoutNative* toNative(jlong ptr) { + return reinterpret_cast<minikin::android::StaticLayoutNative*>(ptr); +} + +// set text and set a number of parameters for creating a layout (width, tabstops, strategy, +// hyphenFrequency) +static jlong nInit(JNIEnv* env, jclass /* unused */, + jint breakStrategy, jint hyphenationFrequency, jboolean isJustified, jintArray indents) { + return reinterpret_cast<jlong>(new minikin::android::StaticLayoutNative( + static_cast<minikin::BreakStrategy>(breakStrategy), + static_cast<minikin::HyphenationFrequency>(hyphenationFrequency), + isJustified, + jintArrayToFloatVector(env, indents))); +} + +static void nFinish(jlong nativePtr) { + delete toNative(nativePtr); +} + +// CriticalNative +static jlong nGetReleaseFunc(CRITICAL_JNI_PARAMS) { + return reinterpret_cast<jlong>(nFinish); +} + +static jlong nComputeLineBreaks(JNIEnv* env, jclass, jlong nativePtr, + // Inputs + jcharArray javaText, + jlong measuredTextPtr, + jint length, + jfloat firstWidth, + jint firstWidthLineCount, + jfloat restWidth, + jfloatArray variableTabStops, + jfloat defaultTabStop, + jint indentsOffset) { + minikin::android::StaticLayoutNative* builder = toNative(nativePtr); + + ScopedCharArrayRO text(env, javaText); + ScopedNullableFloatArrayRO tabStops(env, variableTabStops); + + minikin::U16StringPiece u16Text(text.get(), length); + minikin::MeasuredText* measuredText = reinterpret_cast<minikin::MeasuredText*>(measuredTextPtr); + + std::unique_ptr<minikin::LineBreakResult> result = + std::make_unique<minikin::LineBreakResult>(builder->computeBreaks( + u16Text, *measuredText, firstWidth, firstWidthLineCount, restWidth, indentsOffset, + tabStops.get(), tabStops.size(), defaultTabStop)); + return reinterpret_cast<jlong>(result.release()); +} + +static jint nGetLineCount(CRITICAL_JNI_PARAMS_COMMA jlong ptr) { + return reinterpret_cast<minikin::LineBreakResult*>(ptr)->breakPoints.size(); +} + +static jint nGetLineBreakOffset(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) { + return reinterpret_cast<minikin::LineBreakResult*>(ptr)->breakPoints[i]; +} + +static jfloat nGetLineWidth(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) { + return reinterpret_cast<minikin::LineBreakResult*>(ptr)->widths[i]; +} + +static jfloat nGetLineAscent(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) { + return reinterpret_cast<minikin::LineBreakResult*>(ptr)->ascents[i]; +} + +static jfloat nGetLineDescent(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) { + return reinterpret_cast<minikin::LineBreakResult*>(ptr)->descents[i]; +} + +static jint nGetLineFlag(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) { + return reinterpret_cast<minikin::LineBreakResult*>(ptr)->flags[i]; +} + +static void nReleaseResult(jlong ptr) { + delete reinterpret_cast<minikin::LineBreakResult*>(ptr); +} + +static jlong nGetReleaseResultFunc(CRITICAL_JNI_PARAMS) { + return reinterpret_cast<jlong>(nReleaseResult); +} + +static const JNINativeMethod gMethods[] = { + // Fast Natives + {"nInit", "(" + "I" // breakStrategy + "I" // hyphenationFrequency + "Z" // isJustified + "[I" // indents + ")J", (void*) nInit}, + + // Critical Natives + {"nGetReleaseFunc", "()J", (void*) nGetReleaseFunc}, + + // Regular JNI + {"nComputeLineBreaks", "(" + "J" // nativePtr + "[C" // text + "J" // MeasuredParagraph ptr. + "I" // length + "F" // firstWidth + "I" // firstWidthLineCount + "F" // restWidth + "[F" // variableTabStops + "F" // defaultTabStop + "I" // indentsOffset + ")J", (void*) nComputeLineBreaks}, + + // Result accessors, CriticalNatives + {"nGetLineCount", "(J)I", (void*)nGetLineCount}, + {"nGetLineBreakOffset", "(JI)I", (void*)nGetLineBreakOffset}, + {"nGetLineWidth", "(JI)F", (void*)nGetLineWidth}, + {"nGetLineAscent", "(JI)F", (void*)nGetLineAscent}, + {"nGetLineDescent", "(JI)F", (void*)nGetLineDescent}, + {"nGetLineFlag", "(JI)I", (void*)nGetLineFlag}, + {"nGetReleaseResultFunc", "()J", (void*)nGetReleaseResultFunc}, +}; + +int register_android_graphics_text_LineBreaker(JNIEnv* env) { + return RegisterMethodsOrDie(env, "android/graphics/text/LineBreaker", gMethods, + NELEM(gMethods)); +} + +} diff --git a/libs/hwui/jni/text/MeasuredText.cpp b/libs/hwui/jni/text/MeasuredText.cpp new file mode 100644 index 000000000000..7793746ee285 --- /dev/null +++ b/libs/hwui/jni/text/MeasuredText.cpp @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#undef LOG_TAG +#define LOG_TAG "MeasuredText" + +#include "GraphicsJNI.h" +#include "utils/misc.h" +#include "utils/Log.h" +#include <nativehelper/ScopedStringChars.h> +#include <nativehelper/ScopedPrimitiveArray.h> +#include <cstdint> +#include <vector> +#include <list> +#include <algorithm> + +#include "SkPaint.h" +#include "SkTypeface.h" +#include <hwui/MinikinSkia.h> +#include <hwui/MinikinUtils.h> +#include <hwui/Paint.h> +#include <minikin/FontCollection.h> +#include <minikin/AndroidLineBreakerHelper.h> +#include <minikin/MinikinFont.h> + +namespace android { + +static inline minikin::MeasuredTextBuilder* toBuilder(jlong ptr) { + return reinterpret_cast<minikin::MeasuredTextBuilder*>(ptr); +} + +static inline Paint* toPaint(jlong ptr) { + return reinterpret_cast<Paint*>(ptr); +} + +static inline minikin::MeasuredText* toMeasuredParagraph(jlong ptr) { + return reinterpret_cast<minikin::MeasuredText*>(ptr); +} + +template<typename Ptr> static inline jlong toJLong(Ptr ptr) { + return reinterpret_cast<jlong>(ptr); +} + +static void releaseMeasuredParagraph(jlong measuredTextPtr) { + delete toMeasuredParagraph(measuredTextPtr); +} + +// Regular JNI +static jlong nInitBuilder(CRITICAL_JNI_PARAMS) { + return toJLong(new minikin::MeasuredTextBuilder()); +} + +// Regular JNI +static void nAddStyleRun(JNIEnv* /* unused */, jclass /* unused */, jlong builderPtr, + jlong paintPtr, jint start, jint end, jboolean isRtl) { + Paint* paint = toPaint(paintPtr); + const Typeface* typeface = Typeface::resolveDefault(paint->getAndroidTypeface()); + minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(paint, typeface); + toBuilder(builderPtr)->addStyleRun(start, end, std::move(minikinPaint), isRtl); +} + +// Regular JNI +static void nAddReplacementRun(JNIEnv* /* unused */, jclass /* unused */, jlong builderPtr, + jlong paintPtr, jint start, jint end, jfloat width) { + toBuilder(builderPtr)->addReplacementRun(start, end, width, + toPaint(paintPtr)->getMinikinLocaleListId()); +} + +// Regular JNI +static jlong nBuildMeasuredText(JNIEnv* env, jclass /* unused */, jlong builderPtr, + jlong hintPtr, jcharArray javaText, jboolean computeHyphenation, + jboolean computeLayout) { + ScopedCharArrayRO text(env, javaText); + const minikin::U16StringPiece textBuffer(text.get(), text.size()); + + // Pass the ownership to Java. + return toJLong(toBuilder(builderPtr)->build(textBuffer, computeHyphenation, computeLayout, + toMeasuredParagraph(hintPtr)).release()); +} + +// Regular JNI +static void nFreeBuilder(JNIEnv* env, jclass /* unused */, jlong builderPtr) { + delete toBuilder(builderPtr); +} + +// CriticalNative +static jfloat nGetWidth(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint start, jint end) { + minikin::MeasuredText* mt = toMeasuredParagraph(ptr); + float r = 0.0f; + for (int i = start; i < end; ++i) { + r += mt->widths[i]; + } + return r; +} + +static jfloat nGetCharWidthAt(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint offset) { + return toMeasuredParagraph(ptr)->widths[offset]; +} + +// Regular JNI +static void nGetBounds(JNIEnv* env, jobject, jlong ptr, jcharArray javaText, jint start, jint end, + jobject bounds) { + ScopedCharArrayRO text(env, javaText); + const minikin::U16StringPiece textBuffer(text.get(), text.size()); + const minikin::Range range(start, end); + + minikin::MinikinRect rect = toMeasuredParagraph(ptr)->getBounds(textBuffer, range); + + SkRect r; + r.fLeft = rect.mLeft; + r.fTop = rect.mTop; + r.fRight = rect.mRight; + r.fBottom = rect.mBottom; + + SkIRect ir; + r.roundOut(&ir); + GraphicsJNI::irect_to_jrect(ir, env, bounds); +} + +// CriticalNative +static jlong nGetReleaseFunc(CRITICAL_JNI_PARAMS) { + return toJLong(&releaseMeasuredParagraph); +} + +static jint nGetMemoryUsage(CRITICAL_JNI_PARAMS_COMMA jlong ptr) { + return static_cast<jint>(toMeasuredParagraph(ptr)->getMemoryUsage()); +} + +static const JNINativeMethod gMTBuilderMethods[] = { + // MeasuredParagraphBuilder native functions. + {"nInitBuilder", "()J", (void*) nInitBuilder}, + {"nAddStyleRun", "(JJIIZ)V", (void*) nAddStyleRun}, + {"nAddReplacementRun", "(JJIIF)V", (void*) nAddReplacementRun}, + {"nBuildMeasuredText", "(JJ[CZZ)J", (void*) nBuildMeasuredText}, + {"nFreeBuilder", "(J)V", (void*) nFreeBuilder}, +}; + +static const JNINativeMethod gMTMethods[] = { + // MeasuredParagraph native functions. + {"nGetWidth", "(JII)F", (void*) nGetWidth}, // Critical Natives + {"nGetBounds", "(J[CIILandroid/graphics/Rect;)V", (void*) nGetBounds}, // Regular JNI + {"nGetReleaseFunc", "()J", (void*) nGetReleaseFunc}, // Critical Natives + {"nGetMemoryUsage", "(J)I", (void*) nGetMemoryUsage}, // Critical Native + {"nGetCharWidthAt", "(JI)F", (void*) nGetCharWidthAt}, // Critical Native +}; + +int register_android_graphics_text_MeasuredText(JNIEnv* env) { + return RegisterMethodsOrDie(env, "android/graphics/text/MeasuredText", + gMTMethods, NELEM(gMTMethods)) + + RegisterMethodsOrDie(env, "android/graphics/text/MeasuredText$Builder", + gMTBuilderMethods, NELEM(gMTBuilderMethods)); +} + +} |