mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-14 17:43:04 +00:00
O recorte do card 'Time de você' falhava em navegadores endurecidos (IronFox/Tor desligam WebAssembly e WebGL por padrão) com a copy errada de 'tente outra foto'; o motor MediaPipe exige ambos (o grafo pede kGpuService mesmo com delegate CPU). Agora o adapter sonda WASM/WebGL antes do download e devolve 'unsupported' com mensagem acionável; a entrada de inferência é capada em 1600 e a foto passa pelo loadCardPhoto (foto de 61MP que falhava agora conclui). O enquadramento automático volta ao S15 (bbox cobre a janela): o S18 mirava a caixa do rosto em 103px e deixava o busto inteiro pequeno na janela. Saem o detector de rosto (asset removido), frameCardPhotoOnFace, a referência 103px, o piso 0.2 e a âncora S20 (inerte sob o cover). S16/S17 intactos.
104 lines
3.3 KiB
TypeScript
104 lines
3.3 KiB
TypeScript
'use client'
|
|
|
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
|
|
import { removeCardPhotoBackground } from '@/components/cards/cardCutout'
|
|
import { harmonizeCardCutout } from '@/components/cards/cardPhotoHarmonyCanvas'
|
|
import type { CardRect } from '@/lib/cardModels'
|
|
import { frameCardPhotoOnBbox, type CardPhotoTransform } from '@/lib/cardPhotoTransform'
|
|
|
|
export type CardCutoutState =
|
|
| { status: 'idle' }
|
|
| { status: 'processing'; phase: 'download' | 'process'; ratio: number }
|
|
| {
|
|
status: 'ready'
|
|
canvas: HTMLCanvasElement
|
|
/**
|
|
* S17 — tone-harmonized variant of `canvas` (same size/alpha). Equals
|
|
* `canvas` when the photo is already on tone; `null` when the reference
|
|
* could not be read (the composer then hides the control).
|
|
*/
|
|
harmonized: HTMLCanvasElement | null
|
|
width: number
|
|
height: number
|
|
/** Initial framing: cutout top on the slot top, centered (S15). */
|
|
transform: CardPhotoTransform
|
|
}
|
|
| { status: 'error'; reason: 'engine' | 'empty' | 'unsupported' }
|
|
|
|
/**
|
|
* S15 — owns the cutout lifecycle for the team model: engine import, progress,
|
|
* degenerate framing (fail-closed), retry with the same file and run-id guards
|
|
* so a stale inference or an unmount never writes state. The engine itself is a
|
|
* page-lifetime singleton in the adapter (re-creating the wasm session per open
|
|
* would cost seconds); each result's masks/bitmap are released there.
|
|
*
|
|
* S17 — the ready state also carries the harmonized variant, computed once
|
|
* before the first ready paint (default ON must not flash the raw photo).
|
|
*/
|
|
export const useCardCutout = (photoWindow: CardRect | undefined, harmonyReferenceSrc?: string) => {
|
|
const [state, setState] = useState<CardCutoutState>({ status: 'idle' })
|
|
const fileRef = useRef<File | null>(null)
|
|
const runRef = useRef(0)
|
|
|
|
useEffect(
|
|
() => () => {
|
|
runRef.current += 1
|
|
},
|
|
[],
|
|
)
|
|
|
|
const start = useCallback(
|
|
async (file: File) => {
|
|
runRef.current += 1
|
|
const run = runRef.current
|
|
fileRef.current = file
|
|
setState({ status: 'processing', phase: 'download', ratio: 0 })
|
|
|
|
const result = await removeCardPhotoBackground(file, (progress) => {
|
|
if (runRef.current !== run) return
|
|
setState({ status: 'processing', phase: progress.phase, ratio: progress.ratio })
|
|
})
|
|
if (runRef.current !== run) return
|
|
|
|
if (!result.ok) {
|
|
setState({ status: 'error', reason: result.reason })
|
|
return
|
|
}
|
|
|
|
const transform = photoWindow
|
|
? frameCardPhotoOnBbox(
|
|
{ width: result.width, height: result.height },
|
|
photoWindow,
|
|
result.bbox,
|
|
)
|
|
: null
|
|
if (!transform) {
|
|
setState({ status: 'error', reason: 'empty' })
|
|
return
|
|
}
|
|
|
|
const harmonized = harmonyReferenceSrc
|
|
? await harmonizeCardCutout(result.canvas, harmonyReferenceSrc)
|
|
: null
|
|
if (runRef.current !== run) return
|
|
|
|
setState({
|
|
status: 'ready',
|
|
canvas: result.canvas,
|
|
harmonized,
|
|
width: result.width,
|
|
height: result.height,
|
|
transform,
|
|
})
|
|
},
|
|
[photoWindow, harmonyReferenceSrc],
|
|
)
|
|
|
|
const retry = useCallback(() => {
|
|
const file = fileRef.current
|
|
if (file) void start(file)
|
|
}, [start])
|
|
|
|
return { state, start, retry }
|
|
}
|