新增美颜

This commit is contained in:
Your Name
2026-07-03 16:51:02 +08:00
parent eb0efba739
commit 1777fe7d8b
17 changed files with 19413 additions and 3 deletions
+164
View File
@@ -0,0 +1,164 @@
/**
* 视频通话美颜设置
*
* 全部效果(磨皮/美白/红润/瘦脸/口红/腮红)默认由本地管线处理:
* MediaPipe Face Landmarker(开源免费)识别人脸关键点 + WebGL/Canvas 逐帧渲染,
* 经 getUserMedia 拦截注入通话视频流(见 utils/beauty/ 目录),预览所见即所得。
*
* 仅当浏览器不支持 WebGL 时,磨皮/美白/红润回退到腾讯云 TRTC 免费 BasicBeauty
* 插件(TUICallEngine.setBeautyLevel)。不依赖付费的腾讯特效 SDK。
*/
/** 高级美颜(本地 AI 处理)设置 */
export interface AdvancedBeautySettings {
/** 总开关;通话中途开启需重新开关摄像头才能接管视频流 */
enabled: boolean
/** 瘦脸强度 0-100 */
slimStrength: number
/** 口红浓度 0-1000 为关闭) */
lipstickStrength: number
/** 口红颜色(hex */
lipstickColor: string
/** 腮红浓度 0-1000 为关闭) */
blushStrength: number
/** 腮红颜色(hex */
blushColor: string
}
export interface CallBeautySettings {
/** 是否启用基础美颜(关闭时以全 0 参数下发,引擎会停用 BasicBeauty 插件) */
enabled: boolean
/** 磨皮算法:0 光滑(TRTCBeautyStyleSmooth/ 1 自然(TRTCBeautyStyleNature */
style: number
/** 磨皮级别 0-9 */
beautyLevel: number
/** 美白级别 0-9 */
whitenessLevel: number
/** 红润级别 0-9 */
ruddinessLevel: number
/** 高级美颜(瘦脸/口红/腮红,本地 AI) */
advanced: AdvancedBeautySettings
}
export const BEAUTY_STYLE_SMOOTH = 0
export const BEAUTY_STYLE_NATURE = 1
export const BEAUTY_LEVEL_MAX = 9
const STORAGE_KEY = 'tcm_call_beauty_settings'
export const defaultAdvancedBeautySettings = (): AdvancedBeautySettings => ({
enabled: false,
slimStrength: 30,
lipstickStrength: 0,
lipstickColor: '#c94f5e',
blushStrength: 0,
blushColor: '#e88193'
})
export const defaultBeautySettings = (): CallBeautySettings => ({
enabled: false,
style: BEAUTY_STYLE_NATURE,
beautyLevel: 5,
whitenessLevel: 3,
ruddinessLevel: 2,
advanced: defaultAdvancedBeautySettings()
})
const clampLevel = (value: unknown): number => {
const num = Math.round(Number(value))
if (!Number.isFinite(num)) return 0
return Math.min(BEAUTY_LEVEL_MAX, Math.max(0, num))
}
const clampPercent = (value: unknown, fallback: number): number => {
const num = Math.round(Number(value))
if (!Number.isFinite(num)) return fallback
return Math.min(100, Math.max(0, num))
}
const normalizeHexColor = (value: unknown, fallback: string): string => {
if (typeof value === 'string' && /^#[0-9a-fA-F]{6}$/.test(value)) return value
return fallback
}
/** 高级美颜是否有任一效果实际开启 */
export function hasAnyAdvancedEffect(advanced: AdvancedBeautySettings): boolean {
return (
advanced.enabled &&
(advanced.slimStrength > 0 || advanced.lipstickStrength > 0 || advanced.blushStrength > 0)
)
}
/** 全部美颜(基础 + 高级)是否有任一效果实际开启(决定是否接管摄像头流) */
export function hasAnyBeautyEffect(settings: CallBeautySettings): boolean {
const basicOn =
settings.enabled &&
(settings.beautyLevel > 0 || settings.whitenessLevel > 0 || settings.ruddinessLevel > 0)
return basicOn || hasAnyAdvancedEffect(settings.advanced)
}
export function loadBeautySettings(): CallBeautySettings {
const defaults = defaultBeautySettings()
try {
const raw = localStorage.getItem(STORAGE_KEY)
if (!raw) return defaults
const parsed = JSON.parse(raw)
const advDefaults = defaults.advanced
const adv = parsed?.advanced ?? {}
return {
enabled: Boolean(parsed?.enabled),
style:
parsed?.style === BEAUTY_STYLE_SMOOTH
? BEAUTY_STYLE_SMOOTH
: BEAUTY_STYLE_NATURE,
beautyLevel: clampLevel(parsed?.beautyLevel ?? defaults.beautyLevel),
whitenessLevel: clampLevel(parsed?.whitenessLevel ?? defaults.whitenessLevel),
ruddinessLevel: clampLevel(parsed?.ruddinessLevel ?? defaults.ruddinessLevel),
advanced: {
enabled: Boolean(adv?.enabled),
slimStrength: clampPercent(adv?.slimStrength, advDefaults.slimStrength),
lipstickStrength: clampPercent(adv?.lipstickStrength, advDefaults.lipstickStrength),
lipstickColor: normalizeHexColor(adv?.lipstickColor, advDefaults.lipstickColor),
blushStrength: clampPercent(adv?.blushStrength, advDefaults.blushStrength),
blushColor: normalizeHexColor(adv?.blushColor, advDefaults.blushColor)
}
}
} catch {
return defaults
}
}
export function saveBeautySettings(settings: CallBeautySettings) {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(settings))
} catch {
// localStorage 不可用时静默忽略,仅本次会话生效
}
}
/**
* 将美颜设置应用到 TUICallEngine 实例。
* 引擎内部对 setBeautyLevel 自带 try-catch(摄像头未开启等场景不会抛错),
* 这里只兜底引擎不存在 / 接口缺失的情况。
*/
export async function applyBeautyToEngine(
engine: any,
settings: CallBeautySettings
): Promise<boolean> {
if (!engine || typeof engine.setBeautyLevel !== 'function') {
return false
}
const enabled = settings.enabled
try {
await engine.setBeautyLevel({
style: settings.style === BEAUTY_STYLE_SMOOTH ? BEAUTY_STYLE_SMOOTH : BEAUTY_STYLE_NATURE,
beautyLevel: enabled ? clampLevel(settings.beautyLevel) : 0,
whitenessLevel: enabled ? clampLevel(settings.whitenessLevel) : 0,
ruddinessLevel: enabled ? clampLevel(settings.ruddinessLevel) : 0
})
return true
} catch (error) {
console.warn('[call-beauty] 应用美颜设置失败', error)
return false
}
}