summaryrefslogtreecommitdiff
path: root/libs/hwui
diff options
context:
space:
mode:
authorDerek Sollenberger <djsollen@google.com>2020-02-21 11:43:02 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2020-02-21 11:43:02 +0000
commit76e7430baead21c67fd4c81a36774a6461d2fb99 (patch)
tree239a62b59c57b4b95d8e82943bf9a48e84439819 /libs/hwui
parent1ab8e44c06ca05806a81c6737cb3f6e316d8d25a (diff)
parent2173ea286afff6766043227de0bc2d82d9595f77 (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')
-rw-r--r--libs/hwui/Android.bp232
-rw-r--r--libs/hwui/DeviceInfo.cpp3
-rw-r--r--libs/hwui/apex/LayoutlibLoader.cpp191
-rw-r--r--libs/hwui/apex/TypeCast.h70
-rw-r--r--libs/hwui/apex/android_bitmap.cpp305
-rw-r--r--libs/hwui/apex/android_canvas.cpp109
-rw-r--r--libs/hwui/apex/android_matrix.cpp37
-rw-r--r--libs/hwui/apex/android_paint.cpp47
-rw-r--r--libs/hwui/apex/android_region.cpp60
-rw-r--r--libs/hwui/apex/include/android/graphics/bitmap.h146
-rw-r--r--libs/hwui/apex/include/android/graphics/canvas.h142
-rw-r--r--libs/hwui/apex/include/android/graphics/jni_runtime.h35
-rw-r--r--libs/hwui/apex/include/android/graphics/matrix.h39
-rw-r--r--libs/hwui/apex/include/android/graphics/paint.h67
-rw-r--r--libs/hwui/apex/include/android/graphics/region.h77
-rw-r--r--libs/hwui/apex/include/android/graphics/renderthread.h34
-rw-r--r--libs/hwui/apex/jni_runtime.cpp177
-rw-r--r--libs/hwui/apex/renderthread.cpp25
-rw-r--r--libs/hwui/jni/AnimatedImageDrawable.cpp271
-rwxr-xr-xlibs/hwui/jni/Bitmap.cpp1184
-rw-r--r--libs/hwui/jni/Bitmap.h53
-rw-r--r--libs/hwui/jni/BitmapFactory.cpp667
-rw-r--r--libs/hwui/jni/BitmapFactory.h31
-rw-r--r--libs/hwui/jni/BitmapRegionDecoder.cpp288
-rw-r--r--libs/hwui/jni/ByteBufferStreamAdaptor.cpp334
-rw-r--r--libs/hwui/jni/ByteBufferStreamAdaptor.h37
-rw-r--r--libs/hwui/jni/Camera.cpp143
-rw-r--r--libs/hwui/jni/CanvasProperty.cpp50
-rw-r--r--libs/hwui/jni/ColorFilter.cpp89
-rw-r--r--libs/hwui/jni/CreateJavaOutputStreamAdaptor.cpp306
-rw-r--r--libs/hwui/jni/CreateJavaOutputStreamAdaptor.h42
-rw-r--r--libs/hwui/jni/FontFamily.cpp233
-rw-r--r--libs/hwui/jni/FontUtils.cpp62
-rw-r--r--libs/hwui/jni/FontUtils.h71
-rw-r--r--libs/hwui/jni/GIFMovie.cpp447
-rw-r--r--libs/hwui/jni/Graphics.cpp768
-rw-r--r--libs/hwui/jni/GraphicsJNI.h335
-rw-r--r--libs/hwui/jni/GraphicsStatsService.cpp197
-rw-r--r--libs/hwui/jni/ImageDecoder.cpp529
-rw-r--r--libs/hwui/jni/ImageDecoder.h25
-rw-r--r--libs/hwui/jni/Interpolator.cpp84
-rw-r--r--libs/hwui/jni/MaskFilter.cpp90
-rw-r--r--libs/hwui/jni/MimeType.h22
-rw-r--r--libs/hwui/jni/Movie.cpp164
-rw-r--r--libs/hwui/jni/Movie.h79
-rw-r--r--libs/hwui/jni/MovieImpl.cpp94
-rw-r--r--libs/hwui/jni/NinePatch.cpp168
-rw-r--r--libs/hwui/jni/NinePatchPeeker.cpp93
-rw-r--r--libs/hwui/jni/NinePatchPeeker.h59
-rw-r--r--libs/hwui/jni/Paint.cpp1159
-rw-r--r--libs/hwui/jni/PaintFilter.cpp80
-rw-r--r--libs/hwui/jni/Path.cpp560
-rw-r--r--libs/hwui/jni/PathEffect.cpp117
-rw-r--r--libs/hwui/jni/PathMeasure.cpp160
-rw-r--r--libs/hwui/jni/Picture.cpp119
-rw-r--r--libs/hwui/jni/Picture.h68
-rw-r--r--libs/hwui/jni/Region.cpp359
-rw-r--r--libs/hwui/jni/RtlProperties.h49
-rw-r--r--libs/hwui/jni/Shader.cpp305
-rw-r--r--libs/hwui/jni/TEST_MAPPING7
-rw-r--r--libs/hwui/jni/Typeface.cpp159
-rw-r--r--libs/hwui/jni/Utils.cpp161
-rw-r--r--libs/hwui/jni/Utils.h83
-rw-r--r--libs/hwui/jni/YuvToJpegEncoder.cpp268
-rw-r--r--libs/hwui/jni/YuvToJpegEncoder.h74
-rw-r--r--libs/hwui/jni/android_graphics_Canvas.cpp739
-rw-r--r--libs/hwui/jni/android_graphics_ColorSpace.cpp113
-rw-r--r--libs/hwui/jni/android_graphics_DisplayListCanvas.cpp213
-rw-r--r--libs/hwui/jni/android_graphics_HardwareRenderer.cpp745
-rw-r--r--libs/hwui/jni/android_graphics_HardwareRendererObserver.cpp130
-rw-r--r--libs/hwui/jni/android_graphics_HardwareRendererObserver.h75
-rw-r--r--libs/hwui/jni/android_graphics_Matrix.cpp396
-rw-r--r--libs/hwui/jni/android_graphics_Matrix.h30
-rw-r--r--libs/hwui/jni/android_graphics_Picture.cpp110
-rw-r--r--libs/hwui/jni/android_graphics_RenderNode.cpp759
-rw-r--r--libs/hwui/jni/android_graphics_TextureLayer.cpp85
-rw-r--r--libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp112
-rw-r--r--libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp221
-rw-r--r--libs/hwui/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp218
-rw-r--r--libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp423
-rw-r--r--libs/hwui/jni/android_nio_utils.cpp46
-rw-r--r--libs/hwui/jni/android_nio_utils.h81
-rw-r--r--libs/hwui/jni/android_util_PathParser.cpp118
-rw-r--r--libs/hwui/jni/fonts/Font.cpp142
-rw-r--r--libs/hwui/jni/fonts/FontFamily.cpp101
-rw-r--r--libs/hwui/jni/graphics_jni_helpers.h106
-rw-r--r--libs/hwui/jni/pdf/PdfDocument.cpp163
-rw-r--r--libs/hwui/jni/pdf/PdfEditor.cpp307
-rw-r--r--libs/hwui/jni/pdf/PdfRenderer.cpp134
-rw-r--r--libs/hwui/jni/pdf/PdfUtils.cpp136
-rw-r--r--libs/hwui/jni/pdf/PdfUtils.h35
-rw-r--r--libs/hwui/jni/scoped_nullable_primitive_array.h103
-rw-r--r--libs/hwui/jni/text/LineBreaker.cpp174
-rw-r--r--libs/hwui/jni/text/MeasuredText.cpp167
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));
+}
+
+}