Read the original article:Implementing Custom Drawing Native C (NDK)
Context
When the registered event is detected as a drawing type, you can use the custom drawing feature to implement your own drawing logic and render custom content.
Description
This module demonstrates how to implement custom 2D drawing using the Native Development Kit (NDK). By leveraging the power of C/C++ and low-level graphics APIs such as ANativeWindow, Canvas, or OpenGL ES, developers can create high-performance rendering routines suitable for games, visualizations, or custom UI components.
Solution/Approach
napi_init.cpp
#include "native_drawing/drawing_canvas.h"
#include "native_drawing/drawing_color.h"
#include "native_drawing/drawing_path.h"
#include <arkui/native_interface.h>
#include <arkui/native_node.h>
#include <arkui/native_node_napi.h>
#include <js_native_api_types.h>
#include <native_drawing/drawing_pen.h>
#include <string>
ArkUI_NativeNodeAPI_1 *nodeAPI = nullptr;
// Define custom UserData.
struct A {
int32_t a = 6;
bool flag = true;
ArkUI_NodeHandle node;
};
ArkUI_NodeHandle test(ArkUI_NodeContentHandle handle) {
ArkUI_NativeNodeAPI_1 *nodeAPI = reinterpret_cast<ArkUI_NativeNodeAPI_1 *>(
OH_ArkUI_QueryModuleInterfaceByName(ARKUI_NATIVE_NODE, "ArkUI_NativeNodeAPI_1"));
auto customNode = nodeAPI->createNode(ARKUI_NODE_CUSTOM);
// Create a node.
auto column = nodeAPI->createNode(ARKUI_NODE_COLUMN);
ArkUI_NumberValue value[] = {480};
ArkUI_AttributeItem item = {value, 1};
// Set attributes.
nodeAPI->setAttribute(column, NODE_WIDTH, &item);
value[0].i32 = 720;
nodeAPI->setAttribute(column, NODE_HEIGHT, &item);
ArkUI_NumberValue NODE_WIDTH_value[] = {200};
ArkUI_AttributeItem NODE_WIDTH_Item[] = {NODE_WIDTH_value, 1};
ArkUI_NumberValue NODE_HEIGHT_value[] = {200};
ArkUI_AttributeItem NODE_HEIGHT_Item[] = {NODE_HEIGHT_value, 1};
ArkUI_NumberValue NODE_BACKGROUND_COLOR_item_value[] = {{.u32 = 0xFFFFFF00}};
ArkUI_AttributeItem NODE_BACKGROUND_COLOR_Item[] = {NODE_BACKGROUND_COLOR_item_value, 1};
A *a = new A;
a->node = customNode;
nodeAPI->setAttribute(customNode, NODE_WIDTH, NODE_WIDTH_Item);
nodeAPI->setAttribute(customNode, NODE_HEIGHT, NODE_HEIGHT_Item);
nodeAPI->setAttribute(customNode, NODE_BACKGROUND_COLOR, NODE_BACKGROUND_COLOR_Item);
// Register the event.
nodeAPI->registerNodeCustomEvent(customNode, ARKUI_NODE_CUSTOM_EVENT_ON_FOREGROUND_DRAW, 1, a);
// Define the event callback.
nodeAPI->registerNodeCustomEventReceiver([](ArkUI_NodeCustomEvent *event) {
// Obtain information from the custom event.
auto type = OH_ArkUI_NodeCustomEvent_GetEventType(event);
auto targetId = OH_ArkUI_NodeCustomEvent_GetEventTargetId(event);
auto userData = reinterpret_cast<A *>(OH_ArkUI_NodeCustomEvent_GetUserData(event));
if (type == ARKUI_NODE_CUSTOM_EVENT_ON_FOREGROUND_DRAW && targetId == 1 && userData->flag) {
// Obtain the drawing context for the custom event.
auto *drawContext = OH_ArkUI_NodeCustomEvent_GetDrawContextInDraw(event);
// Obtain the drawing canvas pointer.
auto *canvas1 = OH_ArkUI_DrawContext_GetCanvas(drawContext);
// Cast the pointer to an OH_Drawing_Canvas pointer for drawing.
OH_Drawing_Canvas *canvas = reinterpret_cast<OH_Drawing_Canvas *>(canvas1);
int32_t width = 1000;
int32_t height = 1000;
auto path = OH_Drawing_PathCreate();
OH_Drawing_PathMoveTo(path, width / 4, height / 4);
OH_Drawing_PathLineTo(path, width * 3 / 4, height * 3 / 4);
OH_Drawing_PathLineTo(path, width * 3 / 4, height * 3 / 4);
OH_Drawing_PathLineTo(path, width * 3 / 4, height * 3 / 4);
OH_Drawing_PathLineTo(path, width * 3 / 4, height * 3 / 4);
OH_Drawing_PathClose(path);
auto pen = OH_Drawing_PenCreate();
OH_Drawing_PenSetWidth(pen, 10);
OH_Drawing_PenSetColor(pen, OH_Drawing_ColorSetArgb(0xFF, 0xFF, 0x00, 0x00));
OH_Drawing_CanvasAttachPen(canvas, pen);
OH_Drawing_CanvasDrawPath(canvas, path);
}
});
// Add the custom node to the tree.
nodeAPI->addChild(column, customNode);
OH_ArkUI_NodeContent_AddNode(handle, column);
return column;
}
static napi_value Add(napi_env env, napi_callback_info info) {
size_t argc = 2;
napi_value args[2] = {nullptr};
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
ArkUI_NodeContentHandle contentHandle;
OH_ArkUI_GetNodeContentFromNapiValue(env, args[0], &contentHandle);
test(contentHandle);
return 0;
}
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
napi_property_descriptor desc[] = {{"add", nullptr, Add, nullptr, nullptr, nullptr, napi_default, nullptr}};
napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
return exports;
}
EXTERN_C_END
static napi_module demoModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = Init,
.nm_modname = "entry",
.nm_priv = ((void *)0),
.reserved = {0},
};
extern "C" __attribute__((constructor)) void RegisterEntryModule(void) { napi_module_register(&demoModule); }
Index.d.ts
export const add: (content:Object) =>void;
CMakelists.txt
# the minimum version of CMake.
cmake_minimum_required(VERSION 3.5.0)
project(NativeDrawing)
set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
if(DEFINED PACKAGE_FIND_FILE)
include(${PACKAGE_FIND_FILE})
endif()
include_directories(${NATIVERENDER_ROOT_PATH}
${NATIVERENDER_ROOT_PATH}/include)
add_library(entry SHARED napi_init.cpp)
target_link_libraries(entry PUBLIC libace_napi.z.so libnative_drawing.so libace_ndk.z.so)
Index.ets
import { hilog } from '@kit.PerformanceAnalysisKit';
import testNapi from 'libentry.so';
import { NodeContent } from '@kit.ArkUI';
const DOMAIN = 0x0000;
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
rootSlot=new NodeContent()
aboutToAppear(): void {
testNapi.add(this.rootSlot)
}
build() {
Row() {
Column() {
ContentSlot(this.rootSlot)
}
.width('100%')
}
.height('100%')
}
}
Key Takeaways
- High-Performance Rendering: NDK allows low-level access to native drawing surfaces, enabling faster and more efficient rendering compared to Java-based approaches.
- Direct Pixel Manipulation: You can directly access and modify frame buffers using C/C++, giving you full control over the rendering process.
- Integration via JNI: Custom drawing logic written in C/C++ can be seamlessly integrated into Android apps using the Java Native Interface (JNI).
-
Use of ANativeWindow:
ANativeWindowprovides a way to draw directly onto a surface from native code, commonly used in games and media apps. - Optional OpenGL ES Support: For hardware-accelerated graphics, OpenGL ES can be used in conjunction with NDK to render complex visual elements.
- Efficient for Real-Time Graphics: Suitable for applications requiring real-time rendering, such as games, simulations, or custom UI components.
- Resource Management is Crucial: Proper handling of memory, surface lifecycle, and rendering threads is essential to avoid crashes and memory leaks
Top comments (0)