✨(frontend) add french touch effect
Enhanced the FaceLandmarksProcessor to include a new 'french' effect, allowing users to add a beret image to detected faces. Updated the EffectsConfiguration component to toggle this effect and modified localization files to reflect the change from 'mustache' to 'french'.
This commit is contained in:
committed by
aleb_the_flash
parent
1d5aebcfdc
commit
7405011cd2
BIN
src/frontend/public/assets/beret.png
Normal file
BIN
src/frontend/public/assets/beret.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
@@ -49,7 +49,7 @@ export class FaceLandmarksProcessor implements BackgroundProcessorInterface {
|
||||
// Effect images
|
||||
glassesImage?: HTMLImageElement
|
||||
mustacheImage?: HTMLImageElement
|
||||
|
||||
beretImage?: HTMLImageElement
|
||||
constructor(opts: BackgroundOptions) {
|
||||
this.name = 'face_landmarks'
|
||||
this.options = opts
|
||||
@@ -65,6 +65,10 @@ export class FaceLandmarksProcessor implements BackgroundProcessorInterface {
|
||||
this.mustacheImage = new Image()
|
||||
this.mustacheImage.src = '/assets/mustache.png'
|
||||
this.mustacheImage.crossOrigin = 'anonymous'
|
||||
|
||||
this.beretImage = new Image()
|
||||
this.beretImage.src = '/assets/beret.png'
|
||||
this.beretImage.crossOrigin = 'anonymous'
|
||||
}
|
||||
|
||||
static get isSupported() {
|
||||
@@ -172,7 +176,8 @@ export class FaceLandmarksProcessor implements BackgroundProcessorInterface {
|
||||
rightPoint: { x: number; y: number },
|
||||
image: HTMLImageElement,
|
||||
widthScale: number,
|
||||
heightScale: number
|
||||
heightScale: number,
|
||||
yOffset: number = 0
|
||||
) {
|
||||
// Calculate distance between points
|
||||
const distance = Math.sqrt(
|
||||
@@ -186,7 +191,7 @@ export class FaceLandmarksProcessor implements BackgroundProcessorInterface {
|
||||
|
||||
// Calculate center position between points
|
||||
const centerX = (leftPoint.x + rightPoint.x) / 2
|
||||
const centerY = (leftPoint.y + rightPoint.y) / 2
|
||||
const centerY = (leftPoint.y + rightPoint.y) / 2 + yOffset
|
||||
|
||||
// Draw image
|
||||
this.outputCanvasCtx!.save()
|
||||
@@ -237,21 +242,29 @@ export class FaceLandmarksProcessor implements BackgroundProcessorInterface {
|
||||
this.outputCanvasCtx!.lineWidth = 2
|
||||
|
||||
for (const face of this.faceLandmarkerResult.faceLandmarks) {
|
||||
// Find eye landmarks (indices 33 and 263 are the left and right eye corners)
|
||||
// Find eye landmarks
|
||||
const leftEye = face[468]
|
||||
const rightEye = face[473]
|
||||
|
||||
// Find mouth landmarks for mustache (indices 0 and 17 are the left and right corners of the mouth)
|
||||
// Find mouth landmarks for mustache
|
||||
const leftMoustache = face[92]
|
||||
const rightMoustache = face[322]
|
||||
|
||||
// Find forehead landmarks for beret
|
||||
const leftForehead = face[103]
|
||||
const rightForehead = face[332]
|
||||
|
||||
if (leftEye && rightEye && this.options.showGlasses) {
|
||||
this.drawEffect(leftEye, rightEye, this.glassesImage!, 2.5, 0.7)
|
||||
}
|
||||
|
||||
if (leftMoustache && rightMoustache && this.options.showMustache) {
|
||||
if (leftMoustache && rightMoustache && this.options.showFrench) {
|
||||
this.drawEffect(leftMoustache, rightMoustache, this.mustacheImage!, 1.5, 0.5)
|
||||
}
|
||||
|
||||
if (leftForehead && rightForehead && this.options.showFrench) {
|
||||
this.drawEffect(leftForehead, rightForehead, this.beretImage!, 2.1, 0.7, -0.1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ export type BackgroundOptions = {
|
||||
blurRadius?: number
|
||||
imagePath?: string
|
||||
showGlasses?: boolean
|
||||
showMustache?: boolean
|
||||
showFrench?: boolean
|
||||
}
|
||||
|
||||
export interface ProcessorSerialized {
|
||||
|
||||
@@ -15,7 +15,7 @@ import { BlurOnStrong } from '@/components/icons/BlurOnStrong'
|
||||
import { useTrackToggle } from '@livekit/components-react'
|
||||
import { Loader } from '@/primitives/Loader'
|
||||
import { useSyncAfterDelay } from '@/hooks/useSyncAfterDelay'
|
||||
import { RiProhibited2Line, RiGlassesLine, RiEmotionLine } from '@remixicon/react'
|
||||
import { RiProhibited2Line, RiGlassesLine, RiGoblet2Fill } from '@remixicon/react'
|
||||
import { useHasFaceLandmarksAccess } from '../../hooks/useHasFaceLandmarksAccess'
|
||||
|
||||
enum BlurRadius {
|
||||
@@ -142,7 +142,7 @@ export const EffectsConfiguration = ({
|
||||
|
||||
const tooltipLabel = (type: ProcessorType, options: BackgroundOptions) => {
|
||||
if (type === ProcessorType.FACE_LANDMARKS) {
|
||||
const effect = options.showGlasses ? 'glasses' : 'mustache'
|
||||
const effect = options.showGlasses ? 'glasses' : 'french'
|
||||
return t(`faceLandmarks.${effect}.${isSelected(type, options) ? 'clear' : 'apply'}`)
|
||||
}
|
||||
return t(`${type}.${isSelected(type, options) ? 'clear' : 'apply'}`)
|
||||
@@ -151,19 +151,19 @@ export const EffectsConfiguration = ({
|
||||
const getFaceLandmarksOptions = () => {
|
||||
const processor = getProcessor()
|
||||
if (processor?.serialize().type === ProcessorType.FACE_LANDMARKS) {
|
||||
return processor.serialize().options as { showGlasses?: boolean; showMustache?: boolean }
|
||||
return processor.serialize().options as { showGlasses?: boolean; showFrench?: boolean }
|
||||
}
|
||||
return { showGlasses: false, showMustache: false }
|
||||
return { showGlasses: false, showFrench: false }
|
||||
}
|
||||
|
||||
const toggleFaceLandmarkEffect = async (effect: 'glasses' | 'mustache') => {
|
||||
const toggleFaceLandmarkEffect = async (effect: 'glasses' | 'french') => {
|
||||
const currentOptions = getFaceLandmarksOptions()
|
||||
const newOptions = {
|
||||
...currentOptions,
|
||||
[effect === 'glasses' ? 'showGlasses' : 'showMustache']: !currentOptions[effect === 'glasses' ? 'showGlasses' : 'showMustache']
|
||||
[effect === 'glasses' ? 'showGlasses' : 'showFrench']: !currentOptions[effect === 'glasses' ? 'showGlasses' : 'showFrench']
|
||||
}
|
||||
|
||||
if (!newOptions.showGlasses && !newOptions.showMustache) {
|
||||
if (!newOptions.showGlasses && !newOptions.showFrench) {
|
||||
// If both effects are off stop the processor
|
||||
await clearEffect()
|
||||
} else {
|
||||
@@ -356,11 +356,11 @@ export const EffectsConfiguration = ({
|
||||
variant="bigSquare"
|
||||
aria-label={tooltipLabel(ProcessorType.FACE_LANDMARKS, {
|
||||
showGlasses: true,
|
||||
showMustache: false,
|
||||
showFrench: false,
|
||||
})}
|
||||
tooltip={tooltipLabel(ProcessorType.FACE_LANDMARKS, {
|
||||
showGlasses: true,
|
||||
showMustache: false,
|
||||
showFrench: false,
|
||||
})}
|
||||
isDisabled={processorPendingReveal}
|
||||
onChange={async () => await toggleFaceLandmarkEffect('glasses')}
|
||||
@@ -373,18 +373,18 @@ export const EffectsConfiguration = ({
|
||||
variant="bigSquare"
|
||||
aria-label={tooltipLabel(ProcessorType.FACE_LANDMARKS, {
|
||||
showGlasses: false,
|
||||
showMustache: true,
|
||||
showFrench: true,
|
||||
})}
|
||||
tooltip={tooltipLabel(ProcessorType.FACE_LANDMARKS, {
|
||||
showGlasses: false,
|
||||
showMustache: true,
|
||||
showFrench: true,
|
||||
})}
|
||||
isDisabled={processorPendingReveal}
|
||||
onChange={async () => await toggleFaceLandmarkEffect('mustache')}
|
||||
isSelected={getFaceLandmarksOptions().showMustache}
|
||||
data-attr="toggle-mustache"
|
||||
onChange={async () => await toggleFaceLandmarkEffect('french')}
|
||||
isSelected={getFaceLandmarksOptions().showFrench}
|
||||
data-attr="toggle-french"
|
||||
>
|
||||
<RiEmotionLine />
|
||||
<RiGoblet2Fill />
|
||||
</ToggleButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -156,9 +156,9 @@
|
||||
"apply": "Brille hinzufügen",
|
||||
"clear": "Brille entfernen"
|
||||
},
|
||||
"mustache": {
|
||||
"apply": "Schnurrbart hinzufügen",
|
||||
"clear": "Schnurrbart entfernen"
|
||||
"french": {
|
||||
"apply": "Französische Touch hinzufügen",
|
||||
"clear": "Französische Touch entfernen"
|
||||
}
|
||||
},
|
||||
"experimental": "Experimentelle Funktion. Eine v2 kommt für vollständige Browserunterstützung und verbesserte Qualität."
|
||||
|
||||
@@ -155,9 +155,9 @@
|
||||
"apply": "Add Glasses",
|
||||
"clear": "Remove Glasses"
|
||||
},
|
||||
"mustache": {
|
||||
"apply": "Add Mustache",
|
||||
"clear": "Remove Mustache"
|
||||
"french": {
|
||||
"apply": "Add French touch",
|
||||
"clear": "Remove French touch"
|
||||
}
|
||||
},
|
||||
"experimental": "Experimental feature. A v2 is coming for full browser support and improved quality."
|
||||
|
||||
@@ -155,9 +155,9 @@
|
||||
"apply": "Ajouter des lunettes",
|
||||
"clear": "Retirer les lunettes"
|
||||
},
|
||||
"mustache": {
|
||||
"apply": "Ajouter une moustache",
|
||||
"clear": "Retirer la moustache"
|
||||
"french": {
|
||||
"apply": "Ajouter la touche française",
|
||||
"clear": "Retirer la touche française"
|
||||
}
|
||||
},
|
||||
"experimental": "Fonctionnalité expérimentale. Une v2 arrive pour un support complet des navigateurs et une meilleure qualité."
|
||||
|
||||
@@ -155,9 +155,9 @@
|
||||
"apply": "Bril toevoegen",
|
||||
"clear": "Bril verwijderen"
|
||||
},
|
||||
"mustache": {
|
||||
"apply": "Snor toevoegen",
|
||||
"clear": "Snor verwijderen"
|
||||
"french": {
|
||||
"apply": "Franse stijl toevoegen",
|
||||
"clear": "Franse stijl verwijderen"
|
||||
}
|
||||
},
|
||||
"experimental": "Experimentele functie. Een v2 komt eraan voor volledige browserondersteuning en verbeterde kwaliteit."
|
||||
|
||||
Reference in New Issue
Block a user