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() {
|
Stack() {
|
||||||
// 大厅 Web(常驻)
|
// 大厅 Web(常驻)
|
||||||
Web({ src: this.lobbySrc(), controller: this.controllerL })
|
Web({ src: this.lobbySrc(), controller: this.controllerL })
|
||||||
|
.id('web_lobby')
|
||||||
.javaScriptAccess(true).domStorageAccess(true).fileAccess(true)
|
.javaScriptAccess(true).domStorageAccess(true).fileAccess(true)
|
||||||
.mixedMode(MixedMode.All).cacheMode(CacheMode.None)
|
.mixedMode(MixedMode.All).cacheMode(CacheMode.None)
|
||||||
.geolocationAccess(true).zoomAccess(false)
|
.geolocationAccess(true).zoomAccess(false)
|
||||||
@@ -372,6 +373,7 @@ export struct BridgeGameContainer {
|
|||||||
// 子游戏 Web(临时,subgameUrl 非空时存在)
|
// 子游戏 Web(临时,subgameUrl 非空时存在)
|
||||||
if (this.subgameUrl !== '' && this.slotS !== undefined) {
|
if (this.subgameUrl !== '' && this.slotS !== undefined) {
|
||||||
Web({ src: this.subgameUrl, controller: this.slotS.controller })
|
Web({ src: this.subgameUrl, controller: this.slotS.controller })
|
||||||
|
.id('web_subgame')
|
||||||
.javaScriptAccess(true).domStorageAccess(true).fileAccess(true)
|
.javaScriptAccess(true).domStorageAccess(true).fileAccess(true)
|
||||||
.mixedMode(MixedMode.All).cacheMode(CacheMode.None)
|
.mixedMode(MixedMode.All).cacheMode(CacheMode.None)
|
||||||
.geolocationAccess(true).zoomAccess(false)
|
.geolocationAccess(true).zoomAccess(false)
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import { webview } from '@kit.ArkWeb';
|
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 { common } from '@kit.AbilityKit';
|
||||||
import { BusinessError } from '@kit.BasicServicesKit';
|
import { BusinessError } from '@kit.BasicServicesKit';
|
||||||
import { BridgeController, WebviewControllerAdapter, BridgeJsLoader } from 'feature_bridge';
|
import { BridgeController, WebviewControllerAdapter, BridgeJsLoader } from 'feature_bridge';
|
||||||
@@ -225,46 +228,33 @@ export class WebSlot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 截取本槽 Web 内的 canvas(截图分享)。脚本在 H5 内 toDataURL,返回 dataURL;
|
* 截取本槽 Web 组件已渲染像素(截图分享)。改用 componentSnapshot 抓组件合成结果,
|
||||||
* runJavaScript 回调的 result 是 JSON 字符串(外层带引号),需 JSON.parse 去引号还原。
|
* 规避 WebGL canvas.toDataURL 黑屏问题;打包为 image/jpeg 的 dataURL 返回。
|
||||||
* 非激活/出错一律返回空串(由 ShareProvider 降级网页分享,绝不抛错)。
|
* 非激活/出错一律返回空串(由 ShareProvider 降级,绝不抛错)。canvasId 参数保留兼容、当前忽略。
|
||||||
*/
|
*/
|
||||||
private captureCanvas(canvasId: string): Promise<string> {
|
private captureCanvas(canvasId: string): Promise<string> {
|
||||||
if (!this.active || this.disposed) {
|
if (!this.active || this.disposed) {
|
||||||
return Promise.resolve('');
|
return Promise.resolve('');
|
||||||
}
|
}
|
||||||
const sel: string = canvasId !== ''
|
const compId: string = `web_${this.role}`;
|
||||||
? `document.getElementById(${JSON.stringify(canvasId)}).toDataURL('image/png')`
|
return componentSnapshot.get(compId, { waitUntilRenderFinished: true })
|
||||||
: `document.querySelector('canvas').toDataURL('image/jpeg',0.8)`;
|
.then((pm: image.PixelMap): Promise<string> => {
|
||||||
const script: string = `(function(){try{return ${sel};}catch(e){return '';}})()`;
|
const packer: image.ImagePacker = image.createImagePacker();
|
||||||
return new Promise<string>((resolve: (v: string) => void) => {
|
return packer.packToData(pm, { format: 'image/jpeg', quality: 90 })
|
||||||
try {
|
.then((buf: ArrayBuffer): string => {
|
||||||
this.controller.runJavaScript(script, (err: BusinessError, result: string) => {
|
const b64: string = new util.Base64Helper().encodeToStringSync(new Uint8Array(buf));
|
||||||
if (err) {
|
WebSlot.log.i(`[${this.role}] snapshot ok bytes=${buf.byteLength}`);
|
||||||
WebSlot.log.w(`[${this.role}] captureCanvas runJavaScript error: ${err.message}`);
|
return `data:image/jpeg;base64,${b64}`;
|
||||||
resolve('');
|
})
|
||||||
return;
|
.finally(() => {
|
||||||
}
|
pm.release();
|
||||||
resolve(WebSlot.parseJsString(result));
|
packer.release();
|
||||||
});
|
});
|
||||||
} catch (e) {
|
})
|
||||||
WebSlot.log.w(`[${this.role}] captureCanvas failed: ${(e as BusinessError).message}`);
|
.catch((e: Object): string => {
|
||||||
resolve('');
|
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 {
|
private flushPending(): void {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
import { systemShare } from '@kit.ShareKit';
|
import { systemShare } from '@kit.ShareKit';
|
||||||
import { uniformTypeDescriptor as utd } from '@kit.ArkData';
|
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 { common, bundleManager } from '@kit.AbilityKit';
|
||||||
import { BusinessError, pasteboard } from '@kit.BasicServicesKit';
|
import { BusinessError, pasteboard } from '@kit.BasicServicesKit';
|
||||||
import { image } from '@kit.ImageKit';
|
import { image } from '@kit.ImageKit';
|
||||||
@@ -316,7 +316,14 @@ export class ShareProvider implements CapabilityProvider {
|
|||||||
|
|
||||||
/** 图片写剪贴板(尽力;对方能否粘贴 PIXELMAP 不可保证,systemImage 才是可靠交付)。 */
|
/** 图片写剪贴板(尽力;对方能否粘贴 PIXELMAP 不可保证,systemImage 才是可靠交付)。 */
|
||||||
private writeClipboardImage(filePath: string): void {
|
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) => {
|
source.createPixelMap().then((pm: image.PixelMap) => {
|
||||||
let data: pasteboard.PasteData;
|
let data: pasteboard.PasteData;
|
||||||
try {
|
try {
|
||||||
@@ -326,14 +333,15 @@ export class ShareProvider implements CapabilityProvider {
|
|||||||
pm.release();
|
pm.release();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// pm 须存活到 setData 完成(createData 可能持引用),故在 setData settle 后再 release。
|
|
||||||
pasteboard.getSystemPasteboard().setData(data)
|
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}`))
|
.catch((e: BusinessError) => this.log.w(`clipboard image failed: ${e.code} ${e.message}`))
|
||||||
.finally(() => pm.release());
|
.finally(() => pm.release());
|
||||||
}).catch((e: BusinessError) => {
|
}).catch((e: BusinessError) => {
|
||||||
this.log.w(`createPixelMap failed: ${e.code} ${e.message}`);
|
this.log.w(`createPixelMap failed: ${e.code} ${e.message}`);
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
source.release();
|
source.release();
|
||||||
|
fileIo.closeSync(file);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user