@hxa-rn/react-native-gcanvas 6.0.25 → 6.0.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -2
- package/LICENSE +203 -21
- package/README.md +5 -5
- package/harmony/gcanvas/LICENSE +203 -21
- package/harmony/gcanvas/NOTICE +32 -32
- package/harmony/gcanvas/OAT.xml +37 -37
- package/harmony/gcanvas/build-profile.json5 +16 -16
- package/harmony/gcanvas/hvigorfile.ts +1 -1
- package/harmony/gcanvas/index.ets +1 -1
- package/harmony/gcanvas/oh-package.json5 +14 -14
- package/harmony/gcanvas/src/main/cpp/CMakeLists.txt +126 -126
- package/harmony/gcanvas/src/main/cpp/GCanvasNapi.cpp +485 -485
- package/harmony/gcanvas/src/main/cpp/GCanvasXComponent.cpp +106 -106
- package/harmony/gcanvas/src/main/cpp/GcanvasPackage.h +24 -24
- package/harmony/gcanvas/src/main/ets/GCanvasModuleTurboModule.ts +448 -448
- package/harmony/gcanvas/src/main/ets/GCanvasNativeEngine.ts +206 -206
- package/harmony/gcanvas/src/main/ets/components/GCanvasView.ets +64 -64
- package/harmony/gcanvas/src/main/module.json5 +21 -21
- package/harmony/gcanvas/src/main/resources/base/element/string.json +12 -12
- package/harmony/gcanvas/src/main/resources/en_US/element/string.json +8 -8
- package/harmony/gcanvas/src/main/resources/zh_CN/element/string.json +12 -12
- package/harmony/gcanvas.har +0 -0
- package/package.json +4 -4
- package/packages/gcanvas/.babelrc +4 -4
- package/packages/gcanvas/.eslintrc.js +133 -133
- package/packages/gcanvas/package.json +19 -19
- package/packages/gcanvas/rollup.config.js +13 -13
- package/packages/gcanvas/src/bridge/react-native.js +286 -286
- package/packages/gcanvas/src/bridge/weex.js +210 -210
- package/packages/gcanvas/src/context/2d/FillStyleLinearGradient.js +16 -16
- package/packages/gcanvas/src/context/2d/FillStylePattern.js +7 -7
- package/packages/gcanvas/src/context/2d/FillStyleRadialGradient.js +16 -16
- package/packages/gcanvas/src/context/2d/OffScreenRenderingContext.js +48 -48
- package/packages/gcanvas/src/context/2d/RenderingContext.js +787 -787
- package/packages/gcanvas/src/context/webgl/ActiveInfo.js +10 -10
- package/packages/gcanvas/src/context/webgl/Buffer.js +20 -20
- package/packages/gcanvas/src/context/webgl/Framebuffer.js +20 -20
- package/packages/gcanvas/src/context/webgl/GLenum.js +300 -300
- package/packages/gcanvas/src/context/webgl/GLmethod.js +141 -141
- package/packages/gcanvas/src/context/webgl/GLtype.js +22 -22
- package/packages/gcanvas/src/context/webgl/Program.js +20 -20
- package/packages/gcanvas/src/context/webgl/Renderbuffer.js +20 -20
- package/packages/gcanvas/src/context/webgl/RenderingContext.js +1479 -1479
- package/packages/gcanvas/src/context/webgl/Shader.js +21 -21
- package/packages/gcanvas/src/context/webgl/ShaderPrecisionFormat.js +10 -10
- package/packages/gcanvas/src/context/webgl/Texture.js +21 -21
- package/packages/gcanvas/src/context/webgl/UniformLocation.js +21 -21
- package/packages/gcanvas/src/context/webgl/classUtils.js +2 -2
- package/packages/gcanvas/src/env/canvas.js +210 -210
- package/packages/gcanvas/src/env/image.js +76 -76
- package/packages/gcanvas/src/env/off-screen-canvas.js +74 -74
- package/packages/gcanvas/src/index.js +56 -56
- package/src/components/CanvasView.js +20 -20
- package/src/components/GCanvasComponent.js +408 -408
- package/src/index.js +69 -69
- package/src/packages/gcanvas/.babelrc +4 -4
- package/src/packages/gcanvas/.eslintrc.js +133 -133
- package/src/packages/gcanvas/package.json +19 -19
- package/src/packages/gcanvas/rollup.config.js +13 -13
- package/src/packages/gcanvas/src/bridge/react-native.js +288 -288
- package/src/packages/gcanvas/src/bridge/weex.js +210 -210
- package/src/packages/gcanvas/src/context/2d/FillStyleLinearGradient.js +16 -16
- package/src/packages/gcanvas/src/context/2d/FillStylePattern.js +7 -7
- package/src/packages/gcanvas/src/context/2d/FillStyleRadialGradient.js +16 -16
- package/src/packages/gcanvas/src/context/2d/OffScreenRenderingContext.js +48 -48
- package/src/packages/gcanvas/src/context/2d/RenderingContext.js +801 -801
- package/src/packages/gcanvas/src/context/webgl/ActiveInfo.js +10 -10
- package/src/packages/gcanvas/src/context/webgl/Buffer.js +20 -20
- package/src/packages/gcanvas/src/context/webgl/Framebuffer.js +20 -20
- package/src/packages/gcanvas/src/context/webgl/GLenum.js +300 -300
- package/src/packages/gcanvas/src/context/webgl/GLmethod.js +141 -141
- package/src/packages/gcanvas/src/context/webgl/GLtype.js +22 -22
- package/src/packages/gcanvas/src/context/webgl/Program.js +20 -20
- package/src/packages/gcanvas/src/context/webgl/Renderbuffer.js +20 -20
- package/src/packages/gcanvas/src/context/webgl/RenderingContext.js +1479 -1479
- package/src/packages/gcanvas/src/context/webgl/Shader.js +21 -21
- package/src/packages/gcanvas/src/context/webgl/ShaderPrecisionFormat.js +10 -10
- package/src/packages/gcanvas/src/context/webgl/Texture.js +21 -21
- package/src/packages/gcanvas/src/context/webgl/UniformLocation.js +21 -21
- package/src/packages/gcanvas/src/context/webgl/classUtils.js +2 -2
- package/src/packages/gcanvas/src/env/canvas.js +210 -210
- package/src/packages/gcanvas/src/env/image.js +76 -76
- package/src/packages/gcanvas/src/env/off-screen-canvas.js +74 -74
- package/src/packages/gcanvas/src/index.js +56 -56
- package/src/specs/v1/GCanvasViewNativeComponent.ts +42 -42
- package/src/specs/v1/NativeGCanvasModule.ts +140 -140
|
@@ -1,448 +1,448 @@
|
|
|
1
|
-
import { UITurboModule, UITurboModuleContext } from '@rnoh/react-native-openharmony/ts';
|
|
2
|
-
import { TM } from './generated/ts';
|
|
3
|
-
import { image } from '@kit.ImageKit';
|
|
4
|
-
import { http } from '@kit.NetworkKit';
|
|
5
|
-
import util from '@ohos.util';
|
|
6
|
-
import fs from '@ohos.file.fs';
|
|
7
|
-
import hilog from '@ohos.hilog';
|
|
8
|
-
import { GCanvasNativeEngine } from './GCanvasNativeEngine';
|
|
9
|
-
|
|
10
|
-
const TAG = 'GCanvasModule';
|
|
11
|
-
const DOMAIN = 0x0001;
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* GCanvasModule TurboModule(鸿蒙 ArkTS 侧实现)。
|
|
15
|
-
*
|
|
16
|
-
* 本模块是 C++ OpenGL ES Canvas/WebGL 引擎(GCanvas)的鸿蒙适配:
|
|
17
|
-
* - 同步方法(enable/extendCallNative/setContextType/toDataURL/字体等):透传至
|
|
18
|
-
* C++ 原生引擎 GCanvasNativeEngine(单例),由其内部 GL 线程执行命令并同步返回。
|
|
19
|
-
* 同步语义复刻 Android GCanvasJNI.render() 的 waitUtilTimeout/QueueProc。
|
|
20
|
-
* - 异步方法(图片解码/网络下载/生命周期):在 ArkTS 侧用 image kit / net.http 实现,
|
|
21
|
-
* 解码出的 RGBA 像素交 C++ 引擎上传纹理(对齐 Android texImage2D 命令 '102,9,...')。
|
|
22
|
-
*
|
|
23
|
-
* 方法名、签名、返回类型与 JS 桥(packages/gcanvas/src/bridge/react-native.js)及
|
|
24
|
-
* Spec(src/specs/v1/NativeGCanvasModule.ts)完全一致。TurboModuleRegistry 名称固定 'GCanvasModule'。
|
|
25
|
-
*
|
|
26
|
-
* 错误处理约定:异步方法用 Promise.reject();同步方法不抛异常(GL 调用失败由 C++ 内部记录),
|
|
27
|
-
* 仅在 ArkTS 入口参数非法时记录日志并返回安全默认值,避免原生崩溃导致 RN 应用闪退。
|
|
28
|
-
*/
|
|
29
|
-
export class GCanvasModuleTurboModule extends UITurboModule implements TM.GCanvasModule.Spec {
|
|
30
|
-
private engine: GCanvasNativeEngine = GCanvasNativeEngine.getInstance();
|
|
31
|
-
// GImage.preloadImage currently passes the legacy placeholder id 0 and assigns
|
|
32
|
-
// the real image id in JS afterwards. Android therefore resolves textures by
|
|
33
|
-
// path (fetchLoadImage(path)); keep the same source-keyed cache on Harmony.
|
|
34
|
-
private decodedImagesBySource: Map<string, RgbaPixels> = new Map();
|
|
35
|
-
|
|
36
|
-
constructor(ctx: UITurboModuleContext) {
|
|
37
|
-
super(ctx);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* 启用画布(同步)。返回前需让 GL 表面就绪。
|
|
42
|
-
* config 是上游渲染器选项数组;尺寸和像素比分别来自 XComponent 与 setDevicePixelRatio。
|
|
43
|
-
*/
|
|
44
|
-
enable(args: { componentId: string, config: number[] }): void {
|
|
45
|
-
try {
|
|
46
|
-
this.engine.enable(args.componentId, args.config);
|
|
47
|
-
} catch (e) {
|
|
48
|
-
hilogError(`enable failed: componentId=${args.componentId}`, e);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** 禁用画布(异步)。 */
|
|
53
|
-
disable(refId: string): Promise<void> {
|
|
54
|
-
try {
|
|
55
|
-
this.engine.disable(refId);
|
|
56
|
-
return Promise.resolve();
|
|
57
|
-
} catch (e) {
|
|
58
|
-
hilogError(`disable failed: refId=${refId}`);
|
|
59
|
-
return Promise.reject(new Error(`disable failed: ${refId}`));
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* 异步执行命令字符串(type 含 32-bit 编码:2d=0x00 / webgl=0x40,async)。
|
|
65
|
-
* 命令投递到 GL 线程后立即返回,不等待结果。
|
|
66
|
-
*/
|
|
67
|
-
render(canvasId: string, cmd: string, type: number): Promise<void> {
|
|
68
|
-
try {
|
|
69
|
-
this.engine.render(canvasId, cmd, type);
|
|
70
|
-
return Promise.resolve();
|
|
71
|
-
} catch (e) {
|
|
72
|
-
hilogError(`render failed: canvasId=${canvasId}`, e);
|
|
73
|
-
return Promise.reject(new Error(`render failed: ${canvasId}`));
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* 【核心】同步执行命令字符串并返回结果。
|
|
79
|
-
* 32-bit type = ContextType[31:30](0=2d/1=webgl) | MethodType[29](1=sync) | OptionType[28:0]。
|
|
80
|
-
* 命令在 GL 线程执行并阻塞等待结果,对齐 Android GCanvasJNI.render() 的同步语义。
|
|
81
|
-
*/
|
|
82
|
-
extendCallNative(args: { contextId: string, args: string, type: number }): { result: string } {
|
|
83
|
-
let result: string = '';
|
|
84
|
-
try {
|
|
85
|
-
result = this.engine.extendCallNative(args.contextId, args.args, args.type);
|
|
86
|
-
} catch (e) {
|
|
87
|
-
hilogError(`extendCallNative failed: contextId=${args.contextId}`, e);
|
|
88
|
-
}
|
|
89
|
-
let ret: ExtendCallNativeResult = { result };
|
|
90
|
-
return ret;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/** 设置上下文类型(同步)。0=2d, 1=webgl。 */
|
|
94
|
-
setContextType(contextType: number, refId: string): void {
|
|
95
|
-
try {
|
|
96
|
-
this.engine.setContextType(contextType, refId);
|
|
97
|
-
} catch (e) {
|
|
98
|
-
hilogError(`setContextType failed: refId=${refId}`);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/** 设置设备像素比(同步)。 */
|
|
103
|
-
setDevicePixelRatio(refId: string, ratio: number): void {
|
|
104
|
-
try {
|
|
105
|
-
this.engine.setDevicePixelRatio(refId, ratio);
|
|
106
|
-
} catch (e) {
|
|
107
|
-
hilogError(`setDevicePixelRatio failed: refId=${refId}`);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/** 重置 GL viewport(同步)。 */
|
|
112
|
-
resetGlViewport(refId: string): void {
|
|
113
|
-
try {
|
|
114
|
-
this.engine.resetGlViewport(refId);
|
|
115
|
-
} catch (e) {
|
|
116
|
-
hilogError(`resetGlViewport failed: refId=${refId}`);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* 同步导出画布为 data URL(glReadPixels → PNG → base64)。
|
|
122
|
-
* 返回形如 data:image/png;base64,xxxx;失败返回空字符串。
|
|
123
|
-
*/
|
|
124
|
-
toDataURL(refId: string, mimeType: string, quality: number): string {
|
|
125
|
-
try {
|
|
126
|
-
return this.engine.toDataURL(refId, mimeType, quality);
|
|
127
|
-
} catch (e) {
|
|
128
|
-
hilogError(`toDataURL failed: refId=${refId}`);
|
|
129
|
-
return '';
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/** 将一个画布内容绘制到另一画布(同步)。 */
|
|
134
|
-
drawCanvas2Canvas(map: DrawCanvas2CanvasMap): void {
|
|
135
|
-
try {
|
|
136
|
-
this.engine.drawCanvas2Canvas(map);
|
|
137
|
-
} catch (e) {
|
|
138
|
-
hilogError(`drawCanvas2Canvas failed: src=${map.srcComponentId} dst=${map.dstComponentId}`);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* 图片解码后绑定纹理(异步)。src=图片路径,imageId=纹理 id。
|
|
144
|
-
* 鸿蒙用 image kit 解码为 RGBA_8888,再交 C++ 引擎上传纹理。
|
|
145
|
-
*/
|
|
146
|
-
bindImageTexture(args: { src: string, imageId: number }, refId: string): Promise<void> {
|
|
147
|
-
if (this.engine.bindCachedImageTexture(refId, args.imageId)) {
|
|
148
|
-
return Promise.resolve();
|
|
149
|
-
}
|
|
150
|
-
const cached = this.decodedImagesBySource.get(args.src);
|
|
151
|
-
if (cached) {
|
|
152
|
-
this.engine.bindImageTexture(refId, args.imageId, cached.buffer, cached.width, cached.height);
|
|
153
|
-
return Promise.resolve();
|
|
154
|
-
}
|
|
155
|
-
return decodeImageToRGBA(args.src).then((rgba: RgbaPixels) => {
|
|
156
|
-
this.decodedImagesBySource.set(args.src, rgba);
|
|
157
|
-
this.engine.bindImageTexture(refId, args.imageId, rgba.buffer, rgba.width, rgba.height);
|
|
158
|
-
}).catch((e: Error) => {
|
|
159
|
-
hilogError(`bindImageTexture failed: src=${args.src} err=${e.message}`);
|
|
160
|
-
return Promise.reject(new Error(`bindImageTexture failed: ${e.message}`));
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* 预加载图片(异步)。url=图片地址,id=图片 id,返回尺寸/错误信息。
|
|
166
|
-
* 远程 URL 经 @ohos.net.http 下载为 ArrayBuffer 后再解码(image kit 不支持 http URL)。
|
|
167
|
-
*/
|
|
168
|
-
preLoadImage(args: { url: string, id: number }): Promise<ImageInfo> {
|
|
169
|
-
return decodeImageToRGBA(args.url).then((rgba: RgbaPixels) => {
|
|
170
|
-
// 缓存解码结果,供后续 bindImageTexture/texImage2D 复用
|
|
171
|
-
this.decodedImagesBySource.set(args.url, rgba);
|
|
172
|
-
this.engine.cacheImage(args.id, rgba.buffer, rgba.width, rgba.height);
|
|
173
|
-
let info: ImageInfo = { width: rgba.width, height: rgba.height };
|
|
174
|
-
return info;
|
|
175
|
-
}).catch((e: Error) => {
|
|
176
|
-
hilogError(`preLoadImage failed: url=${args.url} err=${e.message}`);
|
|
177
|
-
let info: ImageInfo = { width: 0, height: 0, error: e.message };
|
|
178
|
-
return info;
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/** 获取设备信息(异步)。返回 JSON 字符串。 */
|
|
183
|
-
getDeviceInfo(args: string): Promise<DeviceInfoRet> {
|
|
184
|
-
try {
|
|
185
|
-
const data = this.engine.getDeviceInfo(args);
|
|
186
|
-
let ret: DeviceInfoRet = { data };
|
|
187
|
-
return Promise.resolve(ret);
|
|
188
|
-
} catch (e) {
|
|
189
|
-
hilogError(`getDeviceInfo failed: args=${args}`);
|
|
190
|
-
let ret: DeviceInfoRet = { data: '' };
|
|
191
|
-
return Promise.resolve(ret);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/** 注册自定义字体族(同步)。fontNames 与 fontFiles 一一对应。 */
|
|
196
|
-
addFontFamily(fontNames: string[], fontFiles: string[]): void {
|
|
197
|
-
try {
|
|
198
|
-
this.engine.addFontFamily(fontNames, fontFiles);
|
|
199
|
-
} catch (e) {
|
|
200
|
-
hilogError('addFontFamily failed');
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/** 枚举系统字体族名(同步)。 */
|
|
205
|
-
getFontNames(): string[] {
|
|
206
|
-
try {
|
|
207
|
-
return this.engine.getFontNames();
|
|
208
|
-
} catch (e) {
|
|
209
|
-
hilogError('getFontNames failed');
|
|
210
|
-
return [];
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/** 设置额外字体目录(同步)。 */
|
|
215
|
-
setExtraFontLocation(location: string): void {
|
|
216
|
-
try {
|
|
217
|
-
this.engine.setExtraFontLocation(location);
|
|
218
|
-
} catch (e) {
|
|
219
|
-
hilogError(`setExtraFontLocation failed: location=${location}`);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/** 设置日志级别(异步)。 */
|
|
224
|
-
setLogLevel(level: number): Promise<void> {
|
|
225
|
-
try {
|
|
226
|
-
this.engine.setLogLevel(level);
|
|
227
|
-
return Promise.resolve();
|
|
228
|
-
} catch (e) {
|
|
229
|
-
hilogError(`setLogLevel failed: level=${level}`);
|
|
230
|
-
return Promise.reject(new Error('setLogLevel failed'));
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/** 重置组件(异步)。 */
|
|
235
|
-
resetComponent(refId: string): Promise<void> {
|
|
236
|
-
try {
|
|
237
|
-
this.engine.resetComponent(refId);
|
|
238
|
-
return Promise.resolve();
|
|
239
|
-
} catch (e) {
|
|
240
|
-
hilogError(`resetComponent failed: refId=${refId}`);
|
|
241
|
-
return Promise.reject(new Error(`resetComponent failed: ${refId}`));
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/** WebGL 纹理上传(同步)。图片须先由 GImage/preLoadImage 解码并按 src 缓存。 */
|
|
246
|
-
texImage2D(refId: string, target: number, level: number, internalformat: number, format: number,
|
|
247
|
-
type: number, src: string): void {
|
|
248
|
-
try {
|
|
249
|
-
const rgba = this.decodedImagesBySource.get(src);
|
|
250
|
-
if (!rgba) {
|
|
251
|
-
hilogError(`texImage2D image is not preloaded: src=${src}`);
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
this.engine.texImage2D(refId, target, level, internalformat, format, type,
|
|
255
|
-
rgba.buffer, rgba.width, rgba.height);
|
|
256
|
-
} catch (e) {
|
|
257
|
-
hilogError(`texImage2D failed: refId=${refId}`);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/** WebGL 纹理子图上传(同步,鸿蒙新增分支)。命令 '105,9,...'。 */
|
|
262
|
-
texSubImage2D(refId: string, target: number, level: number, xoffset: number, yoffset: number,
|
|
263
|
-
format: number, type: number, src: string): void {
|
|
264
|
-
try {
|
|
265
|
-
const rgba = this.decodedImagesBySource.get(src);
|
|
266
|
-
if (!rgba) {
|
|
267
|
-
hilogError(`texSubImage2D image is not preloaded: src=${src}`);
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
this.engine.texSubImage2D(refId, target, level, xoffset, yoffset, format, type,
|
|
271
|
-
rgba.buffer, rgba.width, rgba.height);
|
|
272
|
-
} catch (e) {
|
|
273
|
-
hilogError(`texSubImage2D failed: refId=${refId}`);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/** 设置组件 alpha(异步,原实现为空壳,鸿蒙同样空实现)。 */
|
|
278
|
-
setAlpha(refId: string, alpha: number): Promise<void> {
|
|
279
|
-
// 原生 Android/iOS 端 setAlpha 即为空实现,鸿蒙保持一致。
|
|
280
|
-
return Promise.resolve();
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
/**
|
|
285
|
-
* 解码图片为 RGBA 像素缓冲。
|
|
286
|
-
* 支持:沙箱/资源/$rawfile 路径、http(s) 远程 URL、data: URL。
|
|
287
|
-
* 远程 URL 先经 @ohos.net.http 下载为 ArrayBuffer(image.createImageSource 不支持 http)。
|
|
288
|
-
*/
|
|
289
|
-
async function decodeImageToRGBA(src: string): Promise<RgbaPixels> {
|
|
290
|
-
let buf: ArrayBuffer;
|
|
291
|
-
if (src.startsWith('http://') || src.startsWith('https://')) {
|
|
292
|
-
buf = await downloadToBuffer(src);
|
|
293
|
-
} else if (src.startsWith('data:')) {
|
|
294
|
-
buf = dataUrlToBuffer(src);
|
|
295
|
-
} else {
|
|
296
|
-
buf = await readLocalToBuffer(src);
|
|
297
|
-
}
|
|
298
|
-
let imageSource: image.ImageSource | null = null;
|
|
299
|
-
let pixelMap: image.PixelMap | null = null;
|
|
300
|
-
try {
|
|
301
|
-
imageSource = image.createImageSource(buf);
|
|
302
|
-
pixelMap = await imageSource.createPixelMap({ desiredPixelFormat: image.PixelMapFormat.RGBA_8888 });
|
|
303
|
-
const info = await pixelMap.getImageInfo();
|
|
304
|
-
const width: number = info.size.width;
|
|
305
|
-
const height: number = info.size.height;
|
|
306
|
-
const byteCount: number = pixelMap.getPixelBytesNumber();
|
|
307
|
-
const pixels: ArrayBuffer = new ArrayBuffer(byteCount);
|
|
308
|
-
await pixelMap.readPixelsToBuffer(pixels);
|
|
309
|
-
let ret: RgbaPixels = { buffer: pixels, width, height };
|
|
310
|
-
return ret;
|
|
311
|
-
} catch (e) {
|
|
312
|
-
hilog.error(DOMAIN, TAG, `decodeImageToRGBA failed: src=${src} err=${(e as Error).message}`);
|
|
313
|
-
throw e instanceof Error ? e : new Error(String(e));
|
|
314
|
-
} finally {
|
|
315
|
-
releaseDecodedImage(pixelMap, imageSource);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
function releaseDecodedImage(pixelMap: image.PixelMap | null, imageSource: image.ImageSource | null): void {
|
|
320
|
-
if (pixelMap) {
|
|
321
|
-
try {
|
|
322
|
-
pixelMap.release();
|
|
323
|
-
} catch (e) {
|
|
324
|
-
hilog.error(DOMAIN, TAG, `release PixelMap failed: ${(e as Error).message}`);
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
if (imageSource) {
|
|
328
|
-
try {
|
|
329
|
-
imageSource.release();
|
|
330
|
-
} catch (e) {
|
|
331
|
-
hilog.error(DOMAIN, TAG, `release ImageSource failed: ${(e as Error).message}`);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
/** 远程图片下载为 ArrayBuffer。 */
|
|
337
|
-
async function downloadToBuffer(url: string): Promise<ArrayBuffer> {
|
|
338
|
-
let httpReq: http.HttpRequest | null = null;
|
|
339
|
-
try {
|
|
340
|
-
httpReq = http.createHttp();
|
|
341
|
-
const resp = await httpReq.request(url, {
|
|
342
|
-
method: http.RequestMethod.GET,
|
|
343
|
-
expectDataType: http.HttpDataType.ARRAY_BUFFER,
|
|
344
|
-
});
|
|
345
|
-
httpReq.destroy();
|
|
346
|
-
return resp.result as ArrayBuffer;
|
|
347
|
-
} catch (e) {
|
|
348
|
-
if (httpReq) {
|
|
349
|
-
httpReq.destroy();
|
|
350
|
-
}
|
|
351
|
-
hilog.error(DOMAIN, TAG, `downloadToBuffer failed: url=${url} err=${(e as Error).message}`);
|
|
352
|
-
throw e instanceof Error ? e : new Error(String(e));
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
/** data URL(base64)转 ArrayBuffer。 */
|
|
357
|
-
function dataUrlToBuffer(dataUrl: string): ArrayBuffer {
|
|
358
|
-
try {
|
|
359
|
-
const base64Helper = new util.Base64Helper();
|
|
360
|
-
const commaIdx = dataUrl.indexOf(',');
|
|
361
|
-
const base64 = commaIdx >= 0 ? dataUrl.substring(commaIdx + 1) : dataUrl;
|
|
362
|
-
const bytes = base64Helper.decodeSync(base64);
|
|
363
|
-
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;
|
|
364
|
-
} catch (e) {
|
|
365
|
-
hilog.error(DOMAIN, TAG, `dataUrlToBuffer failed: err=${(e as Error).message}`);
|
|
366
|
-
throw e instanceof Error ? e : new Error(String(e));
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
/** 本地/资源文件读取为 ArrayBuffer。 */
|
|
371
|
-
async function readLocalToBuffer(path: string): Promise<ArrayBuffer> {
|
|
372
|
-
let file: fs.File | null = null;
|
|
373
|
-
try {
|
|
374
|
-
file = fs.openSync(path, fs.OpenMode.READ_ONLY);
|
|
375
|
-
const stat = fs.statSync(path);
|
|
376
|
-
const buf: ArrayBuffer = new ArrayBuffer(stat.size);
|
|
377
|
-
await fs.read(file.fd, buf);
|
|
378
|
-
fs.closeSync(file);
|
|
379
|
-
return buf;
|
|
380
|
-
} catch (e) {
|
|
381
|
-
// 关闭文件也可能抛异常,且门禁要求 @kit 调用必须在 try/catch 内并用 hilog 记录。
|
|
382
|
-
if (file) {
|
|
383
|
-
try {
|
|
384
|
-
fs.closeSync(file);
|
|
385
|
-
} catch (ce) {
|
|
386
|
-
hilog.error(DOMAIN, TAG, `readLocalToBuffer close failed: path=${path} err=${(ce as Error).message}`);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
hilog.error(DOMAIN, TAG, `readLocalToBuffer failed: path=${path} err=${(e as Error).message}`);
|
|
390
|
-
throw e instanceof Error ? e : new Error(String(e));
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
function errorMessage(e: Object): string {
|
|
395
|
-
try {
|
|
396
|
-
return e instanceof Error ? e.message : String(e);
|
|
397
|
-
} catch (_ignored) {
|
|
398
|
-
return 'unknown';
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
function hilogError(msg: string, e?: Object): void {
|
|
403
|
-
if (e === undefined) {
|
|
404
|
-
hilog.error(DOMAIN, TAG, msg);
|
|
405
|
-
return;
|
|
406
|
-
}
|
|
407
|
-
hilog.error(DOMAIN, TAG, `${msg} err=${errorMessage(e)}`);
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
/** extendCallNative 同步返回值。 */
|
|
411
|
-
interface ExtendCallNativeResult {
|
|
412
|
-
result: string;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
/** drawCanvas2Canvas 参数。 */
|
|
416
|
-
interface DrawCanvas2CanvasMap {
|
|
417
|
-
srcComponentId: string;
|
|
418
|
-
dstComponentId: string;
|
|
419
|
-
tw: number;
|
|
420
|
-
th: number;
|
|
421
|
-
sx: number;
|
|
422
|
-
sy: number;
|
|
423
|
-
sw: number;
|
|
424
|
-
sh: number;
|
|
425
|
-
dx: number;
|
|
426
|
-
dy: number;
|
|
427
|
-
dw: number;
|
|
428
|
-
dh: number;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
/** RGBA 像素解码结果。 */
|
|
432
|
-
interface RgbaPixels {
|
|
433
|
-
buffer: ArrayBuffer;
|
|
434
|
-
width: number;
|
|
435
|
-
height: number;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
/** preLoadImage 返回的图片信息。 */
|
|
439
|
-
interface ImageInfo {
|
|
440
|
-
width: number;
|
|
441
|
-
height: number;
|
|
442
|
-
error?: string;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
/** getDeviceInfo 返回。 */
|
|
446
|
-
interface DeviceInfoRet {
|
|
447
|
-
data: string;
|
|
448
|
-
}
|
|
1
|
+
import { UITurboModule, UITurboModuleContext } from '@rnoh/react-native-openharmony/ts';
|
|
2
|
+
import { TM } from './generated/ts';
|
|
3
|
+
import { image } from '@kit.ImageKit';
|
|
4
|
+
import { http } from '@kit.NetworkKit';
|
|
5
|
+
import util from '@ohos.util';
|
|
6
|
+
import fs from '@ohos.file.fs';
|
|
7
|
+
import hilog from '@ohos.hilog';
|
|
8
|
+
import { GCanvasNativeEngine } from './GCanvasNativeEngine';
|
|
9
|
+
|
|
10
|
+
const TAG = 'GCanvasModule';
|
|
11
|
+
const DOMAIN = 0x0001;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* GCanvasModule TurboModule(鸿蒙 ArkTS 侧实现)。
|
|
15
|
+
*
|
|
16
|
+
* 本模块是 C++ OpenGL ES Canvas/WebGL 引擎(GCanvas)的鸿蒙适配:
|
|
17
|
+
* - 同步方法(enable/extendCallNative/setContextType/toDataURL/字体等):透传至
|
|
18
|
+
* C++ 原生引擎 GCanvasNativeEngine(单例),由其内部 GL 线程执行命令并同步返回。
|
|
19
|
+
* 同步语义复刻 Android GCanvasJNI.render() 的 waitUtilTimeout/QueueProc。
|
|
20
|
+
* - 异步方法(图片解码/网络下载/生命周期):在 ArkTS 侧用 image kit / net.http 实现,
|
|
21
|
+
* 解码出的 RGBA 像素交 C++ 引擎上传纹理(对齐 Android texImage2D 命令 '102,9,...')。
|
|
22
|
+
*
|
|
23
|
+
* 方法名、签名、返回类型与 JS 桥(packages/gcanvas/src/bridge/react-native.js)及
|
|
24
|
+
* Spec(src/specs/v1/NativeGCanvasModule.ts)完全一致。TurboModuleRegistry 名称固定 'GCanvasModule'。
|
|
25
|
+
*
|
|
26
|
+
* 错误处理约定:异步方法用 Promise.reject();同步方法不抛异常(GL 调用失败由 C++ 内部记录),
|
|
27
|
+
* 仅在 ArkTS 入口参数非法时记录日志并返回安全默认值,避免原生崩溃导致 RN 应用闪退。
|
|
28
|
+
*/
|
|
29
|
+
export class GCanvasModuleTurboModule extends UITurboModule implements TM.GCanvasModule.Spec {
|
|
30
|
+
private engine: GCanvasNativeEngine = GCanvasNativeEngine.getInstance();
|
|
31
|
+
// GImage.preloadImage currently passes the legacy placeholder id 0 and assigns
|
|
32
|
+
// the real image id in JS afterwards. Android therefore resolves textures by
|
|
33
|
+
// path (fetchLoadImage(path)); keep the same source-keyed cache on Harmony.
|
|
34
|
+
private decodedImagesBySource: Map<string, RgbaPixels> = new Map();
|
|
35
|
+
|
|
36
|
+
constructor(ctx: UITurboModuleContext) {
|
|
37
|
+
super(ctx);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 启用画布(同步)。返回前需让 GL 表面就绪。
|
|
42
|
+
* config 是上游渲染器选项数组;尺寸和像素比分别来自 XComponent 与 setDevicePixelRatio。
|
|
43
|
+
*/
|
|
44
|
+
enable(args: { componentId: string, config: number[] }): void {
|
|
45
|
+
try {
|
|
46
|
+
this.engine.enable(args.componentId, args.config);
|
|
47
|
+
} catch (e) {
|
|
48
|
+
hilogError(`enable failed: componentId=${args.componentId}`, e);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** 禁用画布(异步)。 */
|
|
53
|
+
disable(refId: string): Promise<void> {
|
|
54
|
+
try {
|
|
55
|
+
this.engine.disable(refId);
|
|
56
|
+
return Promise.resolve();
|
|
57
|
+
} catch (e) {
|
|
58
|
+
hilogError(`disable failed: refId=${refId}`);
|
|
59
|
+
return Promise.reject(new Error(`disable failed: ${refId}`));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 异步执行命令字符串(type 含 32-bit 编码:2d=0x00 / webgl=0x40,async)。
|
|
65
|
+
* 命令投递到 GL 线程后立即返回,不等待结果。
|
|
66
|
+
*/
|
|
67
|
+
render(canvasId: string, cmd: string, type: number): Promise<void> {
|
|
68
|
+
try {
|
|
69
|
+
this.engine.render(canvasId, cmd, type);
|
|
70
|
+
return Promise.resolve();
|
|
71
|
+
} catch (e) {
|
|
72
|
+
hilogError(`render failed: canvasId=${canvasId}`, e);
|
|
73
|
+
return Promise.reject(new Error(`render failed: ${canvasId}`));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 【核心】同步执行命令字符串并返回结果。
|
|
79
|
+
* 32-bit type = ContextType[31:30](0=2d/1=webgl) | MethodType[29](1=sync) | OptionType[28:0]。
|
|
80
|
+
* 命令在 GL 线程执行并阻塞等待结果,对齐 Android GCanvasJNI.render() 的同步语义。
|
|
81
|
+
*/
|
|
82
|
+
extendCallNative(args: { contextId: string, args: string, type: number }): { result: string } {
|
|
83
|
+
let result: string = '';
|
|
84
|
+
try {
|
|
85
|
+
result = this.engine.extendCallNative(args.contextId, args.args, args.type);
|
|
86
|
+
} catch (e) {
|
|
87
|
+
hilogError(`extendCallNative failed: contextId=${args.contextId}`, e);
|
|
88
|
+
}
|
|
89
|
+
let ret: ExtendCallNativeResult = { result };
|
|
90
|
+
return ret;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** 设置上下文类型(同步)。0=2d, 1=webgl。 */
|
|
94
|
+
setContextType(contextType: number, refId: string): void {
|
|
95
|
+
try {
|
|
96
|
+
this.engine.setContextType(contextType, refId);
|
|
97
|
+
} catch (e) {
|
|
98
|
+
hilogError(`setContextType failed: refId=${refId}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** 设置设备像素比(同步)。 */
|
|
103
|
+
setDevicePixelRatio(refId: string, ratio: number): void {
|
|
104
|
+
try {
|
|
105
|
+
this.engine.setDevicePixelRatio(refId, ratio);
|
|
106
|
+
} catch (e) {
|
|
107
|
+
hilogError(`setDevicePixelRatio failed: refId=${refId}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** 重置 GL viewport(同步)。 */
|
|
112
|
+
resetGlViewport(refId: string): void {
|
|
113
|
+
try {
|
|
114
|
+
this.engine.resetGlViewport(refId);
|
|
115
|
+
} catch (e) {
|
|
116
|
+
hilogError(`resetGlViewport failed: refId=${refId}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* 同步导出画布为 data URL(glReadPixels → PNG → base64)。
|
|
122
|
+
* 返回形如 data:image/png;base64,xxxx;失败返回空字符串。
|
|
123
|
+
*/
|
|
124
|
+
toDataURL(refId: string, mimeType: string, quality: number): string {
|
|
125
|
+
try {
|
|
126
|
+
return this.engine.toDataURL(refId, mimeType, quality);
|
|
127
|
+
} catch (e) {
|
|
128
|
+
hilogError(`toDataURL failed: refId=${refId}`);
|
|
129
|
+
return '';
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** 将一个画布内容绘制到另一画布(同步)。 */
|
|
134
|
+
drawCanvas2Canvas(map: DrawCanvas2CanvasMap): void {
|
|
135
|
+
try {
|
|
136
|
+
this.engine.drawCanvas2Canvas(map);
|
|
137
|
+
} catch (e) {
|
|
138
|
+
hilogError(`drawCanvas2Canvas failed: src=${map.srcComponentId} dst=${map.dstComponentId}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* 图片解码后绑定纹理(异步)。src=图片路径,imageId=纹理 id。
|
|
144
|
+
* 鸿蒙用 image kit 解码为 RGBA_8888,再交 C++ 引擎上传纹理。
|
|
145
|
+
*/
|
|
146
|
+
bindImageTexture(args: { src: string, imageId: number }, refId: string): Promise<void> {
|
|
147
|
+
if (this.engine.bindCachedImageTexture(refId, args.imageId)) {
|
|
148
|
+
return Promise.resolve();
|
|
149
|
+
}
|
|
150
|
+
const cached = this.decodedImagesBySource.get(args.src);
|
|
151
|
+
if (cached) {
|
|
152
|
+
this.engine.bindImageTexture(refId, args.imageId, cached.buffer, cached.width, cached.height);
|
|
153
|
+
return Promise.resolve();
|
|
154
|
+
}
|
|
155
|
+
return decodeImageToRGBA(args.src).then((rgba: RgbaPixels) => {
|
|
156
|
+
this.decodedImagesBySource.set(args.src, rgba);
|
|
157
|
+
this.engine.bindImageTexture(refId, args.imageId, rgba.buffer, rgba.width, rgba.height);
|
|
158
|
+
}).catch((e: Error) => {
|
|
159
|
+
hilogError(`bindImageTexture failed: src=${args.src} err=${e.message}`);
|
|
160
|
+
return Promise.reject(new Error(`bindImageTexture failed: ${e.message}`));
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* 预加载图片(异步)。url=图片地址,id=图片 id,返回尺寸/错误信息。
|
|
166
|
+
* 远程 URL 经 @ohos.net.http 下载为 ArrayBuffer 后再解码(image kit 不支持 http URL)。
|
|
167
|
+
*/
|
|
168
|
+
preLoadImage(args: { url: string, id: number }): Promise<ImageInfo> {
|
|
169
|
+
return decodeImageToRGBA(args.url).then((rgba: RgbaPixels) => {
|
|
170
|
+
// 缓存解码结果,供后续 bindImageTexture/texImage2D 复用
|
|
171
|
+
this.decodedImagesBySource.set(args.url, rgba);
|
|
172
|
+
this.engine.cacheImage(args.id, rgba.buffer, rgba.width, rgba.height);
|
|
173
|
+
let info: ImageInfo = { width: rgba.width, height: rgba.height };
|
|
174
|
+
return info;
|
|
175
|
+
}).catch((e: Error) => {
|
|
176
|
+
hilogError(`preLoadImage failed: url=${args.url} err=${e.message}`);
|
|
177
|
+
let info: ImageInfo = { width: 0, height: 0, error: e.message };
|
|
178
|
+
return info;
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** 获取设备信息(异步)。返回 JSON 字符串。 */
|
|
183
|
+
getDeviceInfo(args: string): Promise<DeviceInfoRet> {
|
|
184
|
+
try {
|
|
185
|
+
const data = this.engine.getDeviceInfo(args);
|
|
186
|
+
let ret: DeviceInfoRet = { data };
|
|
187
|
+
return Promise.resolve(ret);
|
|
188
|
+
} catch (e) {
|
|
189
|
+
hilogError(`getDeviceInfo failed: args=${args}`);
|
|
190
|
+
let ret: DeviceInfoRet = { data: '' };
|
|
191
|
+
return Promise.resolve(ret);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** 注册自定义字体族(同步)。fontNames 与 fontFiles 一一对应。 */
|
|
196
|
+
addFontFamily(fontNames: string[], fontFiles: string[]): void {
|
|
197
|
+
try {
|
|
198
|
+
this.engine.addFontFamily(fontNames, fontFiles);
|
|
199
|
+
} catch (e) {
|
|
200
|
+
hilogError('addFontFamily failed');
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** 枚举系统字体族名(同步)。 */
|
|
205
|
+
getFontNames(): string[] {
|
|
206
|
+
try {
|
|
207
|
+
return this.engine.getFontNames();
|
|
208
|
+
} catch (e) {
|
|
209
|
+
hilogError('getFontNames failed');
|
|
210
|
+
return [];
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** 设置额外字体目录(同步)。 */
|
|
215
|
+
setExtraFontLocation(location: string): void {
|
|
216
|
+
try {
|
|
217
|
+
this.engine.setExtraFontLocation(location);
|
|
218
|
+
} catch (e) {
|
|
219
|
+
hilogError(`setExtraFontLocation failed: location=${location}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** 设置日志级别(异步)。 */
|
|
224
|
+
setLogLevel(level: number): Promise<void> {
|
|
225
|
+
try {
|
|
226
|
+
this.engine.setLogLevel(level);
|
|
227
|
+
return Promise.resolve();
|
|
228
|
+
} catch (e) {
|
|
229
|
+
hilogError(`setLogLevel failed: level=${level}`);
|
|
230
|
+
return Promise.reject(new Error('setLogLevel failed'));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** 重置组件(异步)。 */
|
|
235
|
+
resetComponent(refId: string): Promise<void> {
|
|
236
|
+
try {
|
|
237
|
+
this.engine.resetComponent(refId);
|
|
238
|
+
return Promise.resolve();
|
|
239
|
+
} catch (e) {
|
|
240
|
+
hilogError(`resetComponent failed: refId=${refId}`);
|
|
241
|
+
return Promise.reject(new Error(`resetComponent failed: ${refId}`));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** WebGL 纹理上传(同步)。图片须先由 GImage/preLoadImage 解码并按 src 缓存。 */
|
|
246
|
+
texImage2D(refId: string, target: number, level: number, internalformat: number, format: number,
|
|
247
|
+
type: number, src: string): void {
|
|
248
|
+
try {
|
|
249
|
+
const rgba = this.decodedImagesBySource.get(src);
|
|
250
|
+
if (!rgba) {
|
|
251
|
+
hilogError(`texImage2D image is not preloaded: src=${src}`);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
this.engine.texImage2D(refId, target, level, internalformat, format, type,
|
|
255
|
+
rgba.buffer, rgba.width, rgba.height);
|
|
256
|
+
} catch (e) {
|
|
257
|
+
hilogError(`texImage2D failed: refId=${refId}`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** WebGL 纹理子图上传(同步,鸿蒙新增分支)。命令 '105,9,...'。 */
|
|
262
|
+
texSubImage2D(refId: string, target: number, level: number, xoffset: number, yoffset: number,
|
|
263
|
+
format: number, type: number, src: string): void {
|
|
264
|
+
try {
|
|
265
|
+
const rgba = this.decodedImagesBySource.get(src);
|
|
266
|
+
if (!rgba) {
|
|
267
|
+
hilogError(`texSubImage2D image is not preloaded: src=${src}`);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
this.engine.texSubImage2D(refId, target, level, xoffset, yoffset, format, type,
|
|
271
|
+
rgba.buffer, rgba.width, rgba.height);
|
|
272
|
+
} catch (e) {
|
|
273
|
+
hilogError(`texSubImage2D failed: refId=${refId}`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** 设置组件 alpha(异步,原实现为空壳,鸿蒙同样空实现)。 */
|
|
278
|
+
setAlpha(refId: string, alpha: number): Promise<void> {
|
|
279
|
+
// 原生 Android/iOS 端 setAlpha 即为空实现,鸿蒙保持一致。
|
|
280
|
+
return Promise.resolve();
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* 解码图片为 RGBA 像素缓冲。
|
|
286
|
+
* 支持:沙箱/资源/$rawfile 路径、http(s) 远程 URL、data: URL。
|
|
287
|
+
* 远程 URL 先经 @ohos.net.http 下载为 ArrayBuffer(image.createImageSource 不支持 http)。
|
|
288
|
+
*/
|
|
289
|
+
async function decodeImageToRGBA(src: string): Promise<RgbaPixels> {
|
|
290
|
+
let buf: ArrayBuffer;
|
|
291
|
+
if (src.startsWith('http://') || src.startsWith('https://')) {
|
|
292
|
+
buf = await downloadToBuffer(src);
|
|
293
|
+
} else if (src.startsWith('data:')) {
|
|
294
|
+
buf = dataUrlToBuffer(src);
|
|
295
|
+
} else {
|
|
296
|
+
buf = await readLocalToBuffer(src);
|
|
297
|
+
}
|
|
298
|
+
let imageSource: image.ImageSource | null = null;
|
|
299
|
+
let pixelMap: image.PixelMap | null = null;
|
|
300
|
+
try {
|
|
301
|
+
imageSource = image.createImageSource(buf);
|
|
302
|
+
pixelMap = await imageSource.createPixelMap({ desiredPixelFormat: image.PixelMapFormat.RGBA_8888 });
|
|
303
|
+
const info = await pixelMap.getImageInfo();
|
|
304
|
+
const width: number = info.size.width;
|
|
305
|
+
const height: number = info.size.height;
|
|
306
|
+
const byteCount: number = pixelMap.getPixelBytesNumber();
|
|
307
|
+
const pixels: ArrayBuffer = new ArrayBuffer(byteCount);
|
|
308
|
+
await pixelMap.readPixelsToBuffer(pixels);
|
|
309
|
+
let ret: RgbaPixels = { buffer: pixels, width, height };
|
|
310
|
+
return ret;
|
|
311
|
+
} catch (e) {
|
|
312
|
+
hilog.error(DOMAIN, TAG, `decodeImageToRGBA failed: src=${src} err=${(e as Error).message}`);
|
|
313
|
+
throw e instanceof Error ? e : new Error(String(e));
|
|
314
|
+
} finally {
|
|
315
|
+
releaseDecodedImage(pixelMap, imageSource);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function releaseDecodedImage(pixelMap: image.PixelMap | null, imageSource: image.ImageSource | null): void {
|
|
320
|
+
if (pixelMap) {
|
|
321
|
+
try {
|
|
322
|
+
pixelMap.release();
|
|
323
|
+
} catch (e) {
|
|
324
|
+
hilog.error(DOMAIN, TAG, `release PixelMap failed: ${(e as Error).message}`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
if (imageSource) {
|
|
328
|
+
try {
|
|
329
|
+
imageSource.release();
|
|
330
|
+
} catch (e) {
|
|
331
|
+
hilog.error(DOMAIN, TAG, `release ImageSource failed: ${(e as Error).message}`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/** 远程图片下载为 ArrayBuffer。 */
|
|
337
|
+
async function downloadToBuffer(url: string): Promise<ArrayBuffer> {
|
|
338
|
+
let httpReq: http.HttpRequest | null = null;
|
|
339
|
+
try {
|
|
340
|
+
httpReq = http.createHttp();
|
|
341
|
+
const resp = await httpReq.request(url, {
|
|
342
|
+
method: http.RequestMethod.GET,
|
|
343
|
+
expectDataType: http.HttpDataType.ARRAY_BUFFER,
|
|
344
|
+
});
|
|
345
|
+
httpReq.destroy();
|
|
346
|
+
return resp.result as ArrayBuffer;
|
|
347
|
+
} catch (e) {
|
|
348
|
+
if (httpReq) {
|
|
349
|
+
httpReq.destroy();
|
|
350
|
+
}
|
|
351
|
+
hilog.error(DOMAIN, TAG, `downloadToBuffer failed: url=${url} err=${(e as Error).message}`);
|
|
352
|
+
throw e instanceof Error ? e : new Error(String(e));
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/** data URL(base64)转 ArrayBuffer。 */
|
|
357
|
+
function dataUrlToBuffer(dataUrl: string): ArrayBuffer {
|
|
358
|
+
try {
|
|
359
|
+
const base64Helper = new util.Base64Helper();
|
|
360
|
+
const commaIdx = dataUrl.indexOf(',');
|
|
361
|
+
const base64 = commaIdx >= 0 ? dataUrl.substring(commaIdx + 1) : dataUrl;
|
|
362
|
+
const bytes = base64Helper.decodeSync(base64);
|
|
363
|
+
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;
|
|
364
|
+
} catch (e) {
|
|
365
|
+
hilog.error(DOMAIN, TAG, `dataUrlToBuffer failed: err=${(e as Error).message}`);
|
|
366
|
+
throw e instanceof Error ? e : new Error(String(e));
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/** 本地/资源文件读取为 ArrayBuffer。 */
|
|
371
|
+
async function readLocalToBuffer(path: string): Promise<ArrayBuffer> {
|
|
372
|
+
let file: fs.File | null = null;
|
|
373
|
+
try {
|
|
374
|
+
file = fs.openSync(path, fs.OpenMode.READ_ONLY);
|
|
375
|
+
const stat = fs.statSync(path);
|
|
376
|
+
const buf: ArrayBuffer = new ArrayBuffer(stat.size);
|
|
377
|
+
await fs.read(file.fd, buf);
|
|
378
|
+
fs.closeSync(file);
|
|
379
|
+
return buf;
|
|
380
|
+
} catch (e) {
|
|
381
|
+
// 关闭文件也可能抛异常,且门禁要求 @kit 调用必须在 try/catch 内并用 hilog 记录。
|
|
382
|
+
if (file) {
|
|
383
|
+
try {
|
|
384
|
+
fs.closeSync(file);
|
|
385
|
+
} catch (ce) {
|
|
386
|
+
hilog.error(DOMAIN, TAG, `readLocalToBuffer close failed: path=${path} err=${(ce as Error).message}`);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
hilog.error(DOMAIN, TAG, `readLocalToBuffer failed: path=${path} err=${(e as Error).message}`);
|
|
390
|
+
throw e instanceof Error ? e : new Error(String(e));
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function errorMessage(e: Object): string {
|
|
395
|
+
try {
|
|
396
|
+
return e instanceof Error ? e.message : String(e);
|
|
397
|
+
} catch (_ignored) {
|
|
398
|
+
return 'unknown';
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function hilogError(msg: string, e?: Object): void {
|
|
403
|
+
if (e === undefined) {
|
|
404
|
+
hilog.error(DOMAIN, TAG, msg);
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
hilog.error(DOMAIN, TAG, `${msg} err=${errorMessage(e)}`);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/** extendCallNative 同步返回值。 */
|
|
411
|
+
interface ExtendCallNativeResult {
|
|
412
|
+
result: string;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/** drawCanvas2Canvas 参数。 */
|
|
416
|
+
interface DrawCanvas2CanvasMap {
|
|
417
|
+
srcComponentId: string;
|
|
418
|
+
dstComponentId: string;
|
|
419
|
+
tw: number;
|
|
420
|
+
th: number;
|
|
421
|
+
sx: number;
|
|
422
|
+
sy: number;
|
|
423
|
+
sw: number;
|
|
424
|
+
sh: number;
|
|
425
|
+
dx: number;
|
|
426
|
+
dy: number;
|
|
427
|
+
dw: number;
|
|
428
|
+
dh: number;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/** RGBA 像素解码结果。 */
|
|
432
|
+
interface RgbaPixels {
|
|
433
|
+
buffer: ArrayBuffer;
|
|
434
|
+
width: number;
|
|
435
|
+
height: number;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/** preLoadImage 返回的图片信息。 */
|
|
439
|
+
interface ImageInfo {
|
|
440
|
+
width: number;
|
|
441
|
+
height: number;
|
|
442
|
+
error?: string;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/** getDeviceInfo 返回。 */
|
|
446
|
+
interface DeviceInfoRet {
|
|
447
|
+
data: string;
|
|
448
|
+
}
|