1# 画布的获取与绘制结果的显示(C/C++) 2 3<!--Kit: ArkGraphics 2D--> 4<!--Subsystem: Graphics--> 5<!--Owner: @hangmengxin--> 6<!--Designer: @wangyanglan--> 7<!--Tester: @nobuggers--> 8<!--Adviser: @ge-yafang--> 9 10## 场景介绍 11 12Canvas即画布,提供绘制基本图形的能力,用于在屏幕上绘制图形和处理图形。开发者可以通过Canvas实现自定义的绘图效果,增强应用的用户体验。 13 14Canvas是图形绘制的核心,本章中提到的所有绘制操作(包括基本图形的绘制、文字的绘制、图片的绘制、图形变换等)都是基于Canvas的。 15 16目前C/C++有两种获取Canvas的方式:获取可直接上屏显示的Canvas、获取离屏的Canvas,前者在调用绘制接口之后无需进行额外的操作即可完成绘制结果的上屏显示,而后者需要依靠已有的显示手段来显示绘制结果。 17 18 19## 接口说明 20 21创建Canvas常用接口如下表所示,详细的使用和参数说明请见[drawing_canvas.h](../reference/apis-arkgraphics2d/capi-drawing-canvas-h.md)。 22 23| 接口 | 描述 | 24| -------- | -------- | 25| OH_Drawing_Canvas\* OH_Drawing_CanvasCreate (void) | 用于创建一个画布对象。 | 26| void OH_Drawing_CanvasBind (OH_Drawing_Canvas\*, OH_Drawing_Bitmap\*) | 用于将一个位图对象绑定到画布中,使得画布绘制的内容输出到位图中。 | 27| OH_Drawing_Canvas\* OH_Drawing_SurfaceGetCanvas (OH_Drawing_Surface \*) | 通过surface对象获取画布对象。 | 28 29 30## 获取可直接显示的Canvas画布 31 32通过XComponent获取可直接显示的Canvas画布。 33 341. 添加链接库。 35 36 在Native工程的src/main/cpp/CMakeLists.txt,添加如下链接库: 37 38 ```c++ 39 target_link_libraries(entry PUBLIC libnative_drawing.so) 40 ``` 41 422. 导入依赖的相关头文件。 43 44 ```c++ 45 #include <native_drawing/drawing_canvas.h> 46 #include <native_drawing/drawing_surface.h> 47 ``` 48 493. 从XComponent对应的NativeWindow中获取BufferHandle对象。NativeWindow相关的API请参考[_native_window](../reference/apis-arkgraphics2d/capi-nativewindow.md)。 50 51 ```c++ 52 uint64_t width, height; 53 OHNativeWindow *nativeWindow; // NativeWindow及其宽高需要从XComponent获取 54 int32_t usage = NATIVEBUFFER_USAGE_CPU_READ | NATIVEBUFFER_USAGE_CPU_WRITE | NATIVEBUFFER_USAGE_MEM_DMA; 55 int ret = OH_NativeWindow_NativeWindowHandleOpt(nativeWindow, SET_USAGE, usage); 56 if (ret != 0) { 57 return; 58 } 59 60 struct NativeWindowBuffer *buffer = nullptr; 61 int fenceFd = 0; 62 ret = OH_NativeWindow_NativeWindowRequestBuffer(nativeWindow, &buffer, &fenceFd); 63 if (ret != 0) { 64 return; 65 } 66 67 BufferHandle* bufferHandle = OH_NativeWindow_GetBufferHandleFromNative(buffer); 68 ``` 69 704. 从BufferHandle中获取对应的内存地址。 71 72 ```c++ 73 uint32_t* mappedAddr = static_cast<uint32_t *>(mmap(bufferHandle->virAddr, bufferHandle->size, PROT_READ | PROT_WRITE, MAP_SHARED, bufferHandle->fd, 0)); 74 ``` 75 765. 创建窗口画布。 77 78 ```c++ 79 OH_Drawing_Image_Info screenImageInfo = {static_cast<int32_t>(width), static_cast<int32_t>(height), COLOR_FORMAT_RGBA_8888, ALPHA_FORMAT_OPAQUE}; 80 OH_Drawing_Bitmap* screenBitmap = OH_Drawing_BitmapCreateFromPixels(&screenImageInfo, mappedAddr, bufferHandle->stride); 81 OH_Drawing_Canvas* screenCanvas = OH_Drawing_CanvasCreate(); 82 OH_Drawing_CanvasBind(screenCanvas, screenBitmap); 83 ``` 84 856. 利用上一步中得到的Canvas进行自定义的绘制操作,即本章下文中的内容。 86 877. 利用XComponent完成显示。 88 89 ```c++ 90 Region region {nullptr, 0}; 91 OH_NativeWindow_NativeWindowFlushBuffer(nativeWindow, buffer, fenceFd, region); 92 ``` 93 94 95## 离屏Canvas画布的获取与显示 96 97目前有两种创建离屏Canvas的方式:创建CPU后端Canvas、创建GPU后端Canvas,这两种Canvas都需要依靠XComponent来完成绘制结果的上屏显示。由于历史原因,早期的Canvas都是CPU后端Canvas。目前已经支持GPU后端Canvas,GPU的并行计算能力更强,更适合图形绘制。但GPU后端Canvas对部分场景的支持还有欠缺,比如复杂的路径,对于简短文字的绘制性能也比不上CPU后端Canvas。 98 99 100### CPU后端Canvas的创建与显示 101 102目前C/C++接口的绘制需要依赖于NativeWindow,CPU后端Canvas需要先离屏绘制,生成位图或像素图(从API Version 20开始支持),再借助XComponent上屏。 103 104 105方式一:通过绑定位图(Bitmap)的方式创建Canvas。 1061. 导入依赖的相关头文件。 107 108 ```c++ 109 #include <native_drawing/drawing_canvas.h> 110 #include <native_drawing/drawing_bitmap.h> 111 ``` 112 1132. 创建基于CPU的Canvas。需要通过OH_Drawing_BitmapCreate()接口创建一个位图对象(具体可参考[图片绘制](pixelmap-drawing-c.md)),并通过OH_Drawing_CanvasBind()接口将位图绑定到Canvas中,从而使得Canvas绘制的内容可以输出到位图中。 114 115 ```c++ 116 // 创建一个位图对象 117 OH_Drawing_Bitmap* bitmap = OH_Drawing_BitmapCreate(); 118 OH_Drawing_BitmapFormat cFormat{COLOR_FORMAT_RGBA_8888, ALPHA_FORMAT_PREMUL}; 119 // 设置位图长宽(按需设置) 120 uint32_t width = 800; 121 uint32_t height = 800; 122 // 初始化位图 123 OH_Drawing_BitmapBuild(bitmap, width, height, &cFormat); 124 // 创建一个Canvas对象 125 OH_Drawing_Canvas* bitmapCanvas = OH_Drawing_CanvasCreate(); 126 // 将Canvas与bitmap绑定,Canvas绘制的内容会输出到绑定的bitmap内存中 127 OH_Drawing_CanvasBind(bitmapCanvas, bitmap); 128 ``` 129 130 如果需要将背景设置为白色,需要执行以下步骤: 131 132 ```c++ 133 OH_Drawing_CanvasClear(bitmapCanvas, OH_Drawing_ColorSetArgb(0xFF, 0xFF, 0xFF, 0xFF)); 134 ``` 135 1363. 将上一步中创建的位图绘制到[窗口画布](#获取可直接显示的canvas画布)上。 137 138 ```c++ 139 OH_Drawing_CanvasDrawBitmap(screenCanvas, bitmap, 0, 0); 140 ``` 141 142 143方式二:通过像素图(PixelMap)创建Canvas。从API Version 20开始,支持使用此种方式创建Canvas。 144像素图是系统中用来表示图片的统一的数据结构,相比于drawing模块中提供的位图,像素图具备通用性,并且能够更好地发挥系统的能力。 145 1461. 添加链接库。 147 148 在Native工程的src/main/cpp/CMakeLists.txt,添加如下链接库: 149 150 ```c++ 151 target_link_libraries(entry PUBLIC libhilog_ndk.z.so libpixelmap.so) 152 ``` 153 1542. 导入依赖的相关头文件。 155 156 ```c++ 157 #include <multimedia/image_framework/image/pixelmap_native.h> 158 #include <native_drawing/drawing_pixel_map.h> 159 ``` 160 1613. 需要通过OH_Drawing_PixelMapGetFromOhPixelMapNative()接口创建一个像素图对象(具体可参考[图片绘制](pixelmap-drawing-c.md)),并通过OH_Drawing_CanvasCreateWithPixelMap()接口借助像素图对象创建Canvas。 162 163 ```c++ 164 // 图片宽高 165 uint32_t width = 600; 166 uint32_t height = 400; 167 // 设置位图格式(长、宽、颜色类型、透明度类型) 168 OH_Pixelmap_InitializationOptions *createOps = nullptr; 169 OH_PixelmapInitializationOptions_Create(&createOps); 170 OH_PixelmapInitializationOptions_SetWidth(createOps, width); 171 OH_PixelmapInitializationOptions_SetHeight(createOps, height); 172 OH_PixelmapInitializationOptions_SetPixelFormat(createOps, PIXEL_FORMAT_RGBA_8888); 173 OH_PixelmapInitializationOptions_SetAlphaType(createOps, PIXELMAP_ALPHA_TYPE_UNKNOWN); 174 // 字节长度,RGBA_8888每个像素占4字节 175 size_t bufferSize = width * height * 4; 176 void *buffer = malloc(bufferSize); 177 // 创建OH_PixelmapNative对象 178 OH_PixelmapNative *pixelMapNative = nullptr; 179 OH_PixelmapNative_CreatePixelmap(static_cast<uint8_t *>(buffer), bufferSize, createOps, &pixelMapNative); 180 // 创建Pixelmap对象 181 OH_Drawing_PixelMap *pixelMap = OH_Drawing_PixelMapGetFromOhPixelMapNative(pixelMapNative); 182 // 创建Canvas对象 183 OH_Drawing_Canvas* pixelmapCanvas = OH_Drawing_CanvasCreateWithPixelMap(pixelMap); 184 ``` 185 186 如果需要将背景设置为白色,需要执行以下步骤: 187 188 ```c++ 189 OH_Drawing_CanvasClear(pixelmapCanvas, OH_Drawing_ColorSetArgb(0xFF, 0xFF, 0xFF, 0xFF)); 190 ``` 191 1924. 将上一步中创建的像素图绘制到[窗口画布](#获取可直接显示的canvas画布)上。 193 194 ```c++ 195 // PixelMap中像素的截取区域 196 OH_Drawing_Rect *src = OH_Drawing_RectCreate(0, 0, width, height); 197 // 画布中显示的区域 198 OH_Drawing_Rect *dst = OH_Drawing_RectCreate(0, 0, width, height); 199 // 采样选项对象 200 OH_Drawing_SamplingOptions* samplingOptions = OH_Drawing_SamplingOptionsCreate( 201 OH_Drawing_FilterMode::FILTER_MODE_LINEAR, OH_Drawing_MipmapMode::MIPMAP_MODE_LINEAR); 202 // 绘制PixelMap 203 OH_Drawing_CanvasDrawPixelMapRect(screenCanvas, pixelMap, src, dst, samplingOptions); 204 ``` 205 206 207### GPU后端Canvas的创建与显示 208 209GPU后端Canvas指画布是基于GPU进行绘制的,GPU的并行计算能力优于CPU,适用于绘制图片或区域相对大的场景,但目前GPU后端的Canvas针对绘制复杂路径的能力还有欠缺。同CPU后端Canvas,目前C/C++接口的绘制需要依赖于XComponent,GPU后端Canvas需要先离屏绘制再借助XComponent上屏。 210 2111. 当前创建GPU后端的Canvas依赖EGL的能力,需要在CMakeList.txt中添加EGL的动态依赖库。 212 213 ```c++ 214 libEGL.so 215 ``` 216 2172. 导入依赖的头文件。 218 219 ```c++ 220 #include <EGL/egl.h> 221 #include <EGL/eglext.h> 222 #include <native_drawing/drawing_gpu_context.h> 223 #include <native_drawing/drawing_surface.h> 224 ``` 2253. 初始化EGL上下文。 226 227 ```c++ 228 // 初始化上下文相关参数 229 EGLDisplay mEGLDisplay = EGL_NO_DISPLAY; 230 EGLConfig mEGLConfig = nullptr; 231 EGLContext mEGLContext = EGL_NO_CONTEXT; 232 EGLSurface mEGLSurface = nullptr; 233 ``` 234 235 ```c++ 236 // 初始化上下文相关配置 237 EGLConfig getConfig(int version, EGLDisplay eglDisplay) 238 { 239 int attribList[] = {EGL_SURFACE_TYPE, 240 EGL_WINDOW_BIT, 241 EGL_RED_SIZE, 242 8, 243 EGL_GREEN_SIZE, 244 8, 245 EGL_BLUE_SIZE, 246 8, 247 EGL_ALPHA_SIZE, 248 8, 249 EGL_RENDERABLE_TYPE, 250 EGL_OPENGL_ES2_BIT, 251 EGL_NONE}; 252 EGLConfig configs = NULL; 253 int configsNum; 254 255 if (!eglChooseConfig(eglDisplay, attribList, &configs, 1, &configsNum)) { 256 return NULL; 257 } 258 259 return configs; 260 } 261 262 // 在需要初始化EGL上下文处调用InitializeEglContext 263 int32_t InitializeEglContext(EGLDisplay mEGLDisplay, EGLConfig mEGLConfig, 264 EGLContext mEGLContext, EGLSurface mEGLSurface) 265 { 266 mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); 267 if (mEGLDisplay == EGL_NO_DISPLAY) { 268 return -1; 269 } 270 271 EGLint eglMajVers; 272 EGLint eglMinVers; 273 if (!eglInitialize(mEGLDisplay, &eglMajVers, &eglMinVers)) { 274 mEGLDisplay = EGL_NO_DISPLAY; 275 return -1; 276 } 277 278 int version = 3; 279 mEGLConfig = getConfig(version, mEGLDisplay); 280 if (mEGLConfig == nullptr) { 281 return -1; 282 } 283 284 int attribList[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; 285 286 mEGLContext = eglCreateContext(mEGLDisplay, mEGLConfig, EGL_NO_CONTEXT, attribList); 287 288 EGLint attribs[] = {EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE}; 289 mEGLSurface = eglCreatePbufferSurface(mEGLDisplay, mEGLConfig, attribs); 290 if (!eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) { 291 return -1; 292 } 293 294 return 0; 295 } 296 ``` 297 2984. 创建GPU后端Canvas。GPU后端Canvas需要借助Surface对象来获取,需先创建surface,surface的API请参考[drawing_surface.h](../reference/apis-arkgraphics2d/capi-drawing-surface-h.md)。通过OH_Drawing_GpuContextCreateFromGL接口创建绘图上下文,再将这个上下文作为参数创建surface,最后通过OH_Drawing_SurfaceGetCanvas接口从surface中获取到canvas。 299 300 ```c++ 301 // 设置宽高(按需设定) 302 int32_t cWidth = 800; 303 int32_t cHeight = 800; 304 // 设置图像,包括宽度、高度、颜色格式和透明度格式 305 OH_Drawing_Image_Info imageInfo = {cWidth, cHeight, COLOR_FORMAT_RGBA_8888, ALPHA_FORMAT_PREMUL}; 306 // GPU上下文的选项 307 OH_Drawing_GpuContextOptions options{false}; 308 // 创建一个使用OpenGL(GL)作为其GPU后端的绘图上下文 309 OH_Drawing_GpuContext *gpuContext = OH_Drawing_GpuContextCreateFromGL(options); 310 // 创建surface对象 311 OH_Drawing_Surface *surface = OH_Drawing_SurfaceCreateFromGpuContext(gpuContext, true, imageInfo); 312 // 创建一个canvas对象 313 OH_Drawing_Canvas* gpuCanvas = OH_Drawing_SurfaceGetCanvas(surface); 314 ``` 315 316 如果需要将背景设置为白色,需要执行以下步骤: 317 318 ```c++ 319 OH_Drawing_CanvasClear(gpuCanvas, OH_Drawing_ColorSetArgb(0xFF, 0xFF, 0xFF, 0xFF)); 320 ``` 321 3225. 将上一步中的绘制结果拷贝到[窗口画布](#获取可直接显示的canvas画布)上。 323 324 ```c++ 325 void* dstPixels = malloc(cWidth * cHeight * 4); // 4 for rgba 326 OH_Drawing_CanvasReadPixels(gpuCanvas, &imageInfo, dstPixels, 4 * cWidth, 0, 0); 327 OH_Drawing_Bitmap* bitmap = OH_Drawing_BitmapCreateFromPixels(&imageInfo, dstPixels, 4 * cWidth); 328 OH_Drawing_CanvasDrawBitmap(screenCanvas, bitmap, 0, 0); 329 ``` 330 3316. 使用完之后需要将EGL上下文销毁。 332 333 ```c++ 334 // 在需要销毁处调用DeInitializeEglContext销毁EGL上下文。 335 void DeInitializeEglContext(EGLDisplay mEGLDisplay, EGLContext mEGLContext, EGLSurface mEGLSurface) 336 { 337 // 以下三个方法都有返回值判断是否销毁成功,必要时可进行调试。 338 eglDestroySurface(mEGLDisplay, mEGLSurface); 339 eglDestroyContext(mEGLDisplay, mEGLContext); 340 eglTerminate(mEGLDisplay); 341 342 mEGLSurface = NULL; 343 mEGLContext = NULL; 344 mEGLDisplay = NULL; 345 } 346 ``` 347 348<!--RP1--> 349## 相关实例 350 351针对Drawing(C/C++)的开发,有以下相关实例可供参考: 352 353- [NDKGraphicsDraw (API14)](https://gitcode.com/openharmony/applications_app_samples/tree/master/code/DocsSample/Drawing/NDKGraphicsDraw) 354<!--RP1End-->