fix(share): 截图改用 componentSnapshot 抓Web组件渲染像素(修WebGL黑屏) + 剪贴板图片改fd写入
- BridgeGameContainer: 大厅Web加 .id('web_lobby'),子游戏Web加 .id('web_subgame'),
供 componentSnapshot.get 按组件id定位
- WebSlot: captureCanvas 整体替换为 componentSnapshot.get + image.ImagePacker.packToData,
规避 WebGL canvas.toDataURL 黑屏;packToData 为 packing 的非废弃替代(§API13+);
删除 parseJsString(仅被旧 captureCanvas 内联用,新实现不需要)
- ShareProvider: writeClipboardImage 改用 fileIo.openSync 取 fd 再
createImageSource(fd),修复传路径字符串可能失败问题;补 import fileIo
API签名核对(devecocli docs):
componentSnapshot.get(id, options) → Promise<PixelMap>(deprecated API18,仍可用)
ImagePacker.packToData(pixelMap, PackingOption) → Promise<ArrayBuffer>(API13+,WARN无error)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
75b7c7fb07
commit
1902e4e5be
@@ -347,6 +347,7 @@ export struct BridgeGameContainer {
|
||||
Stack() {
|
||||
// 大厅 Web(常驻)
|
||||
Web({ src: this.lobbySrc(), controller: this.controllerL })
|
||||
.id('web_lobby')
|
||||
.javaScriptAccess(true).domStorageAccess(true).fileAccess(true)
|
||||
.mixedMode(MixedMode.All).cacheMode(CacheMode.None)
|
||||
.geolocationAccess(true).zoomAccess(false)
|
||||
@@ -372,6 +373,7 @@ export struct BridgeGameContainer {
|
||||
// 子游戏 Web(临时,subgameUrl 非空时存在)
|
||||
if (this.subgameUrl !== '' && this.slotS !== undefined) {
|
||||
Web({ src: this.subgameUrl, controller: this.slotS.controller })
|
||||
.id('web_subgame')
|
||||
.javaScriptAccess(true).domStorageAccess(true).fileAccess(true)
|
||||
.mixedMode(MixedMode.All).cacheMode(CacheMode.None)
|
||||
.geolocationAccess(true).zoomAccess(false)
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { webview } from '@kit.ArkWeb';
|
||||
import { componentSnapshot } from '@kit.ArkUI';
|
||||
import { image } from '@kit.ImageKit';
|
||||
import { util } from '@kit.ArkTS';
|
||||
import { common } from '@kit.AbilityKit';
|
||||
import { BusinessError } from '@kit.BasicServicesKit';
|
||||
import { BridgeController, WebviewControllerAdapter, BridgeJsLoader } from 'feature_bridge';
|
||||
@@ -225,48 +228,35 @@ export class WebSlot {
|
||||
}
|
||||
|
||||
/**
|
||||
* 截取本槽 Web 内的 canvas(截图分享)。脚本在 H5 内 toDataURL,返回 dataURL;
|
||||
* runJavaScript 回调的 result 是 JSON 字符串(外层带引号),需 JSON.parse 去引号还原。
|
||||
* 非激活/出错一律返回空串(由 ShareProvider 降级网页分享,绝不抛错)。
|
||||
* 截取本槽 Web 组件已渲染像素(截图分享)。改用 componentSnapshot 抓组件合成结果,
|
||||
* 规避 WebGL canvas.toDataURL 黑屏问题;打包为 image/jpeg 的 dataURL 返回。
|
||||
* 非激活/出错一律返回空串(由 ShareProvider 降级,绝不抛错)。canvasId 参数保留兼容、当前忽略。
|
||||
*/
|
||||
private captureCanvas(canvasId: string): Promise<string> {
|
||||
if (!this.active || this.disposed) {
|
||||
return Promise.resolve('');
|
||||
}
|
||||
const sel: string = canvasId !== ''
|
||||
? `document.getElementById(${JSON.stringify(canvasId)}).toDataURL('image/png')`
|
||||
: `document.querySelector('canvas').toDataURL('image/jpeg',0.8)`;
|
||||
const script: string = `(function(){try{return ${sel};}catch(e){return '';}})()`;
|
||||
return new Promise<string>((resolve: (v: string) => void) => {
|
||||
try {
|
||||
this.controller.runJavaScript(script, (err: BusinessError, result: string) => {
|
||||
if (err) {
|
||||
WebSlot.log.w(`[${this.role}] captureCanvas runJavaScript error: ${err.message}`);
|
||||
resolve('');
|
||||
return;
|
||||
}
|
||||
resolve(WebSlot.parseJsString(result));
|
||||
const compId: string = `web_${this.role}`;
|
||||
return componentSnapshot.get(compId, { waitUntilRenderFinished: true })
|
||||
.then((pm: image.PixelMap): Promise<string> => {
|
||||
const packer: image.ImagePacker = image.createImagePacker();
|
||||
return packer.packToData(pm, { format: 'image/jpeg', quality: 90 })
|
||||
.then((buf: ArrayBuffer): string => {
|
||||
const b64: string = new util.Base64Helper().encodeToStringSync(new Uint8Array(buf));
|
||||
WebSlot.log.i(`[${this.role}] snapshot ok bytes=${buf.byteLength}`);
|
||||
return `data:image/jpeg;base64,${b64}`;
|
||||
})
|
||||
.finally(() => {
|
||||
pm.release();
|
||||
packer.release();
|
||||
});
|
||||
} catch (e) {
|
||||
WebSlot.log.w(`[${this.role}] captureCanvas failed: ${(e as BusinessError).message}`);
|
||||
resolve('');
|
||||
}
|
||||
})
|
||||
.catch((e: Object): string => {
|
||||
WebSlot.log.w(`[${this.role}] componentSnapshot failed: ${(e as Error).message}`);
|
||||
return '';
|
||||
});
|
||||
}
|
||||
|
||||
/** runJavaScript 返回值是 JSON 字符串(字符串结果外层带引号);去引号还原,异常返回空串。 */
|
||||
private static parseJsString(result: string): string {
|
||||
if (result === undefined || result === null || result === '' || result === 'null') {
|
||||
return '';
|
||||
}
|
||||
try {
|
||||
const parsed: Object = JSON.parse(result) as Object;
|
||||
return typeof parsed === 'string' ? parsed as string : '';
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
private flushPending(): void {
|
||||
if (this.pendingWebdata !== undefined) {
|
||||
this.bridgeCtrl?.callHandler(OutboundHandlers.GetWebData, this.pendingWebdata);
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
import { systemShare } from '@kit.ShareKit';
|
||||
import { uniformTypeDescriptor as utd } from '@kit.ArkData';
|
||||
import { fileUri } from '@kit.CoreFileKit';
|
||||
import { fileUri, fileIo } from '@kit.CoreFileKit';
|
||||
import { common, bundleManager } from '@kit.AbilityKit';
|
||||
import { BusinessError, pasteboard } from '@kit.BasicServicesKit';
|
||||
import { image } from '@kit.ImageKit';
|
||||
@@ -316,7 +316,14 @@ export class ShareProvider implements CapabilityProvider {
|
||||
|
||||
/** 图片写剪贴板(尽力;对方能否粘贴 PIXELMAP 不可保证,systemImage 才是可靠交付)。 */
|
||||
private writeClipboardImage(filePath: string): void {
|
||||
const source: image.ImageSource = image.createImageSource(filePath);
|
||||
let file: fileIo.File;
|
||||
try {
|
||||
file = fileIo.openSync(filePath, fileIo.OpenMode.READ_ONLY);
|
||||
} catch (e) {
|
||||
this.log.w(`clipboard image open failed: ${(e as Error).message}`);
|
||||
return;
|
||||
}
|
||||
const source: image.ImageSource = image.createImageSource(file.fd);
|
||||
source.createPixelMap().then((pm: image.PixelMap) => {
|
||||
let data: pasteboard.PasteData;
|
||||
try {
|
||||
@@ -326,14 +333,15 @@ export class ShareProvider implements CapabilityProvider {
|
||||
pm.release();
|
||||
return;
|
||||
}
|
||||
// pm 须存活到 setData 完成(createData 可能持引用),故在 setData settle 后再 release。
|
||||
pasteboard.getSystemPasteboard().setData(data)
|
||||
.then(() => this.log.i('clipboard image set ok'))
|
||||
.catch((e: BusinessError) => this.log.w(`clipboard image failed: ${e.code} ${e.message}`))
|
||||
.finally(() => pm.release());
|
||||
}).catch((e: BusinessError) => {
|
||||
this.log.w(`createPixelMap failed: ${e.code} ${e.message}`);
|
||||
}).finally(() => {
|
||||
source.release();
|
||||
fileIo.closeSync(file);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user