facemesh / index.html
joserpagil's picture
Upload 4 files
1995730 verified
Raw
History Blame Contribute Delete
64.4 kB
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1.0, user-scalable=no">
<title>
facemesh v220725 x joserpagil@gmail.com
</title>
<style>
body {
margin : 0 ;
overflow : hidden ;
font-family : Arial ;
font-size : 2vmax ;
}
button {
font-family : Arial ;
font-size : 2vmax ;
}
.close_button {
position : absolute ;
top : 0.5vmax ;
right : 1vmax ;
cursor : pointer ;
color : white ;
}
.window {
resize : both ;
display : block ;
overflow : hidden ;
position : absolute ;
left : 0 ;
top : 1vh ;
width : 46vh ;
margin : auto ;
z-index : 1 ;
background-color : #888 ;
border : 2px solid black ;
padding : 0 ;
}
.window_title {
font-family : Arial ;
font-size : 3vh ;
padding : 0.5vmax ;
cursor : move ;
z-index : 10 ;
background-color : black ;
color : white ;
resize : both ;
height : 4vh ;
}
.window_content {
padding : 1vmax ;
}
.animate {
-webkit-animation : animatezoom 0.6s ;
animation : animatezoom 0.6s ;
}
@-webkit-keyframes animatezoom {
from {-webkit-transform: scale(0)}
to {-webkit-transform: scale(1)}
}
@keyframes animatezoom {
from {transform: scale(0)}
to {transform: scale(1)}
}
.switch {
vertical-align : bottom ;
background-color : transparent ;
position: relative;
display: inline-block;
width: 2em;
height: 1em;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: .4s;
transition: .4s;
}
.slider:before {
position: absolute;
content: "";
height: 1em;
width: 1em;
left: 0vw;
top: 0vw;
bottom: 1vw;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
}
input:checked + .slider {
background-color: green;
}
input:focus + .slider {
box-shadow: 0 0 1px #2196F3;
}
input:checked + .slider:before {
-webkit-transform: translateX(1em);
-ms-transform: translateX(1em);
transform: translateX(1em);
}
.slider.round {
border-radius: 5vw;
}
.slider.round:before {
border-radius: 50%;
}
.loading-circle {
position : absolute ;
border : 6px solid rgba(0, 0, 0, 0) ;
border-top : 6px solid rgb(221, 144, 29) ;
border-radius : 50% ;
}
#loading0 {
top: calc(50% - 45px);
left: calc(50% - 45px);
width: 90px;
height: 90px;
animation: spin 1.5s linear infinite;
}
#loading1 {
top: calc(50% - 60px);
left: calc(50% - 60px);
width: 120px;
height: 120px;
animation: spin 2s linear infinite;
}
#loading2 {
top: calc(50% - 75px);
left: calc(50% - 75px);
width: 150px;
height: 150px;
animation: spin 2.5s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
#throbber_div {
position : absolute ;
left : 0px ;
top : 0px ;
width : 100% ;
height : 100% ;
}
table {
width : 100% ;
}
#wind_of_the_west_slider {
width : 100% ;
}
</style>
</head>
<body>
<div id = 'log_div'></div >
<div id = 'tools_window' class = 'window animate' style = 'z-index : 2 ;'>
<div id = 'tools_window_title' class = 'window_title' onmousedown ='Drag_element ( this )'>facemesh v220725
<a class = 'close_button' onclick='tools_window . style . display = "none"'>&times;</a>
</div>
<div class = 'window_content'>
FPS
<label id = 'fps_label'>calculating ...</label>
<br>
<hr>
<table>
<tr>
<td>
Camera
</td>
<td>
<button id = 'switch_camera_button' onclick = 'Switch_camera ()'>switch</button>
</td>
</tr>
<tr>
<td>
<label>flip x</label>
</td>
<td>
<label class = 'switch'>
<input id = 'flip_y_switch' onclick = 'Flip_x_switch_change ()' type = 'checkbox' checked>
<span class = 'slider round'></span>
</label>
</td>
</tr>
<tr>
<td>
<label>flip y</label>
</td>
<td>
<label class = 'switch'>
<input id = 'flip_y_switch' onclick = 'Flip_y_switch_change ()' type = 'checkbox' unchecked>
<span class = 'slider round'></span>
</label>
</td>
</tr>
</table>
<hr>
Show
<br>
<table>
<tr>
<td>
<label>eyes</label>
</td>
<td>
<label class = 'switch'>
<input id = 'eyes_switch' onclick = 'Eyes_switch_change ()' type = 'checkbox' checked>
<span class = 'slider round'></span>
</label>
</td>
<td>
<input id = 'eyes_color_input' type = 'color' value = '#ff0000'>
</td>
</tr>
<tr>
<td>
<label>lips</label>
</td>
<td>
<label class = 'switch'>
<input id = 'lips_switch' onclick = 'Lips_switch_change ()' type = 'checkbox' unchecked>
<span class = 'slider round'></span>
</label>
</td>
<td>
<input id = 'lips_color_input' type = 'color' value = '#ff0000'>
</td>
</tr>
<tr>
<td>
<label>hair</label>
</td>
<td>
<label class = 'switch'>
<input id = 'hair_switch' onclick = 'Hair_switch_change ()' type = 'checkbox' unchecked>
<span class = 'slider round'></span>
</label>
</td>
<td>
<input id = 'hair_color_input' type = 'color' value = '#ffff00'>
</td>
</tr>
<tr>
<td>
<label>beard</label>
</td>
<td>
<label class = 'switch'>
<input id = 'beard_switch' onclick = 'Beard_switch_change ()' type = 'checkbox' unchecked>
<span class = 'slider round'></span>
</label>
</td>
<td>
<input id = 'beard_color_input' type = 'color' value = '#ffffff'>
</td>
</tr>
<tr>
<td>
<label>mask</label>
</td>
<td>
<label class = 'switch'>
<input id = 'mask_switch' onclick = 'Mask_switch_change ()' type = 'checkbox'>
<span class = 'slider round'></span>
</label>
</td>
</tr>
</table>
<hr>
Wind of the west
<br>
<input type = 'range' min = '0' max = '100' value = '0' id = 'wind_of_the_west_slider'>
</div>
</div>
<div id = 'throbber_div'>
<div id = 'loading0' class = 'loading-circle'></div>
<div id = 'loading1' class = 'loading-circle'></div>
<div id = 'loading2' class = 'loading-circle'></div>
</div>
<script>
// Log messages over the display
// usefull when developing on mobile
const Log = args => {
log_div . innerHTML += args . html + '<br>'
}
addEventListener ( 'error' , errorEvent => { Log ( { html : errorEvent . lineno + ' , ' + errorEvent . colno + ' - ' + errorEvent . message } ) } )
const Hair_indexes = [ 127 , 162 , 21 , 54 , 103 , 67 , 109 , 10 , 338 , 297 , 332 , 284 , 251 , 389 , 356 ]
const Beard_indexes = [ 454 , 323 , 361 , 288 , 397 , 365 , 379 , 378 , 400 , 377 , 152 , 148 , 176 , 149 , 150 , 136 , 172 , 58 , 132 , 93 , 234 ]
// FPS vars
let start = 0
let nof = 0
const Number_of_iterations = 10
const Minimal_interface_size = 64
const constraints = {
audio : false ,
video : {
facingMode : 'user'
}
}
let mediaStream = null
let flip_x = true
let flip_y = false
let canvas
let context
let frame_canvas
let frame_context
let scaled_canvas
let scaled_context
let model = null
let antigravity
let gravity
let hair_state = null
let beard_state = null
let point = null
let first_points = []
let tearable_elements = []
let mouse = { x : 0 , y : 0 }
let old_mouse = { x : 0 , y : 0 }
let delta_mouse = { x : 0 , y : 0 }
let image = null
let video = null
let file_button
let show_mask = false
let show_hair = false
let hair_color = 0xffff00
let show_beard = false
let beard_color = 0xffffff
let show_eyes = true
let eyes_color = 0xff0000
let show_lips = false
let lips_color = 0xff0000
let wind_factor = 0
class Vector {
constructor ( args ) {
this . x = 0
this . y = 0
this . z = 0
if ( args != null ) {
if ( 'x' in args ) this . x = args . x
if ( 'y' in args ) this . y = args . y
if ( 'z' in args ) this . z = args . z
}
}
set_vector ( args ) {
if ( args == null ) return
if ( 'vector' in args ) {
this . x = args . vector . x
this . y = args . vector . y
this . z = args . vector . z
}
}
clone ( args ) {
if ( args == null ) return
if ( 'x' in args ) this . x = args . x
if ( 'y' in args ) this . y = args . y
if ( 'z' in args ) this . z = args . z
}
multiply_scalar ( args ) {
this . x *= args . scalar
this . y *= args . scalar
this . z *= args . scalar
return this
}
reset ( args ) {
this . x = 0
this . y = 0
this . z = 0
}
add ( args ) {
this . x += args . vector . x
this . y += args . vector . y
this . z += args . vector . z
return this
}
subtract ( args ) {
this . x -= args . vector . x
this . y -= args . vector . y
this . z -= args . vector . z
return this
}
length ( args ) {
return Math . sqrt ( this . x * this . x + this . y * this . y + this . z * this . z )
}
normalize ( args ) {
let length = this . length ()
if ( length > 0 ) {
this . x /= length
this . y /= length
this . z /= length
}
return this
}
negate ( args ) {
this . x *= - 1
this . y *= - 1
this . z *= - 1
return this;
}
}
class Rectangle extends Vector {
constructor ( args ) {
super ( args )
this . width = 0
this . height = 0
this . ants = false
this . visible = true
if ( args != null ) {
if ( 'ants' in args ) this . ants = args . ants
if ( 'right' in args ) this . width = args . right - this . x
if ( 'bottom' in args ) this . height = args . bottom - this . y
if ( 'width' in args ) this . width = args . width
if ( 'height' in args ) this . height = args . height
}
}
clone ( args ) {
if ( args == null ) return
super . clone ( args )
if ( 'width' in args ) this . width = args . width
if ( 'height' in args ) this . height = args . height
if ( 'ants' in args ) this . ants = args . ants
}
get right () {
return this . x + this . width
}
get bottom () {
return this . y + this . height
}
get center_x () {
return this . width / 2 + this . x
}
get center_y () {
return this . height / 2 + this . y
}
set_rectangle ( args ) {
if ( args == null ) return
if ( 'rectangle' in args ) {
super . set_vector ( { vector : args . rectangle } )
this . width = args . rectangle . width
this . height = args . rectangle . height
this . ants = args . rectangle . ants
}
}
contains ( args ) {
if ( args == null ) return false
let x = null
let y = null
if ( 'x' in args ) x = args . x
if ( 'y' in args ) y = args . y
if ( x == null || y == null ) return false
return x >= this . x && x <= this . x + this . width &&
y >= this . y && y <= this . y + this . height
}
draw ( args ) {
if ( ! this . visible ) return
let context = args . context
let ants = this . ants
if ( 'ants' in args ) ants = args . ants
context . lineWidth = 2
if ( ants ) context . setLineDash ( [ 8 , 4 ] )
context . strokeStyle = "#000000"
context . strokeRect ( this . x , this . y , this . width , this . height )
if ( ants ) context . setLineDash ( [] )
}
}
class Point extends Vector {
constructor ( args ) {
super ( args )
this . previous_position = new Vector ( { x : this . x , y : this . y } )
this . acceleration = new Vector ()
this . fixed = false
this . visible = true
this . selected = true
if ( args == null ) return
if ( 'acceleration' in args ) this . acceleration = args . acceleration
if ( 'fixed' in args ) this . fixed = args . fixed
if ( 'visible' in args ) this . visible = args . visible
if ( 'selected' in args ) this . selected = args . selected
}
contains ( args ) {
return this . rectangle . contains ( args )
}
handlepointer_down ( args ) {
if ( ! this . contains ( args ) ) return false
return true
}
get rectangle () {
return new Rectangle ( { x : this . x - Minimal_interface_size / 2 ,
y : this . y - Minimal_interface_size / 2 ,
width : Minimal_interface_size ,
height : Minimal_interface_size } )
}
clone ( args ) {
super . clone ( args )
this . previous_position . clone ( args . previous_position )
this . acceleration = args . acceleration
this . fixed = args . fixed
}
draw ( args ) {
if ( this . selected ) context . setLineDash ( [ 8 , 4 ] )
context . strokeStyle = "#000000"
context . beginPath ()
context . ellipse ( this . x , this . y , Minimal_interface_size / 2 , Minimal_interface_size / 2 , Math . PI / 4 , 0 , 2 * Math . PI )
context . stroke ()
if ( this . selected ) context . setLineDash ( [] )
}
moveBy ( args ) {
this . x += args . delta_x
this . y += args . delta_y
this . previous_position . x = this . x
this . previous_position . y = this . y
state . constraints . forEach ( constraint => {
constraint . length = Distance ( { from : constraint . point_b , to : constraint . point_a } )
} )
}
addForce ( args ) {
this . acceleration . x += args . force . x
this . acceleration . y += args . force . y
}
Do_Physics ( args ) {
if ( this . fixed ) return
const delta = args . delta * args . delta
let x = this . x
let y = this . y
this . acceleration . multiply_scalar ( { scalar : delta } )
this . x += x - this . previous_position . x + this . acceleration . x
this . y += y - this . previous_position . y + this . acceleration . y
this . acceleration . reset ()
this . previous_position . x = x
this . previous_position . y = y
}
check_bounds ( args ) {
this . x = Math . max ( 1 , Math . min ( window_width - 1 , this . x ) )
this . y = Math . max ( 1 , Math . min ( window_height - 1 , this . y ) )
if ( this . y >= window_height - 1 ) this . x -= ( this . x - this . previous_position . x + this . acceleration . x )
}
move ( args ) {
if ( this . fixed ) return
this . add ( { vector : args . vector } )
}
}
class Constraint {
constructor ( args ) {
this . point_a = null
this . point_b = null
this . length = null
this . elasticity = 0.5
this . visible = true
this . selected = false
if ( args != null ) {
if ( 'point_a' in args ) this . point_a = args . point_a
if ( 'point_b' in args ) this . point_b = args . point_b
if ( 'length' in args ) this . length = args . length
if ( 'elasticity' in args ) this . elasticity = args . elasticity
if ( 'visible' in args ) this . visible = args . visible
}
if ( this . point_a == null || this . point_b == null ) return
if ( this . length == null ) this . length = Distance ( { from : this . point_a , to : this . point_b } )
}
draw ( args ) {
context . lineWidth = 10
if ( this . selected ) context . setLineDash ( [ 8 , 4 ] )
context . strokeStyle = '#' + args . color . toString ( 16 )
context . beginPath ()
context . moveTo ( this . point_a . x , this . point_a . y )
context . lineTo ( this . point_b . x , this . point_b . y )
context . stroke ()
if ( this . selected ) context . setLineDash ( [] )
}
constrain ( args ) {
let dists = new Vector ( { x : this . point_b . x - this . point_a . x , y : this . point_b . y - this . point_a . y } )
let l = dists . length ()
let delta = l - this . length
dists . normalize ()
let f = dists . multiply_scalar ( { scalar : delta * this . elasticity } )
this . point_a . move ( { vector : f } )
this . point_b . move ( { vector : f . negate () } )
}
}
class State {
static Instances = []
constructor ( args ) {
this . root_points = []
this . points = []
this . constraints = []
this . color = 'black'
this . gravity = new Vector ()
this . threads = []
if ( args ) {
if ( 'color' in args ) this . color = args . color
if ( 'gravity' in args ) this . gravity = args . gravity
}
State . Instances . push ( this )
}
add_threads = args => {
for ( let i = 0 ; i < args . threads_count ; i ++ ) {
const point = new Point ( { fixed : true } )
const point2 = new Point ( { fixed : false } )
const point3 = new Point ( { fixed : false } )
const point4 = new Point ( { fixed : false } )
this . points . push ( point )
this . points . push ( point2 )
this . points . push ( point3 )
this . points . push ( point4 )
this . root_points . push ( point )
this . threads . push ( [ point , point2 , point3 , point4 ] )
this . constraints . push ( new Constraint ( { point_a : point , point_b : point2 , length : 50 } ) )
this . constraints . push ( new Constraint ( { point_a : point2 , point_b : point3 , length : 40 } ) )
this . constraints . push ( new Constraint ( { point_a : point3 , point_b : point4 , length : 30 } ) )
}
}
}
class Curve extends Vector {
constructor ( args ) {
super ( args )
this . points = []
this . control_points = []
this . closed = false
this . tension = .5
this . fillStyle = 'blue'
this . strokeStyle = 'black'
this . lineWidth = 5
if ( args ) {
if ( 'points' in args ) this . points = Array . from ( args . points )
if ( 'control_points' in args ) this . control_points = Array . from ( args . control_points )
if ( 'closed' in args ) this . closed = args . closed
if ( 'tension' in args ) this . tension = args . tension
if ( 'fillStyle' in args ) this . fillStyle = args . fillStyle
if ( 'strokeStyle' in args ) this . strokeStyle = args . strokeStyle
if ( 'lineWidth' in args ) this . lineWidth = args . lineWidth
}
if ( this . closed ) {
this . points . push ( this . points [ 0 ] )
this . points . unshift ( this . points [ this . points . length - 1 ] )
}
if ( this . control_points . length == 0 ) this . build ()
}
build ( args ) {
this . control_points = []
const n = this . points . length - 2 //this . closed ? this . points . length : this . points . length - 2
for ( let i = 0 ; i < n ; i ++ ) {
const control_points = this . get_control_points ( { p0 : this . points [ i ] , p1 : this . points [ i + 1 ] , p2 : this . points [ i + 2 ] , tension : this . tension } )
this . control_points . push ( control_points [ 0 ] , control_points [ 1 ] )
}
}
get_control_points ( args ) {
const d01 = Math . sqrt ( Math . pow ( args . p1 . x - args . p0 . x , 2 ) + Math . pow ( args . p1 . y - args . p0 . y , 2 ) )
const d12 = Math . sqrt ( Math . pow ( args . p2 . x - args . p1 . x , 2 ) + Math . pow ( args . p2 . y - args . p1 . y , 2 ) )
const fa = args . tension * d01 / ( d01 + d12 )
const fb = args . tension - fa
const c1 = new Vector ( { x : args . p1 . x + fa * ( args . p0 . x - args . p2 . x ) , y : args . p1 . y + fa * ( args . p0 . y - args . p2 . y ) } )
const c2 = new Vector ( { x : args . p1 . x - fb * ( args . p0 . x - args . p2 . x ) , y : args . p1 . y - fb * ( args . p0 . y - args . p2 . y ) } )
return [ c1 , c2 ]
}
draw ( args ) {
context . fillStyle = this . fillStyle
context . strokeStyle = this . strokeStyle
context . lineWidth = this . lineWidth
if ( this . closed ) {
context . beginPath ()
context . moveTo ( this . points [ 0 ] . x , this . points [ 0 ] . y )
for ( var i = 1 ; i < this . points . length ; i ++ )
context . bezierCurveTo (
this . control_points [ i * 2 - 1 ] . x ,
this . control_points [ i * 2 - 1 ] . y ,
this . control_points [ i * 2 ] . x ,
this . control_points [ i * 2 ] . y ,
this . points [ i ] . x ,
this . points [ i ] . y )
context . bezierCurveTo (
this . control_points [ this . control_points . length - 1 ] . x ,
this . control_points [ this . control_points . length - 1 ] . y ,
this . control_points [ 0 ] . x ,
this . control_points [ 0 ] . y ,
this . points [ 0 ] . x ,
this . points [ 0 ] . y )
context . fill ()
context . stroke ()
} else {
context . beginPath ()
context . moveTo ( this . points [ 0 ] . x , this . points [ 0 ] . y )
context . quadraticCurveTo (
this . control_points [ 0 ] . x ,
this . control_points [ 0 ] . y ,
this . points [ 1 ] . x ,
this . points [ 1 ] . y )
for ( var i = 0 ; i < this . points . length - 3 ; i ++ )
context . bezierCurveTo (
this . control_points [ i * 2 + 1 ] . x ,
this . control_points [ i * 2 + 1 ] . y ,
this . control_points [ i * 2 + 2 ] . x ,
this . control_points [ i * 2 + 2 ] . y ,
this . points [ i + 2 ] . x ,
this . points [ i + 2 ] . y )
var n = this . control_points . length
context . quadraticCurveTo (
this . control_points [ this . control_points . length - 1 ] . x ,
this . control_points [ this . control_points . length - 1 ] . y ,
this . points [ this . points . length - 1 ] . x ,
this . points [ this . points . length - 1 ] . y )
context . stroke ()
}
}
}
const Hex_to_color = args => {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i . exec ( args . hex )
return result ?
parseInt ( result [ 1 ] , 16 ) * 256 * 256 +
parseInt ( result [ 2 ] , 16 ) * 256 +
parseInt ( result [ 3 ] , 16 )
: null
}
const Flip_x_switch_change = element => {
flip_x = ! flip_x
}
const Flip_y_switch_change = element => {
flip_y = ! flip_y
}
const Eyes_switch_change = element => {
show_eyes = ! show_eyes
}
const Lips_switch_change = element => {
show_lips = ! show_lips
}
const Hair_switch_change = element => {
show_hair = ! show_hair
}
const Beard_switch_change = element => {
show_beard = ! show_beard
}
const Mask_switch_change = element => {
show_mask = ! show_mask
}
const Drag_element = element => {
tearable_elements . push ( element . parentElement )
}
const Pointer_down = args => {
if ( args . event . target == canvas ) {
if ( tools_window . style . display == 'none' ) tools_window . style . display = 'block'
else tools_window . style . display = 'none'
}
}
const Pointer_move = args => {
for ( const drag_element of tearable_elements ) {
drag_element . style . left = drag_element . offsetLeft + delta_mouse . x + 'px'
drag_element . style . top = drag_element . offsetTop + delta_mouse . y + 'px'
}
}
const Pointer_up = args => {
tearable_elements = []
}
const Do_Physics = args => {
//if ( ! play ) return
//return
for ( const state of State . Instances ) {
for ( let iteration = 0 ; iteration < Number_of_iterations ; iteration ++ ) {
state . points . forEach ( point => {
point . addForce ( { force : state . gravity } )
point . addForce ( { force : { x : Math . random () * wind_factor , y : 0 } } )
point . Do_Physics ( { delta : 1 / Number_of_iterations } )
//point . check_bounds ( )
} )
state . constraints . forEach ( constraint => {
constraint . constrain ()
} )
}
}
}
const Distance = args => {
let from = null
let to = null
if ( args == null ) return 0
if ( 'from' in args ) from = args . from
if ( 'to' in args ) to = args . to
if ( from == null || to == null ) return 0
let dx = to . x - from . x
let dy = to . y - from . y
return Math . sqrt ( dx * dx + dy * dy )
}
const Switch_camera = args => {
constraints . video . facingMode = constraints . video . facingMode == 'user' ? 'environment' : 'user'
Open_camera ()
}
const Get_media_stream = async args => {
try {
mediaStream = await navigator . mediaDevices . getUserMedia ( constraints )
video . srcObject = mediaStream
video . onloadedmetadata = event => {
video . play ()
}
} catch ( err ) {
alert ( err . message )
}
}
const Open_camera = async element => {
try {
if ( mediaStream != null && mediaStream . active ) {
const tracks = mediaStream . getVideoTracks ()
tracks . forEach ( track => {
track . stop ()
})
}
video . srcObject = null
await Get_media_stream ()
} catch ( err ) {
alert ( err . message )
}
}
const Draw = async args => {
const now = performance . now ()
if ( video == null ) return
frame_canvas . width = video . videoWidth
frame_canvas . height = video . videoHeight
if ( frame_canvas . width == 0 || frame_canvas . height == 0 ) return
frame_context . save ()
if ( flip_x && flip_y ) {
frame_context . translate ( frame_canvas . width , frame_canvas . height )
frame_context . scale ( - 1 , - 1 )
} else if ( flip_x ) {
frame_context . translate ( frame_canvas . width , 0 )
frame_context . scale ( - 1 , 1 )
} else if ( flip_y ) {
frame_context . translate ( 0 , frame_canvas . height )
frame_context . scale ( 1 , - 1 )
}
frame_context . drawImage ( video , 0 , 0 )
frame_context . restore ()
let factor = Math . max ( frame_canvas . width / canvas . width , frame_canvas . height / canvas . height )
const w = frame_canvas . width / factor
const h = frame_canvas . height / factor
scaled_canvas . width = w
scaled_canvas . height = h
scaled_context . drawImage ( frame_canvas , 0 , 0 , frame_canvas . width , frame_canvas . height , 0 , 0 , w , h )
const faces = await model . estimateFaces ( { input : scaled_canvas } )
if ( throbber_div . style . display != 'none' ) throbber_div . style . display = 'none'
context . clearRect ( 0 , 0 , canvas . width , canvas . height )
context . drawImage ( scaled_canvas , 0 , 0 )
const Draw_polygon = args => {
let first = true
for ( let xy of args . polygon ) {
if ( first ) {
first = false
context . moveTo ( xy [ 0 ] , xy [ 1 ] )
} else context . lineTo ( xy [ 0 ] , xy [ 1 ] )
}
context . closePath ()
}
for ( let face of faces ) {
if ( show_eyes ) {
const Draw_iris = args => {
const radius = Math . abs ( Math . max ( args . iris [ 3 ][ 0 ] - args . iris [ 1 ][ 0 ] , args . iris [ 4 ][ 1 ] - args . iris [ 2 ][ 1 ] ) )/ 2
context . arc ( args . iris [ 0 ][ 0 ] , args . iris [ 0 ][ 1 ] , radius , 0 , 2 * Math . PI )
}
context . fillStyle = '#' + eyes_color . toString ( 16 )
context . globalAlpha = .25
context . beginPath ()
Draw_iris ( { iris : face . annotations . leftEyeIris } )
Draw_iris ( { iris : face . annotations . rightEyeIris } )
context . fill ()
context . globalAlpha = 1
context . save ()
context . beginPath ()
Draw_polygon ( { polygon : face . annotations . leftEyeUpper1 } )
Draw_polygon ( { polygon : face . annotations . leftEyeLower3 } )
Draw_polygon ( { polygon : face . annotations . rightEyeUpper1 } )
Draw_polygon ( { polygon : face . annotations . rightEyeLower3 } )
context . clip ()
context . drawImage ( scaled_canvas , 0 , 0 )
context . restore ()
}
if ( show_lips ) {
let outer_lip = Array . from ( face . annotations . lipsLowerOuter )
outer_lip . pop ()
outer_lip = outer_lip . concat ( face . annotations . lipsUpperOuter . reverse () )
outer_lip . push ( outer_lip [ 0 ] )
let inner_lip = Array . from ( face . annotations . lipsLowerInner )
inner_lip . pop ()
inner_lip = inner_lip . concat ( face . annotations . lipsUpperInner . reverse () )
inner_lip . push ( inner_lip [ 0 ] )
context . fillStyle = '#' + lips_color . toString ( 16 )
context . globalAlpha = .25
context . beginPath ()
Draw_polygon ( { polygon : outer_lip } )
Draw_polygon ( { polygon : inner_lip } )
context . fill ( 'evenodd' )
context . globalAlpha = 1
}
if ( show_mask ) {
context . strokeStyle = 'white'
context . globalAlpha = .25
context . lineWidth = 1
for ( let tis of Triangle_indexes ) {
context . beginPath ()
context . moveTo ( face . scaledMesh [ tis [ 0 ] ] [ 0 ] , face . scaledMesh [ tis [ 0 ] ] [ 1 ] )
context . lineTo ( face . scaledMesh [ tis [ 1 ] ] [ 0 ] , face . scaledMesh [ tis [ 1 ] ] [ 1 ] )
context . lineTo ( face . scaledMesh [ tis [ 2 ] ] [ 0 ] , face . scaledMesh [ tis [ 2 ] ] [ 1 ] )
context . lineTo ( face . scaledMesh [ tis [ 0 ] ] [ 0 ] , face . scaledMesh [ tis [ 0 ] ] [ 1 ] )
context . stroke ()
}
context . fillStyle = 'white'
for ( let xy of face . scaledMesh ) {
context . fillRect ( xy [ 0 ] - 3 , xy [ 1 ] - 3 , 6 , 6 )
}
context . globalAlpha = 1
}
if ( show_hair ) {
if ( ! hair_state ) {
hair_state = new State ( { color : hair_color , gravity : antigravity } )
hair_state . add_threads ( { threads_count : Hair_indexes . length } )
}
let i = 0
for ( let index of Hair_indexes ) {
const point = hair_state . root_points [ i ++ ]
const vector = new Vector ( { x : face . scaledMesh [ index ] [ 0 ] , y : face . scaledMesh [ index ] [ 1 ] , z : face . scaledMesh [ index ] [ 2 ] } )
point . set_vector ( { vector : vector } )
}
for ( const thread of hair_state . threads ) {
const points = []
for ( const point of thread ) {
points . push ( point )
}
const curve = new Curve ( { strokeStyle : '#' + hair_color . toString ( 16 ) , points : points } ) . draw ()
}
}
if ( show_beard ) {
if ( ! beard_state ) {
beard_state = new State ( { color : beard_color , gravity : gravity } )
beard_state . add_threads ( { threads_count : Beard_indexes . length } )
}
let i = 0
for ( let index of Beard_indexes ) {
const point = beard_state . root_points [ i ++ ]
const vector = new Vector ( { x : face . scaledMesh [ index ] [ 0 ] , y : face . scaledMesh [ index ] [ 1 ] , z : face . scaledMesh [ index ] [ 2 ] } )
point . set_vector ( { vector : vector } )
}
for ( const thread of beard_state . threads ) {
const points = []
for ( const point of thread ) {
points . push ( point )
}
const curve = new Curve ( { strokeStyle : '#' + beard_color . toString ( 16 ) , points : points } ) . draw ()
}
}
}
{ // update the fps label
const delta = now - start
if ( start == 0 || delta > 5000 ) {
start = now
nof = 0
}
fps_label . innerHTML = Math . floor ( nof ++ * 1000 / delta )
}
// requestAnimationFrame ( Draw )
}
const Tik = args => {
Do_Physics ()
Draw ()
}
const Load_file = args => {
let filename = args . file . name
let splits = filename . split ( '.' )
let extension = splits . pop () . toLowerCase ()
let name = splits . pop () . split ( '/' ) . pop ()
let fileReader = new FileReader ()
fileReader . filename_name = name
fileReader . onload = function ( event ) {
let base_64 = event . target . result
if ( base_64 . startsWith ( 'data:image/' ) ) {
img = document . createElement ( 'img' )
img . addEventListener ( 'load' , function ( event ) {
image = img
} )
img . src = base_64
} else if ( base_64 . startsWith ( 'data:video/' ) ) {
body_max_dx = 0
video = document . createElement ( 'video' )
video . muted = true
video . autoplay = false
video . controls = true
video . oncanplaythrough = () => {
frame_canvas . width = video . videoWidth
frame_canvas . height = video . videoHeight
video . play ()
};
video . onended = () => {
video . play ()
}
video . src = base_64
}
}
fileReader . readAsDataURL ( args . file )
}
const Load_files = args => {
for ( let i = 0 ; i < args . files . length ; i ++ )
Load_file ( { at : Pointers . position , file : args . files [ i ] } )
}
const Allow_drop = event => {
event . preventDefault ()
}
const Drop = event => {
event . preventDefault ()
if ( event . dataTransfer . items )
for ( let i = 0 ; i < event . dataTransfer . items . length ; i++ ) {
if ( event . dataTransfer . items [ i ] .kind === 'file' )
Load_file ( { file : event . dataTransfer . items [ i ] . getAsFile () } )
}
else
Load_files ( { files : event . dataTransfer . files } )
}
const Main = async args => {
file_button = document . createElement ( 'input' )
file_button . type = 'file'
document . ondrop = Drop
document . ondragover = Allow_drop
canvas = document . createElement ( 'canvas' )
canvas . width = window . innerWidth
canvas . height = window . innerHeight
context = canvas . getContext ( '2d' )
document . body . appendChild ( canvas )
frame_canvas = document . createElement ( 'canvas' )
frame_context = frame_canvas . getContext ( '2d' )
scaled_canvas = document . createElement ( 'canvas' )
scaled_context = scaled_canvas . getContext ( '2d' )
video = document . createElement ( 'video' )
video . muted = true
video . setAttribute ( "playsinline" , "true" )
Open_camera ()
model = await faceLandmarksDetection . load ()
antigravity = new Vector ( { x : 0 , y : - 10 } )
gravity = new Vector ( { x : 0 , y : 10 } )
state = new State ()
window . addEventListener ( 'pointerdown' , event => {
mouse = { x : event . x , y : event . y }
delta_mouse = { x : mouse . x - old_mouse . x , y : mouse . y - old_mouse . y }
old_mouse = { x : mouse . x , y : mouse . y }
Pointer_down ( { event : event } )
} )
window . addEventListener ( 'touchstart' , event => {
//event . preventDefault ()
mouse = { x : event . touches [ 0 ] . clientX , y : event . touches [ 0 ] . clientY }
delta_mouse = { x : mouse . x - old_mouse . x , y : mouse . y - old_mouse . y }
old_mouse = { x : mouse . x , y : mouse . y }
Pointer_down ( { event : event } )
} )
window . addEventListener ( 'pointermove' , event => {
mouse = { x : event . x , y : event . y }
delta_mouse = { x : mouse . x - old_mouse . x , y : mouse . y - old_mouse . y }
Pointer_move ( { event : event } )
old_mouse = { x : mouse . x , y : mouse . y }
} )
window . addEventListener ( 'touchmove' , event => {
//event . preventDefault ()
mouse = { x : event . touches [ 0 ] . clientX , y : event . touches [ 0 ] . clientY }
delta_mouse = { x : mouse . x - old_mouse . x , y : mouse . y - old_mouse . y }
Pointer_move ( { event : event } )
old_mouse = { x : mouse . x , y : mouse . y }
} )
window . addEventListener ( 'pointerup' , event => {
Pointer_up ( { event : event } )
} )
window . addEventListener ( 'touchend' , event => {
//event . preventDefault ()
Pointer_up ( { event : event } )
} )
eyes_color_input . addEventListener ( 'input' , event => { eyes_color = Hex_to_color ( { hex : event . target . value } ) } )
lips_color_input . addEventListener ( 'input' , event => { lips_color = Hex_to_color ( { hex : event . target . value } ) } )
hair_color_input . addEventListener ( 'input' , event => { hair_color = Hex_to_color ( { hex : event . target . value } ) } )
beard_color_input . addEventListener ( 'input' , event => { beard_color = Hex_to_color ( { hex : event . target . value } ) } )
wind_of_the_west_slider . oninput = args => {
wind_factor = wind_of_the_west_slider . value
}
setInterval ( Tik , 33 )
//Draw ()
}
</script>
<script src = './tfjs.js'></script>
<script src = './tf-backend-wasm.js'></script>
<script src = './face-landmarks-detection.js'></script>
<script>
const Triangle_indexes = [
[127, 34, 139],
[ 11, 0, 37],
[232, 231, 120],
[ 72, 37, 39],
[128, 121, 47],
[232, 121, 128],
[104, 69, 67],
[175, 171, 148],
[118, 50, 101],
[ 73, 39, 40],
[ 9, 151, 108],
[ 48, 115, 131],
[194, 204, 211],
[ 74, 40, 185],
[ 80, 42, 183],
[ 40, 92, 186],
[230, 229, 118],
[202, 212, 214],
[ 83, 18, 17],
[ 76, 61, 146],
[160, 29, 30],
[ 56, 157, 173],
[106, 204, 194],
[135, 214, 192],
[203, 165, 98],
[ 21, 71, 68],
[ 51, 45, 4],
[144, 24, 23],
[ 77, 146, 91],
[205, 50, 187],
[201, 200, 18],
[ 91, 106, 182],
[ 90, 91, 181],
[ 85, 84, 17],
[206, 203, 36],
[148, 171, 140],
[ 92, 40, 39],
[193, 189, 244],
[159, 158, 28],
[247, 246, 161],
[236, 3, 196],
[ 54, 68, 104],
[193, 168, 8],
[117, 228, 31],
[189, 193, 55],
[ 98, 97, 99],
[126, 47, 100],
[166, 79, 218],
[155, 154, 26],
[209, 49, 131],
[135, 136, 150],
[ 47, 126, 217],
[223, 52, 53],
[ 45, 51, 134],
[211, 170, 140],
[ 67, 69, 108],
[ 43, 106, 91],
[230, 119, 120],
[226, 130, 247],
[ 63, 53, 52],
[238, 20, 242],
[ 46, 70, 156],
[ 78, 62, 96],
[ 46, 53, 63],
[143, 34, 227],
[123, 117, 111],
[ 44, 125, 19],
[236, 134, 51],
[216, 206, 205],
[154, 153, 22],
[ 39, 37, 167],
[200, 201, 208],
[ 36, 142, 100],
[ 57, 212, 202],
[ 20, 60, 99],
[ 28, 158, 157],
[ 35, 226, 113],
[160, 159, 27],
[204, 202, 210],
[113, 225, 46],
[ 43, 202, 204],
[ 62, 76, 77],
[137, 123, 116],
[ 41, 38, 72],
[203, 129, 142],
[ 64, 98, 240],
[ 49, 102, 64],
[ 41, 73, 74],
[212, 216, 207],
[ 42, 74, 184],
[169, 170, 211],
[170, 149, 176],
[105, 66, 69],
[122, 6, 168],
[123, 147, 187],
[ 96, 77, 90],
[ 65, 55, 107],
[ 89, 90, 180],
[101, 100, 120],
[ 63, 105, 104],
[ 93, 137, 227],
[ 15, 86, 85],
[129, 102, 49],
[ 14, 87, 86],
[ 55, 8, 9],
[100, 47, 121],
[145, 23, 22],
[ 88, 89, 179],
[ 6, 122, 196],
[ 88, 95, 96],
[138, 172, 136],
[215, 58, 172],
[115, 48, 219],
[ 42, 80, 81],
[195, 3, 51],
[ 43, 146, 61],
[171, 175, 199],
[ 81, 82, 38],
[ 53, 46, 225],
[144, 163, 110],
[ 52, 65, 66],
[229, 228, 117],
[ 34, 127, 234],
[107, 108, 69],
[109, 108, 151],
[ 48, 64, 235],
[ 62, 78, 191],
[129, 209, 126],
[111, 35, 143],
[117, 123, 50],
[222, 65, 52],
[ 19, 125, 141],
[221, 55, 65],
[ 3, 195, 197],
[ 25, 7, 33],
[220, 237, 44],
[ 70, 71, 139],
[122, 193, 245],
[247, 130, 33],
[ 71, 21, 162],
[170, 169, 150],
[188, 174, 196],
[216, 186, 92],
[ 2, 97, 167],
[141, 125, 241],
[164, 167, 37],
[ 72, 38, 12],
[ 38, 82, 13],
[ 63, 68, 71],
[226, 35, 111],
[101, 50, 205],
[206, 92, 165],
[209, 198, 217],
[165, 167, 97],
[220, 115, 218],
[133, 112, 243],
[239, 238, 241],
[214, 135, 169],
[190, 173, 133],
[171, 208, 32],
[125, 44, 237],
[ 86, 87, 178],
[ 85, 86, 179],
[ 84, 85, 180],
[ 83, 84, 181],
[201, 83, 182],
[137, 93, 132],
[ 76, 62, 183],
[ 61, 76, 184],
[ 57, 61, 185],
[212, 57, 186],
[214, 207, 187],
[ 34, 143, 156],
[ 79, 239, 237],
[123, 137, 177],
[ 44, 1, 4],
[201, 194, 32],
[ 64, 102, 129],
[213, 215, 138],
[ 59, 166, 219],
[242, 99, 97],
[ 2, 94, 141],
[ 75, 59, 235],
[ 24, 110, 228],
[ 25, 130, 226],
[ 23, 24, 229],
[ 22, 23, 230],
[ 26, 22, 231],
[112, 26, 232],
[189, 190, 243],
[221, 56, 190],
[ 28, 56, 221],
[ 27, 28, 222],
[ 29, 27, 223],
[ 30, 29, 224],
[247, 30, 225],
[238, 79, 20],
[166, 59, 75],
[ 60, 75, 240],
[147, 177, 215],
[ 20, 79, 166],
[187, 147, 213],
[112, 233, 244],
[233, 128, 245],
[128, 114, 188],
[114, 217, 174],
[131, 115, 220],
[217, 198, 236],
[198, 131, 134],
[177, 132, 58],
[143, 35, 124],
[110, 163, 7],
[228, 110, 25],
[356, 389, 368],
[ 11, 302, 267],
[452, 350, 349],
[302, 303, 269],
[357, 343, 277],
[452, 453, 357],
[333, 332, 297],
[175, 152, 377],
[347, 348, 330],
[303, 304, 270],
[ 9, 336, 337],
[278, 279, 360],
[418, 262, 431],
[304, 408, 409],
[310, 415, 407],
[270, 409, 410],
[450, 348, 347],
[422, 430, 434],
[313, 314, 17],
[306, 307, 375],
[387, 388, 260],
[286, 414, 398],
[335, 406, 418],
[364, 367, 416],
[423, 358, 327],
[251, 284, 298],
[281, 5, 4],
[373, 374, 253],
[307, 320, 321],
[425, 427, 411],
[421, 313, 18],
[321, 405, 406],
[320, 404, 405],
[315, 16, 17],
[426, 425, 266],
[377, 400, 369],
[322, 391, 269],
[417, 465, 464],
[386, 257, 258],
[466, 260, 388],
[456, 399, 419],
[284, 332, 333],
[417, 285, 8],
[346, 340, 261],
[413, 441, 285],
[327, 460, 328],
[355, 371, 329],
[392, 439, 438],
[382, 341, 256],
[429, 420, 360],
[364, 394, 379],
[277, 343, 437],
[443, 444, 283],
[275, 440, 363],
[431, 262, 369],
[297, 338, 337],
[273, 375, 321],
[450, 451, 349],
[446, 342, 467],
[293, 334, 282],
[458, 461, 462],
[276, 353, 383],
[308, 324, 325],
[276, 300, 293],
[372, 345, 447],
[352, 345, 340],
[274, 1, 19],
[456, 248, 281],
[436, 427, 425],
[381, 256, 252],
[269, 391, 393],
[200, 199, 428],
[266, 330, 329],
[287, 273, 422],
[250, 462, 328],
[258, 286, 384],
[265, 353, 342],
[387, 259, 257],
[424, 431, 430],
[342, 353, 276],
[273, 335, 424],
[292, 325, 307],
[366, 447, 345],
[271, 303, 302],
[423, 266, 371],
[294, 455, 460],
[279, 278, 294],
[271, 272, 304],
[432, 434, 427],
[272, 407, 408],
[394, 430, 431],
[395, 369, 400],
[334, 333, 299],
[351, 417, 168],
[352, 280, 411],
[325, 319, 320],
[295, 296, 336],
[319, 403, 404],
[330, 348, 349],
[293, 298, 333],
[323, 454, 447],
[ 15, 16, 315],
[358, 429, 279],
[ 14, 15, 316],
[285, 336, 9],
[329, 349, 350],
[374, 380, 252],
[318, 402, 403],
[ 6, 197, 419],
[318, 319, 325],
[367, 364, 365],
[435, 367, 397],
[344, 438, 439],
[272, 271, 311],
[195, 5, 281],
[273, 287, 291],
[396, 428, 199],
[311, 271, 268],
[283, 444, 445],
[373, 254, 339],
[282, 334, 296],
[449, 347, 346],
[264, 447, 454],
[336, 296, 299],
[338, 10, 151],
[278, 439, 455],
[292, 407, 415],
[358, 371, 355],
[340, 345, 372],
[346, 347, 280],
[442, 443, 282],
[ 19, 94, 370],
[441, 442, 295],
[248, 419, 197],
[263, 255, 359],
[440, 275, 274],
[300, 383, 368],
[351, 412, 465],
[263, 467, 466],
[301, 368, 389],
[395, 378, 379],
[412, 351, 419],
[436, 426, 322],
[ 2, 164, 393],
[370, 462, 461],
[164, 0, 267],
[302, 11, 12],
[268, 12, 13],
[293, 300, 301],
[446, 261, 340],
[330, 266, 425],
[426, 423, 391],
[429, 355, 437],
[391, 327, 326],
[440, 457, 438],
[341, 382, 362],
[459, 457, 461],
[434, 430, 394],
[414, 463, 362],
[396, 369, 262],
[354, 461, 457],
[316, 403, 402],
[315, 404, 403],
[314, 405, 404],
[313, 406, 405],
[421, 418, 406],
[366, 401, 361],
[306, 408, 407],
[291, 409, 408],
[287, 410, 409],
[432, 436, 410],
[434, 416, 411],
[264, 368, 383],
[309, 438, 457],
[352, 376, 401],
[274, 275, 4],
[421, 428, 262],
[294, 327, 358],
[433, 416, 367],
[289, 455, 439],
[462, 370, 326],
[ 2, 326, 370],
[305, 460, 455],
[254, 449, 448],
[255, 261, 446],
[253, 450, 449],
[252, 451, 450],
[256, 452, 451],
[341, 453, 452],
[413, 464, 463],
[441, 413, 414],
[258, 442, 441],
[257, 443, 442],
[259, 444, 443],
[260, 445, 444],
[467, 342, 445],
[459, 458, 250],
[289, 392, 290],
[290, 328, 460],
[376, 433, 435],
[250, 290, 392],
[411, 416, 433],
[341, 463, 464],
[453, 464, 465],
[357, 465, 412],
[343, 412, 399],
[360, 363, 440],
[437, 399, 456],
[420, 456, 363],
[401, 435, 288],
[372, 383, 353],
[339, 255, 249],
[448, 261, 255],
[133, 243, 190],
[133, 155, 112],
[ 33, 246, 247],
[ 33, 130, 25],
[398, 384, 286],
[362, 398, 414],
[362, 463, 341],
[263, 359, 467],
[263, 249, 255],
[466, 467, 260],
[ 75, 60, 166],
[238, 239, 79],
[162, 127, 139],
[ 72, 11, 37],
[121, 232, 120],
[ 73, 72, 39],
[114, 128, 47],
[233, 232, 128],
[103, 104, 67],
[152, 175, 148],
[119, 118, 101],
[ 74, 73, 40],
[107, 9, 108],
[ 49, 48, 131],
[ 32, 194, 211],
[184, 74, 185],
[191, 80, 183],
[185, 40, 186],
[119, 230, 118],
[210, 202, 214],
[ 84, 83, 17],
[ 77, 76, 146],
[161, 160, 30],
[190, 56, 173],
[182, 106, 194],
[138, 135, 192],
[129, 203, 98],
[ 54, 21, 68],
[ 5, 51, 4],
[145, 144, 23],
[ 90, 77, 91],
[207, 205, 187],
[ 83, 201, 18],
[181, 91, 182],
[180, 90, 181],
[ 16, 85, 17],
[205, 206, 36],
[176, 148, 140],
[165, 92, 39],
[245, 193, 244],
[ 27, 159, 28],
[ 30, 247, 161],
[174, 236, 196],
[103, 54, 104],
[ 55, 193, 8],
[111, 117, 31],
[221, 189, 55],
[240, 98, 99],
[142, 126, 100],
[219, 166, 218],
[112, 155, 26],
[198, 209, 131],
[169, 135, 150],
[114, 47, 217],
[224, 223, 53],
[220, 45, 134],
[ 32, 211, 140],
[109, 67, 108],
[146, 43, 91],
[231, 230, 120],
[113, 226, 247],
[105, 63, 52],
[241, 238, 242],
[124, 46, 156],
[ 95, 78, 96],
[ 70, 46, 63],
[116, 143, 227],
[116, 123, 111],
[ 1, 44, 19],
[ 3, 236, 51],
[207, 216, 205],
[ 26, 154, 22],
[165, 39, 167],
[199, 200, 208],
[101, 36, 100],
[ 43, 57, 202],
[242, 20, 99],
[ 56, 28, 157],
[124, 35, 113],
[ 29, 160, 27],
[211, 204, 210],
[124, 113, 46],
[106, 43, 204],
[ 96, 62, 77],
[227, 137, 116],
[ 73, 41, 72],
[ 36, 203, 142],
[235, 64, 240],
[ 48, 49, 64],
[ 42, 41, 74],
[214, 212, 207],
[183, 42, 184],
[210, 169, 211],
[140, 170, 176],
[104, 105, 69],
[193, 122, 168],
[ 50, 123, 187],
[ 89, 96, 90],
[ 66, 65, 107],
[179, 89, 180],
[119, 101, 120],
[ 68, 63, 104],
[234, 93, 227],
[ 16, 15, 85],
[209, 129, 49],
[ 15, 14, 86],
[107, 55, 9],
[120, 100, 121],
[153, 145, 22],
[178, 88, 179],
[197, 6, 196],
[ 89, 88, 96],
[135, 138, 136],
[138, 215, 172],
[218, 115, 219],
[ 41, 42, 81],
[ 5, 195, 51],
[ 57, 43, 61],
[208, 171, 199],
[ 41, 81, 38],
[224, 53, 225],
[ 24, 144, 110],
[105, 52, 66],
[118, 229, 117],
[227, 34, 234],
[ 66, 107, 69],
[ 10, 109, 151],
[219, 48, 235],
[183, 62, 191],
[142, 129, 126],
[116, 111, 143],
[118, 117, 50],
[223, 222, 52],
[ 94, 19, 141],
[222, 221, 65],
[196, 3, 197],
[ 45, 220, 44],
[156, 70, 139],
[188, 122, 245],
[139, 71, 162],
[149, 170, 150],
[122, 188, 196],
[206, 216, 92],
[164, 2, 167],
[242, 141, 241],
[ 0, 164, 37],
[ 11, 72, 12],
[ 12, 38, 13],
[ 70, 63, 71],
[ 31, 226, 111],
[ 36, 101, 205],
[203, 206, 165],
[126, 209, 217],
[ 98, 165, 97],
[237, 220, 218],
[237, 239, 241],
[210, 214, 169],
[140, 171, 32],
[241, 125, 237],
[179, 86, 178],
[180, 85, 179],
[181, 84, 180],
[182, 83, 181],
[194, 201, 182],
[177, 137, 132],
[184, 76, 183],
[185, 61, 184],
[186, 57, 185],
[216, 212, 186],
[192, 214, 187],
[139, 34, 156],
[218, 79, 237],
[147, 123, 177],
[ 45, 44, 4],
[208, 201, 32],
[ 98, 64, 129],
[192, 213, 138],
[235, 59, 219],
[141, 242, 97],
[ 97, 2, 141],
[240, 75, 235],
[229, 24, 228],
[ 31, 25, 226],
[230, 23, 229],
[231, 22, 230],
[232, 26, 231],
[233, 112, 232],
[244, 189, 243],
[189, 221, 190],
[222, 28, 221],
[223, 27, 222],
[224, 29, 223],
[225, 30, 224],
[113, 247, 225],
[ 99, 60, 240],
[213, 147, 215],
[ 60, 20, 166],
[192, 187, 213],
[243, 112, 244],
[244, 233, 245],
[245, 128, 188],
[188, 114, 174],
[134, 131, 220],
[174, 217, 236],
[236, 198, 134],
[215, 177, 58],
[156, 143, 124],
[ 25, 110, 7],
[ 31, 228, 25],
[264, 356, 368],
[ 0, 11, 267],
[451, 452, 349],
[267, 302, 269],
[350, 357, 277],
[350, 452, 357],
[299, 333, 297],
[396, 175, 377],
[280, 347, 330],
[269, 303, 270],
[151, 9, 337],
[344, 278, 360],
[424, 418, 431],
[270, 304, 409],
[272, 310, 407],
[322, 270, 410],
[449, 450, 347],
[432, 422, 434],
[ 18, 313, 17],
[291, 306, 375],
[259, 387, 260],
[424, 335, 418],
[434, 364, 416],
[391, 423, 327],
[301, 251, 298],
[275, 281, 4],
[254, 373, 253],
[375, 307, 321],
[280, 425, 411],
[200, 421, 18],
[335, 321, 406],
[321, 320, 405],
[314, 315, 17],
[423, 426, 266],
[396, 377, 369],
[270, 322, 269],
[413, 417, 464],
[385, 386, 258],
[248, 456, 419],
[298, 284, 333],
[168, 417, 8],
[448, 346, 261],
[417, 413, 285],
[326, 327, 328],
[277, 355, 329],
[309, 392, 438],
[381, 382, 256],
[279, 429, 360],
[365, 364, 379],
[355, 277, 437],
[282, 443, 283],
[281, 275, 363],
[395, 431, 369],
[299, 297, 337],
[335, 273, 321],
[348, 450, 349],
[359, 446, 467],
[283, 293, 282],
[250, 458, 462],
[300, 276, 383],
[292, 308, 325],
[283, 276, 293],
[264, 372, 447],
[346, 352, 340],
[354, 274, 19],
[363, 456, 281],
[426, 436, 425],
[380, 381, 252],
[267, 269, 393],
[421, 200, 428],
[371, 266, 329],
[432, 287, 422],
[290, 250, 328],
[385, 258, 384],
[446, 265, 342],
[386, 387, 257],
[422, 424, 430],
[445, 342, 276],
[422, 273, 424],
[306, 292, 307],
[352, 366, 345],
[268, 271, 302],
[358, 423, 371],
[327, 294, 460],
[331, 279, 294],
[303, 271, 304],
[436, 432, 427],
[304, 272, 408],
[395, 394, 431],
[378, 395, 400],
[296, 334, 299],
[ 6, 351, 168],
[376, 352, 411],
[307, 325, 320],
[285, 295, 336],
[320, 319, 404],
[329, 330, 349],
[334, 293, 333],
[366, 323, 447],
[316, 15, 315],
[331, 358, 279],
[317, 14, 316],
[ 8, 285, 9],
[277, 329, 350],
[253, 374, 252],
[319, 318, 403],
[351, 6, 419],
[324, 318, 325],
[397, 367, 365],
[288, 435, 397],
[278, 344, 439],
[310, 272, 311],
[248, 195, 281],
[375, 273, 291],
[175, 396, 199],
[312, 311, 268],
[276, 283, 445],
[390, 373, 339],
[295, 282, 296],
[448, 449, 346],
[356, 264, 454],
[337, 336, 299],
[337, 338, 151],
[294, 278, 455],
[308, 292, 415],
[429, 358, 355],
[265, 340, 372],
[352, 346, 280],
[295, 442, 282],
[354, 19, 370],
[285, 441, 295],
[195, 248, 197],
[457, 440, 274],
[301, 300, 368],
[417, 351, 465],
[251, 301, 389],
[394, 395, 379],
[399, 412, 419],
[410, 436, 322],
[326, 2, 393],
[354, 370, 461],
[393, 164, 267],
[268, 302, 12],
[312, 268, 13],
[298, 293, 301],
[265, 446, 340],
[280, 330, 425],
[322, 426, 391],
[420, 429, 437],
[393, 391, 326],
[344, 440, 438],
[458, 459, 461],
[364, 434, 394],
[428, 396, 262],
[274, 354, 457],
[317, 316, 402],
[316, 315, 403],
[315, 314, 404],
[314, 313, 405],
[313, 421, 406],
[323, 366, 361],
[292, 306, 407],
[306, 291, 408],
[291, 287, 409],
[287, 432, 410],
[427, 434, 411],
[372, 264, 383],
[459, 309, 457],
[366, 352, 401],
[ 1, 274, 4],
[418, 421, 262],
[331, 294, 358],
[435, 433, 367],
[392, 289, 439],
[328, 462, 326],
[ 94, 2, 370],
[289, 305, 455],
[339, 254, 448],
[359, 255, 446],
[254, 253, 449],
[253, 252, 450],
[252, 256, 451],
[256, 341, 452],
[414, 413, 463],
[286, 441, 414],
[286, 258, 441],
[258, 257, 442],
[257, 259, 443],
[259, 260, 444],
[260, 467, 445],
[309, 459, 250],
[305, 289, 290],
[305, 290, 460],
[401, 376, 435],
[309, 250, 392],
[376, 411, 433],
[453, 341, 464],
[357, 453, 465],
[343, 357, 412],
[437, 343, 399],
[344, 360, 440],
[420, 437, 456],
[360, 420, 363],
[361, 401, 288],
[265, 372, 353],
[390, 339, 249]
]
/*
{faceInViewConfidence: 1, boundingBox: {…}, mesh: Array(468), scaledMesh: Array(468), annotations: {…}}
annotations:
leftCheek: [Array(3)]
leftEyeLower0: (9) [Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3)]
leftEyeLower1: (9) [Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3)]
leftEyeLower2: (9) [Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3)]
leftEyeLower3: (9) [Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3)]
leftEyeUpper0: (7) [Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3)]
leftEyeUpper1: (7) [Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3)]
leftEyeUpper2: (7) [Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3)]
leftEyebrowLower: (6) [Array(3), Array(3), Array(3), Array(3), Array(3), Array(3)]
leftEyebrowUpper: (8) [Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3)]
lipsLowerInner: (11) [Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3)]
lipsLowerOuter: (10) [Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3)]
lipsUpperInner: (11) [Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3)]
lipsUpperOuter: (11) [Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3)]
midwayBetweenEyes: [Array(3)]
noseBottom: [Array(3)]
noseLeftCorner: [Array(3)]
noseRightCorner: [Array(3)]
noseTip: [Array(3)]
rightCheek: [Array(3)]
rightEyeLower0: (9) [Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3)]
rightEyeLower1: (9) [Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3)]
rightEyeLower2: (9) [Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3)]
rightEyeLower3: (9) [Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3)]
rightEyeUpper0: (7) [Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3)]
rightEyeUpper1: (7) [Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3)]
rightEyeUpper2: (7) [Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3)]
rightEyebrowLower: (6) [Array(3), Array(3), Array(3), Array(3), Array(3), Array(3)]
rightEyebrowUpper: (8) [Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3)]
silhouette: (36) [Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3)]
[[Prototype]]: Object
boundingBox: {topLeft: Array(2), bottomRight: Array(2)}
faceInViewConfidence: 1
mesh: (468) [Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), …]
scaledMesh: (468) [Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), …]
[[Prototype]]: Object
*/
Main ()
</script>
</body>
</html>