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