新增美颜
This commit is contained in:
@@ -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-100(0 为关闭) */
|
||||
lipstickStrength: number
|
||||
/** 口红颜色(hex) */
|
||||
lipstickColor: string
|
||||
/** 腮红浓度 0-100(0 为关闭) */
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user