修改:优化手机上效果图点云显示
continuous-integration/drone/push Build was killed
continuous-integration/drone/promote/prod Build is passing

This commit is contained in:
Linzm committed 2026-10-20 16:59:06 +08:00
1 parent 17908ab309
commit 605bd894c5
4 files changed
+87 -30

No files matched your search

@@ -64,8 +64,8 @@ let renderer: any = null
let sm: any = null
let raf = 0
let lastTs = 0
let cssW = 320
let cssH = 320
let cssW = 340
let cssH = 340
let logoFromCache = false
let resultKey = ''
@@ -186,12 +186,23 @@ function preloadResult(url: string) {
if (!url || !renderer || !sm) return
resultKey = url
sm.setResultPreloadStatus('loading')
// 效果图保留完整画面做点云(去背景会在手机上采空,表现为「没有效果图点云」)
renderer.textures
.load(url, { removeBackground: true })
.load(url, { removeBackground: false })
.then((entry: any) => {
if (destroyed || !sm) return
if (!entry || !entry.pointCloudBuffer) {
console.error('[ImagePointCloud] result cloud buffer missing')
sm.setResultPreloadStatus('error')
return
}
renderer.setResultTexture(entry)
sm.setResultPreloadStatus('ready')
// 若状态机已进入 RESULT_*(异步晚到),立刻切到效果图点云
const phase = String(sm.getPhase?.() || '')
if (phase.startsWith('RESULT')) {
renderer.transitionToCloud(entry)
}
})
.catch((err: any) => {
console.error('[ImagePointCloud] result preload fail', err)
@@ -310,11 +321,15 @@ function init() {
}
},
onSwitchToResult: () => {
const entry = renderer.textures.get(resultKey || props.resultUrl)
const key = resultKey || props.resultUrl
const entry = key ? renderer.textures.get(key) : null
if (entry && entry.pointCloudBuffer) {
renderer.transitionToCloud(entry)
renderer.setResultTexture(entry)
return
}
// 缓冲晚到时由 preloadResult 成功回调补切
if (key) preloadResult(key)
},
onHandoff: () => emit('handoff'),
})
@@ -124,6 +124,8 @@ function createStateMachine(hooks) {
phase !== PHASE.RESULT_PRELOADING &&
phase !== PHASE.SUBJECT_DETECTING &&
phase !== PHASE.SUBJECT_OUTLINE &&
phase !== PHASE.SUBJECT_CLOUD_GRAY &&
phase !== PHASE.SUBJECT_CLOUD_COLORIZE &&
phase !== PHASE.SUBJECT_CLOUD_COLOR &&
phase !== PHASE.SUBJECT_POINT_CLOUD
) {
@@ -292,6 +294,7 @@ function createStateMachine(hooks) {
if (!morphing) assembly = 1
subjectProgress = 1
outlineProgress = Math.max(0.35, 1 - Math.min(1, elapsed / 2400) * 0.55)
if (tryStartResultTransition()) return getUniforms()
if (!morphing && elapsed >= TIMING.SUBJECT_GRAY_MS) {
enter(PHASE.SUBJECT_CLOUD_COLORIZE)
}
@@ -307,6 +310,7 @@ function createStateMachine(hooks) {
if (!morphing) assembly = 1
subjectProgress = 1
outlineProgress = 0.35
if (tryStartResultTransition()) return getUniforms()
if (t >= 1) {
colorProgress = 1
enter(PHASE.SUBJECT_CLOUD_COLOR)
@@ -115,7 +115,8 @@ function createTextureManager(gl, canvas, scaleCanvas, viewportW, viewportH) {
}
/**
* 加载图片并严格在 onload 之后再交给后续逻辑(避免 TMA 未定义行为告警)
* 加载图片并严格在 onload 之后再交给后续逻辑。
* http(s) 优先 fetch→blob,避免手机 WebView 跨域导致 getImageData 采点失败。
*/
function loadImage(src) {
return new Promise(function (resolve, reject) {
@@ -123,38 +124,73 @@ function createTextureManager(gl, canvas, scaleCanvas, viewportW, viewportH) {
reject(new Error('empty src'))
return
}
let settled = false
const img = new Image()
// 跨域效果图需 CORS,否则无法 getImageData 采样
if (/^https?:\/\//i.test(src)) {
img.crossOrigin = 'anonymous'
}
function finishOk() {
if (settled) return
setTimeout(function () {
function bindImage(imgSrc, revokeUrl) {
let settled = false
const img = new Image()
if (/^https?:\/\//i.test(imgSrc) && imgSrc.indexOf('blob:') !== 0) {
img.crossOrigin = 'anonymous'
}
function finishOk() {
if (settled) return
const w = img.width || 0
const h = img.height || 0
if (!w || !h) {
setTimeout(function () {
if (settled) return
const w = img.width || 0
const h = img.height || 0
if (!w || !h) {
settled = true
if (revokeUrl) {
try { URL.revokeObjectURL(revokeUrl) } catch (e) {}
}
reject(new Error('image empty after onload'))
return
}
settled = true
reject(new Error('image empty after onload'))
return
}
resolve({
image: img,
path: src,
width: w,
height: h,
revokeUrl: revokeUrl || '',
})
}, 0)
}
function finishErr(err) {
if (settled) return
settled = true
resolve({ image: img, path: src, width: w, height: h })
}, 0)
if (revokeUrl) {
try { URL.revokeObjectURL(revokeUrl) } catch (e) {}
}
reject(err || new Error('image load fail'))
}
img.onload = finishOk
img.onerror = finishErr
img.src = imgSrc
}
function finishErr(err) {
if (settled) return
settled = true
reject(err || new Error('image load fail'))
// 远程图:先 blob 同源化,再采样(手机端效果图点云依赖此路径)
if (/^https?:\/\//i.test(src) && typeof fetch === 'function') {
fetch(src, { mode: 'cors', credentials: 'omit', cache: 'force-cache' })
.then(function (res) {
if (!res || !res.ok) throw new Error('fetch image http ' + (res && res.status))
return res.blob()
})
.then(function (blob) {
if (!blob || !blob.size) throw new Error('empty image blob')
const objUrl = URL.createObjectURL(blob)
bindImage(objUrl, objUrl)
})
.catch(function () {
// CORS 拉流失败时退回 <img crossOrigin>
bindImage(src, null)
})
return
}
img.onload = finishOk
img.onerror = finishErr
img.src = src
bindImage(src, null)
})
}
+3 -1
View File
@@ -333,10 +333,12 @@ function pollStatus() {
imageId.value = parsed.imageId
sessionStorage.setItem('douyin_image_id', parsed.imageId)
}
// 不再 10s 强切预览:会掐掉 RESULT_CLOUD_* 效果图点云段
// 正常走点云状态机 handoff;仅作兜底(给足灰/彩点云播放时间)
if (fadeTimer) clearTimeout(fadeTimer)
fadeTimer = setTimeout(() => {
if (!handoffDone) enterResultView()
}, 10000)
}, 60000)
if (pollTimer) {
clearInterval(pollTimer)
pollTimer = null