离屏获取文本像素图并使用OpenGL绘制
====================

#### 问题现象

在音视频应用中，当需要在视频画面上叠加实时字幕，或在直播场景下实现高速滚动的弹幕时，使用OpenGL绘制文字能实现流畅低延时的渲染效果。在HarmonyOS中，如何获取文本像素数据并使用OpenGL绘制文字？

#### 背景知识

*   [EGL](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/egl)是OpenGL与本地窗口系统之间的接口，将OpenGL的渲染输出连接到HarmonyOS的显示窗口。
*   [OpenGL](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/opengles)不能直接绘制文字，而是先将文字渲染成纹理（图片），再将纹理贴到指定的矩形方块中显示。
    *   [glTexImage2D](https://developer.huawei.com/consumer/cn/doc/graphics-References/glteximage-0000001050170287)接口用于加载图像像素数据创建二维纹理对象。
*   [OffscreenCanvas](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-components-offscreencanvas)是一个可以在屏幕外渲染的画布，可以离屏绘制文本。
    *   [fillText](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-offscreencanvasrenderingcontext2d#filltext)接口用于在画布指定位置绘制填充类文本内容。
    *   [getPixelMap](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-offscreencanvasrenderingcontext2d#getpixelmap)接口用于拷贝画布指定区域的像素数据。
*   [Drawing](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-drawing)模块支持创建[CPU后端](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/canvas-get-result-draw-c#cpu后端canvas的创建与显示)或[GPU后端](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/canvas-get-result-draw-c#gpu后端canvas的创建与显示)的离屏画布，可以离屏绘制文本。
    *   [OH\_Drawing\_CanvasDrawSingleCharacter](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-drawing-canvas-h#oh_drawing_canvasdrawsinglecharacter)用于绘制单个字符，当前字型中的字体不支持待绘制字符时，退化到使用系统字体绘制字符。
    *   [OH\_Drawing\_CanvasReadPixels](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-drawing-canvas-h#oh_drawing_canvasreadpixels)接口从画布中拷贝像素数据到指定地址。

#### 解决方案

使用OpenGL绘制文本，首先需要获取绘制文本的像素数据，获得文本像素图数据后，OpenGL可以根据文本像素数据生成纹理对象，并将纹理对象渲染到指定的矩形方块上显示在屏幕上。在HarmonyOS中支持通过OffscreenCanvas以及Drawing模块离屏绘制文本内容并获取文本绘制的图像像素数据。

*   **方案一**：使用OffscreenCanvas离屏绘制文本取得文本像素数据。
    1.  ArkTS侧，创建OffscreenCanvas组件，使用OffscreenCanvasRenderingContext2D在OffscreenCanvas上绘制文本内容。拷贝离屏画布文本绘制区域的像素数据，与图像宽、高以及XComponent的SurfaceID一同传递到Native侧绘制。
        
        XComponent({ type: XComponentType.SURFACE, controller: this.xController3 })
          .width('100%')
          .aspectRatio(4)
          .backgroundColor(Color.Yellow)
          .onLoad(async () \=\> {
           _ // 离屏绘制文本_
            let text: string \= '你好\\u{D83D}\\u{DE02}';
            let offCanvas: OffscreenCanvas \= new OffscreenCanvas(300, 100);
            let offContext \= offCanvas.getContext('2d');
            offContext.fillStyle \= '#000000';
            offContext.font \= '100px sans-serif';
            offContext.fillText(text, 0, 50);
          _  // 从离屏画布上读取位图数据。_
            this.pixel \= offContext.getPixelMap(0, 0, 300, 100);
            let buffer \= new ArrayBuffer(this.pixel.getPixelBytesNumber());
            await this.pixel.readPixelsToBuffer(buffer);
          _  // 获取位图的宽、高信息。_
            let imgInfo \= await this.pixel.getImageInfo();
            let imgWidth \= imgInfo.size.width;
            let imgHeight \= imgInfo.size.height;
          _  // 获取XComponent的SurfaceID。_
            let surfaceId \= this.xController3.getXComponentSurfaceId();
           _ // 将位图数据、宽、高，SurfaceID传递到Native侧使用OpenGL ES完成绘制。_
            testNapi.drawText(BigInt(surfaceId), buffer, imgWidth, imgHeight);
          });
        
    2.  在Native侧接收位图像素数据，位图宽、高以及XComponent的SurfaceID。
        
        static napi\_value DrawImage(napi\_env env, napi\_callback\_info info)
        {
            size\_t argc \= 5;
            napi\_value args\[5\] \= {nullptr};
            napi\_get\_cb\_info(env, info, &argc, args, nullptr, nullptr);
          _  // 获取SurfaceID_
            bool lossless \= true;
            uint64\_t surfaceId \= 0;
            napi\_get\_value\_bigint\_uint64(env, args\[0\], &surfaceId, &lossless);
           _ // 获取位图数据_
            void \*data \= nullptr;
            size\_t byteLength \= 0;
            napi\_get\_arraybuffer\_info(env, args\[1\], &data, &byteLength);
           _ // 获取位图宽、高_
            int32\_t imageWidth \= 0;
            int32\_t imageHeight \= 0;
            napi\_get\_value\_int32(env, args\[2\], &imageWidth);
            napi\_get\_value\_int32(env, args\[3\], &imageHeight);
           _ // 创建NativeWindow对象_
            OHNativeWindow \*window \= nullptr;
            OH\_NativeWindow\_CreateNativeWindowFromSurfaceId(surfaceId, &window);
          _  // 使用OpenGL ES绘制位图_
            GLDraw(window, imageWidth, imageHeight, data);
         _   // 销毁NativeWindow_
            OH\_NativeWindow\_DestroyNativeWindow(window);
            return nullptr;
        }
        
    3.  使用OpenGL加载图像像素数据生成纹理，将纹理渲染到2D矩形区域内完成文本绘制。
        
        static void GLDraw(OHNativeWindow \*window, int32\_t width, int32\_t height, void \*data)
        {
            EGLDisplay display;
            EGLint majorVersion;
            EGLint minorVersion;
            display \= eglGetDisplay(EGL\_DEFAULT\_DISPLAY);
            eglInitialize(display, &majorVersion, &minorVersion);
            EGLConfig config;
            EGLint numConfigs;
            EGLint attribs\[\] \= {
                EGL\_SURFACE\_TYPE,
                EGL\_WINDOW\_BIT,
                EGL\_RENDERABLE\_TYPE,
                EGL\_OPENGL\_ES3\_BIT,
                EGL\_BLUE\_SIZE,
                8,
                EGL\_GREEN\_SIZE,
                8,
                EGL\_RED\_SIZE,
                8,
                EGL\_ALPHA\_SIZE,
                8,
                EGL\_NONE,
            };
            eglChooseConfig(display, attribs, &config, 1, &numConfigs);
            EGLSurface surface;
            EGLContext context;
            EGLint contextAttribs\[\] \= {EGL\_CONTEXT\_CLIENT\_VERSION, 3, EGL\_NONE};
            surface \= eglCreateWindowSurface(display, config, (EGLNativeWindowType)window, NULL);
            context \= eglCreateContext(display, config, EGL\_NO\_CONTEXT, contextAttribs);
            eglMakeCurrent(display, surface, surface, context);
            glViewport(0, 0, width, height);
            glClearColor(1.0f, 1.0f, 0.0f, 1.0f);
            glClear(GL\_COLOR\_BUFFER\_BIT);
            GLfloat vertices\[\] \= {
                _// First triangle_
                1.0f, 1.0f, 0.0f, 1.0f, 0.0f,  _ // ..._
                1.0f, \-1.0f, 0.0f, 1.0f, 1.0f,  _// ..._
                \-1.0f, \-1.0f, 0.0f, 0.0f, 1.0f, _// ..._
              _  // Second triangle_
                1.0f, 1.0f, 0.0f, 1.0f, 0.0f,   _// ..._
                \-1.0f, \-1.0f, 0.0f, 0.0f, 1.0f, _// ..._
                \-1.0f, 1.0f, 0.0f, 0.0f, 0.0f,  _// ..._
            };
            GLuint vbo;
            GLuint vao\[0\];
            glGenVertexArrays(1, vao);
            glBindVertexArray(vao\[0\]);
            glGenBuffers(1, &vbo);
            glBindBuffer(GL\_ARRAY\_BUFFER, vbo);
            glBufferData(GL\_ARRAY\_BUFFER, sizeof(vertices), vertices, GL\_STATIC\_DRAW);
            const char \*vertexShaderSource \= R"(#version 300 es
                layout (location = 0) in vec4 vPosition;
                layout (location = 1) in vec2 vTexCoord;
                out vec2 TexCoord;
                void main() {
                    gl\_Position = vPosition;
                    TexCoord = vTexCoord;
                }
            )";
            const char \*fragmentShaderSource \= R"(#version 300 es
                precision mediump float;
                out vec4 FragColor;
                in vec2 TexCoord;
                uniform sampler2D testTexture;
                void main() {
                    FragColor = texture(testTexture, TexCoord);
                }
            )";
            GLuint vertexShader;
            vertexShader \= glCreateShader(GL\_VERTEX\_SHADER);
            glShaderSource(vertexShader, 1, &vertexShaderSource, nullptr);
            glCompileShader(vertexShader);
            GLuint fragmentShader;
            fragmentShader \= glCreateShader(GL\_FRAGMENT\_SHADER);
            glShaderSource(fragmentShader, 1, &fragmentShaderSource, nullptr);
            glCompileShader(fragmentShader);
            GLuint shaderProgram;
            shaderProgram \= glCreateProgram();
            glAttachShader(shaderProgram, vertexShader);
            glAttachShader(shaderProgram, fragmentShader);
            glLinkProgram(shaderProgram);
            glUseProgram(shaderProgram);
            GLuint textureId;
            glGenTextures(1, &textureId);
            glBindTexture(GL\_TEXTURE\_2D, textureId);
            glTexParameteri(GL\_TEXTURE\_2D, GL\_TEXTURE\_WRAP\_S, GL\_REPEAT);
            glTexParameteri(GL\_TEXTURE\_2D, GL\_TEXTURE\_WRAP\_T, GL\_REPEAT);
            glTexParameteri(GL\_TEXTURE\_2D, GL\_TEXTURE\_MIN\_FILTER, GL\_LINEAR);
            glTexParameteri(GL\_TEXTURE\_2D, GL\_TEXTURE\_MAG\_FILTER, GL\_LINEAR);
            glTexImage2D(GL\_TEXTURE\_2D, 0, GL\_RGBA, width, height, 0, GL\_RGBA, GL\_UNSIGNED\_BYTE, data);
            glGenerateMipmap(GL\_TEXTURE\_2D);
            glUniform1i(glGetUniformLocation(shaderProgram, "testTexture"), 0);
            glActiveTexture(GL\_TEXTURE0);
            glBindTexture(GL\_TEXTURE\_2D, textureId);
            glVertexAttribPointer(0, 3, GL\_FLOAT, GL\_FALSE, 5 \* sizeof(GLfloat), (GLvoid \*)0);
            glEnableVertexAttribArray(0);
            glVertexAttribPointer(1, 2, GL\_FLOAT, GL\_FALSE, 5 \* sizeof(GLfloat), (GLvoid \*)(3 \* sizeof(GLfloat)));
            glEnableVertexAttribArray(1);
            glDrawArrays(GL\_TRIANGLES, 0, 6);
            eglSwapBuffers(display, surface);
            glDeleteShader(vertexShader);
            glDeleteShader(fragmentShader);
            glDeleteBuffers(1, &vbo);
            std::this\_thread::sleep\_for(std::chrono::milliseconds(50));
            eglMakeCurrent(display, EGL\_NO\_SURFACE, EGL\_NO\_SURFACE, EGL\_NO\_CONTEXT);
            eglDestroySurface(display, surface);
            eglDestroyContext(display, context);
            eglTerminate(display);
        }
        
*   **方案二**：使用CPU后端的离屏画布绘制文本取得文本像素数据。
    1.  在ArkTS侧，创建XComponent用作OpenGL ES绘制窗口，将SurfaceID传递到Native侧。
        
        XComponent({ type: XComponentType.SURFACE, controller: this.xController1 })
          .width('100%')
          .aspectRatio(4)
          .backgroundColor(Color.Yellow)
          .onLoad(() \=\> {
            let surfaceId \= this.xController1.getXComponentSurfaceId();
            testNapi.nativeDrawCpu(BigInt(surfaceId));
          });
        
    2.  创建CPU后端的离屏画布，使用单字绘制接口逐个绘制文字字符（单字绘制能够利用字体退化机制，提升对特殊字符的兼容性）。拷贝离屏画布文本绘制区域的像素数据，用于OpenGL生成纹理对象。
        
        static napi\_value NativeDrawCpu(napi\_env env, napi\_callback\_info info)
        {
            int32\_t width \= 900;
            int32\_t height \= 300;
          _  // 创建位图对象_
            OH\_Drawing\_Bitmap \*bitmap \= OH\_Drawing\_BitmapCreate();
            OH\_Drawing\_BitmapFormat cFormat{COLOR\_FORMAT\_BGRA\_8888, ALPHA\_FORMAT\_PREMUL};
         _   // 初始化位图_
            OH\_Drawing\_BitmapBuild(bitmap, width, height, &cFormat);
          _  // 创建Canvas对象_
            OH\_Drawing\_Canvas \*bitmapCanvas \= OH\_Drawing\_CanvasCreate();
          _  // 将Canvas与位图绑定，Canvas绘制的内容会输出到绑定的bitmap内存中_
            OH\_Drawing\_CanvasBind(bitmapCanvas, bitmap);
          _  // 绘制字块_
            char text\[\] \= "你好\\xF0\\x9F\\x98\\x82";
            OH\_Drawing\_Font \*font \= OH\_Drawing\_FontCreate();
            OH\_Drawing\_FontSetTextSize(font, 100);
            float posX \= 0;
            float posY \= 150;
            for (int32\_t idx \= 0; idx < 2; idx++) {
                float textWidth \= 0.0f;
                OH\_Drawing\_CanvasDrawSingleCharacter(bitmapCanvas, &text\[idx \* 3\], font, posX, posY);
                OH\_Drawing\_FontMeasureSingleCharacter(font, &text\[idx \* 3\], &textWidth);
                posX += textWidth;
            }
            for (int32\_t idx \= 0; idx < 1; idx++) {
                float textWidth \= 0.0f;
                OH\_Drawing\_CanvasDrawSingleCharacter(bitmapCanvas, &text\[idx \* 4 + 6\], font, posX, posY);
                OH\_Drawing\_FontMeasureSingleCharacter(font, &text\[idx \* 4 + 6\], &textWidth);
                posX += textWidth;
            }
            OH\_Drawing\_FontDestroy(font);
           _ // 从Canvas上拷贝绘制结果位图数据_
            std::unique\_ptr<uint8\_t\> dstPixels(new uint8\_t\[width \* height \* 4\]);
            OH\_Drawing\_Image\_Info imageInfo \= {width, height, COLOR\_FORMAT\_RGBA\_8888, ALPHA\_FORMAT\_PREMUL};
            OH\_Drawing\_CanvasReadPixels(bitmapCanvas, &imageInfo, dstPixels.get(), 4 \* width, 0, 0);
          _  // 清理资源_
            OH\_Drawing\_CanvasDestroy(bitmapCanvas);
            OH\_Drawing\_BitmapDestroy(bitmap);
            size\_t argc \= 1;
            napi\_value args\[1\] \= {nullptr};
            napi\_get\_cb\_info(env, info, &argc, args, nullptr, nullptr);
          _  // 获取XComponent的SurfaceID_
            bool lossless \= true;
            uint64\_t surfaceId \= 0;
            napi\_get\_value\_bigint\_uint64(env, args\[0\], &surfaceId, &lossless);
         _   // 通过SurfaceID创建NativeWindow对象_
            OHNativeWindow \*window \= nullptr;
            OH\_NativeWindow\_CreateNativeWindowFromSurfaceId(surfaceId, &window);
          _  // 通过OpenGL ES绘制图像_
            GLDraw(window, width, height, dstPixels.get());
          _  // 销毁NativeWindow_
            OH\_NativeWindow\_DestroyNativeWindow(window);
            return nullptr;
        }
        
    3.  使用OpenGL加载图像像素数据生成纹理，将纹理渲染到2D矩形区域内完成文本绘制。同方案一步骤三。
*   **方案三**：使用GPU后端的离屏画布绘制文本取得文本像素数据。
    1.  在ArkTS侧，创建XComponent用作OpenGL ES绘制窗口，将SurfaceID传递到Native侧。
        
        XComponent({ type: XComponentType.SURFACE, controller: this.xController2 })
          .width('100%')
          .aspectRatio(4)
          .backgroundColor(Color.Yellow)
          .onLoad(() \=\> {
            let surfaceId \= this.xController2.getXComponentSurfaceId();
            testNapi.nativeDrawGpu(BigInt(surfaceId));
          });
        
    2.  初始化EGL上下文。创建GPU后端离屏画布。在离屏画布上，使用单字绘制接口逐个绘制文字字符。拷贝离屏画布文本绘制区域的像素数据，用于OpenGL生成纹理对象。
        
        static napi\_value NativeDrawGpu(napi\_env env, napi\_callback\_info info)
        {
           _ // 初始化EGL上下文_
            EGLDisplay bufDisplay;
            EGLConfig bufConfig;
            EGLSurface bufSurface;
            EGLContext bufContext;
            EGLint majorVersion;
            EGLint minorVersion;
            bufDisplay \= eglGetDisplay(EGL\_DEFAULT\_DISPLAY);
            eglInitialize(bufDisplay, &majorVersion, &minorVersion);
            EGLint numConfigs;
            EGLint attribs\[\] \= {
                EGL\_SURFACE\_TYPE,
                EGL\_WINDOW\_BIT,
                EGL\_RENDERABLE\_TYPE,
                EGL\_OPENGL\_ES3\_BIT,
                EGL\_BLUE\_SIZE,
                8,
                EGL\_GREEN\_SIZE,
                8,
                EGL\_RED\_SIZE,
                8,
                EGL\_ALPHA\_SIZE,
                8,
                EGL\_NONE,
            };
            eglChooseConfig(bufDisplay, attribs, &bufConfig, 1, &numConfigs);
            EGLint contextAttribs\[\] \= {EGL\_CONTEXT\_CLIENT\_VERSION, 3, EGL\_NONE};
            bufSurface \= eglCreatePbufferSurface(bufDisplay, bufConfig, attribs);
            bufContext \= eglCreateContext(bufDisplay, bufConfig, EGL\_NO\_CONTEXT, contextAttribs);
            eglMakeCurrent(bufDisplay, bufSurface, bufSurface, bufContext);
           _ // 设置宽高（按需设定）_
            int32\_t width \= 900;
            int32\_t height \= 300;
           _ // 设置图像宽、高、颜色格式和透明度格式_
            OH\_Drawing\_Image\_Info imageInfo \= {width, height, COLOR\_FORMAT\_RGBA\_8888, ALPHA\_FORMAT\_PREMUL};
         _   // 创建GPU后端的绘图上下文_
            OH\_Drawing\_GpuContext \*gpuContext \= OH\_Drawing\_GpuContextCreate();
         _   // 创建Surface对象_
            OH\_Drawing\_Surface \*drawSurface \= OH\_Drawing\_SurfaceCreateFromGpuContext(gpuContext, true, imageInfo);
         _   // 创建Canvas对象_
            OH\_Drawing\_Canvas \*gpuCanvas \= OH\_Drawing\_SurfaceGetCanvas(drawSurface);
         _   // 绘制字块_
            char text\[\] \= "你好\\xF0\\x9F\\x98\\x82";
            OH\_Drawing\_Font \*font \= OH\_Drawing\_FontCreate();
            OH\_Drawing\_FontSetTextSize(font, 100);
            float posX \= 0;
            float posY \= 150;
            for (int32\_t idx \= 0; idx < 2; idx++) {
                float textWidth \= 0.0f;
                OH\_Drawing\_CanvasDrawSingleCharacter(gpuCanvas, &text\[idx \* 3\], font, posX, posY);
                OH\_Drawing\_FontMeasureSingleCharacter(font, &text\[idx \* 3\], &textWidth);
                posX += textWidth;
            }
            for (int32\_t idx \= 0; idx < 1; idx++) {
                float textWidth \= 0.0f;
                OH\_Drawing\_CanvasDrawSingleCharacter(gpuCanvas, &text\[idx \* 4 + 6\], font, posX, posY);
                OH\_Drawing\_FontMeasureSingleCharacter(font, &text\[idx \* 4 + 6\], &textWidth);
                posX += textWidth;
            }
            OH\_Drawing\_FontDestroy(font);
          _  // 从Canvas 上拷贝绘制结果位图数据_
            std::unique\_ptr<uint8\_t\> dstPixels(new uint8\_t\[width \* height \* 4\]);
            OH\_Drawing\_CanvasReadPixels(gpuCanvas, &imageInfo, dstPixels.get(), 4 \* width, 0, 0);
          _  // 清理资源_
            OH\_Drawing\_CanvasDestroy(gpuCanvas);
          _  // 清理EGL_
            eglDestroySurface(bufDisplay, bufSurface);
            eglDestroyContext(bufDisplay, bufContext);
            eglTerminate(bufDisplay);
            size\_t argc \= 1;
            napi\_value args\[1\] \= {nullptr};
            napi\_get\_cb\_info(env, info, &argc, args, nullptr, nullptr);
            _// 获取XComponent的SurfaceID_
            bool lossless \= true;
            uint64\_t surfaceId \= 0;
            napi\_get\_value\_bigint\_uint64(env, args\[0\], &surfaceId, &lossless);
           _ // 通过SurfaceID创建NativeWindow对象_
            OHNativeWindow \*window \= nullptr;
            OH\_NativeWindow\_CreateNativeWindowFromSurfaceId(surfaceId, &window);
          _  // 通过OpenGL ES绘制图像_
            GLDraw(window, width, height, dstPixels.get());
           _ // 销毁NativeWindow_
            OH\_NativeWindow\_DestroyNativeWindow(window);
            return nullptr;
        }
        
    3.  使用OpenGL加载图像像素数据生成纹理，将纹理渲染到2D矩形区域内完成文本绘制。同方案一步骤三。

完整示例参考如下：

*   ArkTS侧：
    
    import testNapi from 'libentry.so';
    import image from '@ohos.multimedia.image';
    
    @Entry
    @Component
    struct Index {
      private xController1 \= new XComponentController();
      private xController2 \= new XComponentController();
      private xController3 \= new XComponentController();
      private pixel?: image.PixelMap \= undefined;
    
      build() {
        Column({ space: 20 }) {
          Column({ space: 5 }) {
            Text('CPU Canvas');
            XComponent({ type: XComponentType.SURFACE, controller: this.xController1 })
              .width('100%')
              .aspectRatio(4)
              .backgroundColor(Color.Yellow)
              .onLoad(() \=\> {
                let surfaceId \= this.xController1.getXComponentSurfaceId();
                testNapi.nativeDrawCpu(BigInt(surfaceId));
              });
          };
    
          Column({ space: 5 }) {
            Text('GPU Canvas');
            XComponent({ type: XComponentType.SURFACE, controller: this.xController2 })
              .width('100%')
              .aspectRatio(4)
              .backgroundColor(Color.Yellow)
              .onLoad(() \=\> {
                let surfaceId \= this.xController2.getXComponentSurfaceId();
                testNapi.nativeDrawGpu(BigInt(surfaceId));
              });
          };
    
          Column({ space: 5 }) {
            Text('OffScreenCanvas');
            XComponent({ type: XComponentType.SURFACE, controller: this.xController3 })
              .width('100%')
              .aspectRatio(4)
              .backgroundColor(Color.Yellow)
              .onLoad(async () \=\> {
            _    // 离屏绘制文本_
                let text: string \= '你好\\u{D83D}\\u{DE02}';
                let offCanvas: OffscreenCanvas \= new OffscreenCanvas(300, 100);
                let offContext \= offCanvas.getContext('2d');
                offContext.fillStyle \= '#000000';
                offContext.font \= '100px sans-serif';
                offContext.fillText(text, 0, 50);
           _     // 从离屏画布上读取位图数据。_
                this.pixel \= offContext.getPixelMap(0, 0, 300, 100);
                let buffer \= new ArrayBuffer(this.pixel.getPixelBytesNumber());
                await this.pixel.readPixelsToBuffer(buffer);
              _  // 获取位图的宽、高信息。_
                let imgInfo \= await this.pixel.getImageInfo();
                let imgWidth \= imgInfo.size.width;
                let imgHeight \= imgInfo.size.height;
            _    // 获取XComponent的SurfaceID。_
                let surfaceId \= this.xController3.getXComponentSurfaceId();
                _// 将位图数据、宽、高，SurfaceID传递到Native侧使用OpenGL ES完成绘制。_
                testNapi.drawText(BigInt(surfaceId), buffer, imgWidth, imgHeight);
              });
          };
        }
        .width('100%')
        .height('100%')
        .justifyContent(FlexAlign.Center)
        .alignItems(HorizontalAlign.Center);
      }
    }
    
*   Native侧：
    
    _/\*_
     _\* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved._
     _\*/_
    #include "napi/native\_api.h"
    #include <EGL/egl.h\>
    #include <EGL/eglext.h\>
    #include <EGL/eglplatform.h\>
    #include <GLES3/gl3.h\>
    #include <ace/xcomponent/native\_interface\_xcomponent.h\>
    #include <cstdint\>
    #include <native\_drawing/drawing\_bitmap.h\>
    #include <native\_drawing/drawing\_canvas.h\>
    #include <native\_drawing/drawing\_color.h\>
    #include <native\_drawing/drawing\_font.h\>
    #include <native\_drawing/drawing\_gpu\_context.h\>
    #include <native\_drawing/drawing\_surface.h\>
    #include <native\_drawing/drawing\_text\_blob.h\>
    #include <native\_window/external\_window.h\>
    #include <thread\>
    #undef LOG\_DOMAIN
    #undef LOG\_TAG
    #define LOG\_DOMAIN 0x3200
    #define LOG\_TAG "GL\_Image"
    static void GLDraw(OHNativeWindow \*window, int32\_t width, int32\_t height, void \*data)
    {
        EGLDisplay display;
        EGLint majorVersion;
        EGLint minorVersion;
        display \= eglGetDisplay(EGL\_DEFAULT\_DISPLAY);
        eglInitialize(display, &majorVersion, &minorVersion);
        EGLConfig config;
        EGLint numConfigs;
        EGLint attribs\[\] \= {
            EGL\_SURFACE\_TYPE,
            EGL\_WINDOW\_BIT,
            EGL\_RENDERABLE\_TYPE,
            EGL\_OPENGL\_ES3\_BIT,
            EGL\_BLUE\_SIZE,
            8,
            EGL\_GREEN\_SIZE,
            8,
            EGL\_RED\_SIZE,
            8,
            EGL\_ALPHA\_SIZE,
            8,
            EGL\_NONE,
        };
        eglChooseConfig(display, attribs, &config, 1, &numConfigs);
        EGLSurface surface;
        EGLContext context;
        EGLint contextAttribs\[\] \= {EGL\_CONTEXT\_CLIENT\_VERSION, 3, EGL\_NONE};
        surface \= eglCreateWindowSurface(display, config, (EGLNativeWindowType)window, NULL);
        context \= eglCreateContext(display, config, EGL\_NO\_CONTEXT, contextAttribs);
        eglMakeCurrent(display, surface, surface, context);
        glViewport(0, 0, width, height);
        glClearColor(1.0f, 1.0f, 0.0f, 1.0f);
        glClear(GL\_COLOR\_BUFFER\_BIT);
        GLfloat vertices\[\] \= {
            _// First triangle_
            1.0f, 1.0f, 0.0f, 1.0f, 0.0f,  _ // ..._
            1.0f, \-1.0f, 0.0f, 1.0f, 1.0f, _ // ..._
            \-1.0f, \-1.0f, 0.0f, 0.0f, 1.0f, _// ..._
          _  // Second triangle_
            1.0f, 1.0f, 0.0f, 1.0f, 0.0f,  _ // ..._
            \-1.0f, \-1.0f, 0.0f, 0.0f, 1.0f, _// ..._
            \-1.0f, 1.0f, 0.0f, 0.0f, 0.0f,  _// ..._
        };
        GLuint vbo;
        GLuint vao\[0\];
        glGenVertexArrays(1, vao);
        glBindVertexArray(vao\[0\]);
        glGenBuffers(1, &vbo);
        glBindBuffer(GL\_ARRAY\_BUFFER, vbo);
        glBufferData(GL\_ARRAY\_BUFFER, sizeof(vertices), vertices, GL\_STATIC\_DRAW);
        const char \*vertexShaderSource \= R"(#version 300 es
            layout (location = 0) in vec4 vPosition;
            layout (location = 1) in vec2 vTexCoord;
            out vec2 TexCoord;
            void main() {
                gl\_Position = vPosition;
                TexCoord = vTexCoord;
            }
        )";
        const char \*fragmentShaderSource \= R"(#version 300 es
            precision mediump float;
            out vec4 FragColor;
            in vec2 TexCoord;
            uniform sampler2D testTexture;
            void main() {
                FragColor = texture(testTexture, TexCoord);
            }
        )";
        GLuint vertexShader;
        vertexShader \= glCreateShader(GL\_VERTEX\_SHADER);
        glShaderSource(vertexShader, 1, &vertexShaderSource, nullptr);
        glCompileShader(vertexShader);
        GLuint fragmentShader;
        fragmentShader \= glCreateShader(GL\_FRAGMENT\_SHADER);
        glShaderSource(fragmentShader, 1, &fragmentShaderSource, nullptr);
        glCompileShader(fragmentShader);
        GLuint shaderProgram;
        shaderProgram \= glCreateProgram();
        glAttachShader(shaderProgram, vertexShader);
        glAttachShader(shaderProgram, fragmentShader);
        glLinkProgram(shaderProgram);
        glUseProgram(shaderProgram);
        GLuint textureId;
        glGenTextures(1, &textureId);
        glBindTexture(GL\_TEXTURE\_2D, textureId);
        glTexParameteri(GL\_TEXTURE\_2D, GL\_TEXTURE\_WRAP\_S, GL\_REPEAT);
        glTexParameteri(GL\_TEXTURE\_2D, GL\_TEXTURE\_WRAP\_T, GL\_REPEAT);
        glTexParameteri(GL\_TEXTURE\_2D, GL\_TEXTURE\_MIN\_FILTER, GL\_LINEAR);
        glTexParameteri(GL\_TEXTURE\_2D, GL\_TEXTURE\_MAG\_FILTER, GL\_LINEAR);
        glTexImage2D(GL\_TEXTURE\_2D, 0, GL\_RGBA, width, height, 0, GL\_RGBA, GL\_UNSIGNED\_BYTE, data);
        glGenerateMipmap(GL\_TEXTURE\_2D);
        glUniform1i(glGetUniformLocation(shaderProgram, "testTexture"), 0);
        glActiveTexture(GL\_TEXTURE0);
        glBindTexture(GL\_TEXTURE\_2D, textureId);
        glVertexAttribPointer(0, 3, GL\_FLOAT, GL\_FALSE, 5 \* sizeof(GLfloat), (GLvoid \*)0);
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(1, 2, GL\_FLOAT, GL\_FALSE, 5 \* sizeof(GLfloat), (GLvoid \*)(3 \* sizeof(GLfloat)));
        glEnableVertexAttribArray(1);
        glDrawArrays(GL\_TRIANGLES, 0, 6);
        eglSwapBuffers(display, surface);
        glDeleteShader(vertexShader);
        glDeleteShader(fragmentShader);
        glDeleteBuffers(1, &vbo);
        std::this\_thread::sleep\_for(std::chrono::milliseconds(50));
        eglMakeCurrent(display, EGL\_NO\_SURFACE, EGL\_NO\_SURFACE, EGL\_NO\_CONTEXT);
        eglDestroySurface(display, surface);
        eglDestroyContext(display, context);
        eglTerminate(display);
    }
    static napi\_value NativeDrawGpu(napi\_env env, napi\_callback\_info info)
    {
      _  // 初始化EGL上下文_
        EGLDisplay bufDisplay;
        EGLConfig bufConfig;
        EGLSurface bufSurface;
        EGLContext bufContext;
        EGLint majorVersion;
        EGLint minorVersion;
        bufDisplay \= eglGetDisplay(EGL\_DEFAULT\_DISPLAY);
        eglInitialize(bufDisplay, &majorVersion, &minorVersion);
        EGLint numConfigs;
        EGLint attribs\[\] \= {
            EGL\_SURFACE\_TYPE,
            EGL\_WINDOW\_BIT,
            EGL\_RENDERABLE\_TYPE,
            EGL\_OPENGL\_ES3\_BIT,
            EGL\_BLUE\_SIZE,
            8,
            EGL\_GREEN\_SIZE,
            8,
            EGL\_RED\_SIZE,
            8,
            EGL\_ALPHA\_SIZE,
            8,
            EGL\_NONE,
        };
        eglChooseConfig(bufDisplay, attribs, &bufConfig, 1, &numConfigs);
        EGLint contextAttribs\[\] \= {EGL\_CONTEXT\_CLIENT\_VERSION, 3, EGL\_NONE};
        bufSurface \= eglCreatePbufferSurface(bufDisplay, bufConfig, attribs);
        bufContext \= eglCreateContext(bufDisplay, bufConfig, EGL\_NO\_CONTEXT, contextAttribs);
        eglMakeCurrent(bufDisplay, bufSurface, bufSurface, bufContext);
      _  // 设置宽高（按需设定）_
        int32\_t width \= 900;
        int32\_t height \= 300;
      _  // 设置图像宽、高、颜色格式和透明度格式_
        OH\_Drawing\_Image\_Info imageInfo \= {width, height, COLOR\_FORMAT\_RGBA\_8888, ALPHA\_FORMAT\_PREMUL};
     _   // 创建GPU后端的绘图上下文_
        OH\_Drawing\_GpuContext \*gpuContext \= OH\_Drawing\_GpuContextCreate();
      _  // 创建Surface对象_
        OH\_Drawing\_Surface \*drawSurface \= OH\_Drawing\_SurfaceCreateFromGpuContext(gpuContext, true, imageInfo);
     _   // 创建Canvas对象_
        OH\_Drawing\_Canvas \*gpuCanvas \= OH\_Drawing\_SurfaceGetCanvas(drawSurface);
      _  // 绘制字块_
        char text\[\] \= "你好\\xF0\\x9F\\x98\\x82";
        OH\_Drawing\_Font \*font \= OH\_Drawing\_FontCreate();
        OH\_Drawing\_FontSetTextSize(font, 100);
        float posX \= 0;
        float posY \= 150;
        for (int32\_t idx \= 0; idx < 2; idx++) {
            float textWidth \= 0.0f;
            OH\_Drawing\_CanvasDrawSingleCharacter(gpuCanvas, &text\[idx \* 3\], font, posX, posY);
            OH\_Drawing\_FontMeasureSingleCharacter(font, &text\[idx \* 3\], &textWidth);
            posX += textWidth;
        }
        for (int32\_t idx \= 0; idx < 1; idx++) {
            float textWidth \= 0.0f;
            OH\_Drawing\_CanvasDrawSingleCharacter(gpuCanvas, &text\[idx \* 4 + 6\], font, posX, posY);
            OH\_Drawing\_FontMeasureSingleCharacter(font, &text\[idx \* 4 + 6\], &textWidth);
            posX += textWidth;
        }
        OH\_Drawing\_FontDestroy(font);
      _  // 从Canvas 上拷贝绘制结果位图数据_
        std::unique\_ptr<uint8\_t\> dstPixels(new uint8\_t\[width \* height \* 4\]);
        OH\_Drawing\_CanvasReadPixels(gpuCanvas, &imageInfo, dstPixels.get(), 4 \* width, 0, 0);
      _  // 清理资源_
        OH\_Drawing\_CanvasDestroy(gpuCanvas);
       _ // 清理EGL_
        eglDestroySurface(bufDisplay, bufSurface);
        eglDestroyContext(bufDisplay, bufContext);
        eglTerminate(bufDisplay);
        size\_t argc \= 1;
        napi\_value args\[1\] \= {nullptr};
        napi\_get\_cb\_info(env, info, &argc, args, nullptr, nullptr);
       _ // 获取XComponent的SurfaceID_
        bool lossless \= true;
        uint64\_t surfaceId \= 0;
        napi\_get\_value\_bigint\_uint64(env, args\[0\], &surfaceId, &lossless);
      _  // 通过SurfaceID创建NativeWindow对象_
        OHNativeWindow \*window \= nullptr;
        OH\_NativeWindow\_CreateNativeWindowFromSurfaceId(surfaceId, &window);
     _   // 通过OpenGL ES绘制图像_
        GLDraw(window, width, height, dstPixels.get());
     _   // 销毁NativeWindow_
        OH\_NativeWindow\_DestroyNativeWindow(window);
        return nullptr;
    }
    static napi\_value NativeDrawCpu(napi\_env env, napi\_callback\_info info)
    {
        int32\_t width \= 900;
        int32\_t height \= 300;
     _   // 创建位图对象_
        OH\_Drawing\_Bitmap \*bitmap \= OH\_Drawing\_BitmapCreate();
        OH\_Drawing\_BitmapFormat cFormat{COLOR\_FORMAT\_BGRA\_8888, ALPHA\_FORMAT\_PREMUL};
      _  // 初始化位图_
        OH\_Drawing\_BitmapBuild(bitmap, width, height, &cFormat);
       _ // 创建Canvas对象_
        OH\_Drawing\_Canvas \*bitmapCanvas \= OH\_Drawing\_CanvasCreate();
        _// 将Canvas与位图绑定，Canvas绘制的内容会输出到绑定的bitmap内存中_
        OH\_Drawing\_CanvasBind(bitmapCanvas, bitmap);
       _ // 绘制字块_
        char text\[\] \= "你好\\xF0\\x9F\\x98\\x82";
        OH\_Drawing\_Font \*font \= OH\_Drawing\_FontCreate();
        OH\_Drawing\_FontSetTextSize(font, 100);
        float posX \= 0;
        float posY \= 150;
        for (int32\_t idx \= 0; idx < 2; idx++) {
            float textWidth \= 0.0f;
            OH\_Drawing\_CanvasDrawSingleCharacter(bitmapCanvas, &text\[idx \* 3\], font, posX, posY);
            OH\_Drawing\_FontMeasureSingleCharacter(font, &text\[idx \* 3\], &textWidth);
            posX += textWidth;
        }
        for (int32\_t idx \= 0; idx < 1; idx++) {
            float textWidth \= 0.0f;
            OH\_Drawing\_CanvasDrawSingleCharacter(bitmapCanvas, &text\[idx \* 4 + 6\], font, posX, posY);
            OH\_Drawing\_FontMeasureSingleCharacter(font, &text\[idx \* 4 + 6\], &textWidth);
            posX += textWidth;
        }
        OH\_Drawing\_FontDestroy(font);
      _  // 从Canvas上拷贝绘制结果位图数据_
        std::unique\_ptr<uint8\_t\> dstPixels(new uint8\_t\[width \* height \* 4\]);
        OH\_Drawing\_Image\_Info imageInfo \= {width, height, COLOR\_FORMAT\_RGBA\_8888, ALPHA\_FORMAT\_PREMUL};
        OH\_Drawing\_CanvasReadPixels(bitmapCanvas, &imageInfo, dstPixels.get(), 4 \* width, 0, 0);
       _ // 清理资源_
        OH\_Drawing\_CanvasDestroy(bitmapCanvas);
        OH\_Drawing\_BitmapDestroy(bitmap);
        size\_t argc \= 1;
        napi\_value args\[1\] \= {nullptr};
        napi\_get\_cb\_info(env, info, &argc, args, nullptr, nullptr);
     _   // 获取XComponent的SurfaceID_
        bool lossless \= true;
        uint64\_t surfaceId \= 0;
        napi\_get\_value\_bigint\_uint64(env, args\[0\], &surfaceId, &lossless);
      _  // 通过SurfaceID创建NativeWindow对象_
        OHNativeWindow \*window \= nullptr;
        OH\_NativeWindow\_CreateNativeWindowFromSurfaceId(surfaceId, &window);
      _  // 通过OpenGL ES绘制图像_
        GLDraw(window, width, height, dstPixels.get());
       _ // 销毁NativeWindow_
        OH\_NativeWindow\_DestroyNativeWindow(window);
        return nullptr;
    }
    static napi\_value DrawImage(napi\_env env, napi\_callback\_info info)
    {
        size\_t argc \= 5;
        napi\_value args\[5\] \= {nullptr};
        napi\_get\_cb\_info(env, info, &argc, args, nullptr, nullptr);
       _ // 获取SurfaceID_
        bool lossless \= true;
        uint64\_t surfaceId \= 0;
        napi\_get\_value\_bigint\_uint64(env, args\[0\], &surfaceId, &lossless);
       _ // 获取位图数据_
        void \*data \= nullptr;
        size\_t byteLength \= 0;
        napi\_get\_arraybuffer\_info(env, args\[1\], &data, &byteLength);
       _ // 获取位图宽、高_
        int32\_t imageWidth \= 0;
        int32\_t imageHeight \= 0;
        napi\_get\_value\_int32(env, args\[2\], &imageWidth);
        napi\_get\_value\_int32(env, args\[3\], &imageHeight);
        _// 创建NativeWindow对象_
        OHNativeWindow \*window \= nullptr;
        OH\_NativeWindow\_CreateNativeWindowFromSurfaceId(surfaceId, &window);
       _ // 使用OpenGL ES绘制位图_
        GLDraw(window, imageWidth, imageHeight, data);
       _ // 销毁NativeWindow_
        OH\_NativeWindow\_DestroyNativeWindow(window);
        return nullptr;
    }
    EXTERN\_C\_START
    static napi\_value Init(napi\_env env, napi\_value exports)
    {
        napi\_property\_descriptor desc\[\] \= {
            {"drawText", nullptr, DrawImage, nullptr, nullptr, nullptr, napi\_default, nullptr},
            {"nativeDrawCpu", nullptr, NativeDrawCpu, nullptr, nullptr, nullptr, napi\_default, nullptr},
            {"nativeDrawGpu", nullptr, NativeDrawGpu, 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); }
    

#### 总结

使用OpenGL绘制文字的关键在于取得绘制文字内容的像素数据，获得文本的像素数据后，OpenGL生成纹理对象并在屏幕绘制。目前HarmonyOS支持通过上述三种方式取得文字绘制的像素数据，它们之间的对比如下表所示：

| 
方案

 | 

对比

 |
| :-- | :-- |
| 

OffscreenCanvas组件

 | 

在ArkTS侧实现，实现逻辑简单，使用CPU绘制，需要两次数据拷贝。

 |
| 

CPU后端的离屏画布

 | 

在Native侧实现，使用CPU绘制，一次数据拷贝。

 |
| 

GPU后端的离屏画布

 | 

在Native侧实现，使用GPU绘制，一次数据拷贝。

 |