facecore 0.3.0 copy "facecore: ^0.3.0" to clipboard
facecore: ^0.3.0 copied to clipboard

Self-contained Flutter plugin for camera-based face liveness, depth, and anti-spoof checks before capturing a photo on Android and iOS.

example/lib/main.dart

import 'dart:io';

import 'package:facecore/facecore.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Facecore Demo',
      home: const HomePage(),
      theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.indigo),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({super.key});
  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final _facecore = Facecore();
  final Set<LivenessAction> _selectedActions = {LivenessAction.blink};
  CameraLens _lens = CameraLens.back;
  bool _enableDepth = true;
  bool _requireDepthHw = false;
  bool _enableMoire = true;
  bool _enableBanding = true;
  bool _enableScreenEdge = true;
  bool? _hasDepthCameraForLens;

  FaceCaptureResult? _livenessResult;
  AntiSpoofResult? _antiSpoofResult;
  String? _error;
  bool _busy = false;

  @override
  void initState() {
    super.initState();
    _refreshDepthCapability();
  }

  Future<void> _refreshDepthCapability() async {
    final has = await _facecore.hasDepthCamera(lens: _lens);
    if (!mounted) return;
    setState(() {
      _hasDepthCameraForLens = has;
      if (!has) _requireDepthHw = false;
    });
  }

  Future<void> _runLiveness() async {
    if (_selectedActions.isEmpty) {
      setState(() => _error = 'Pick at least one liveness action.');
      return;
    }
    setState(() {
      _busy = true;
      _error = null;
      _antiSpoofResult = null;
    });
    try {
      final result = await _facecore.captureWithLiveness(
        config: LivenessConfig(
          actions: [
            for (final a in LivenessAction.values)
              if (_selectedActions.contains(a)) a,
          ],
          title: 'Verify your identity',
          subtitle: 'Follow the prompts to confirm you are present.',
        ),
      );
      setState(() => _livenessResult = result);
    } on FaceCaptureException catch (e) {
      setState(() => _error = e.toString());
    } finally {
      if (mounted) setState(() => _busy = false);
    }
  }

  Future<void> _runAntiSpoof() async {
    setState(() {
      _busy = true;
      _error = null;
      _livenessResult = null;
    });
    try {
      final result = await _facecore.captureWithAntiSpoof(
        config: AntiSpoofConfig(
          lens: _lens,
          enableDepth: _enableDepth,
          requireDepthHardware: _requireDepthHw,
          enableMoire: _enableMoire,
          enableBanding: _enableBanding,
          enableScreenEdge: _enableScreenEdge,
          title: 'Anti-spoof capture',
          subtitle: 'Hold steady — verifying the scene is real.',
        ),
      );
      setState(() => _antiSpoofResult = result);
    } on FaceCaptureException catch (e) {
      setState(() => _error = e.toString());
    } finally {
      if (mounted) setState(() => _busy = false);
    }
  }

  String _label(LivenessAction action) => switch (action) {
        LivenessAction.blink => 'Blink',
        LivenessAction.turnLeft => 'Turn left',
        LivenessAction.turnRight => 'Turn right',
      };

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Facecore Demo')),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          const Text('Liveness capture',
              style: TextStyle(fontWeight: FontWeight.w600, fontSize: 16)),
          const SizedBox(height: 8),
          Wrap(
            spacing: 8,
            children: [
              for (final action in LivenessAction.values)
                FilterChip(
                  label: Text(_label(action)),
                  selected: _selectedActions.contains(action),
                  onSelected: _busy
                      ? null
                      : (selected) => setState(() {
                            if (selected) {
                              _selectedActions.add(action);
                            } else {
                              _selectedActions.remove(action);
                            }
                          }),
                ),
            ],
          ),
          const SizedBox(height: 8),
          FilledButton(
            onPressed: _busy ? null : _runLiveness,
            child: const Text('Run liveness'),
          ),
          const Divider(height: 32),
          const Text('Anti-spoof capture',
              style: TextStyle(fontWeight: FontWeight.w600, fontSize: 16)),
          const SizedBox(height: 8),
          SegmentedButton<CameraLens>(
            segments: const [
              ButtonSegment(value: CameraLens.back, label: Text('Back')),
              ButtonSegment(value: CameraLens.front, label: Text('Front')),
            ],
            selected: {_lens},
            onSelectionChanged: _busy
                ? null
                : (s) {
                    setState(() => _lens = s.first);
                    _refreshDepthCapability();
                  },
          ),
          Padding(
            padding: const EdgeInsets.only(top: 8),
            child: Text(
              _hasDepthCameraForLens == null
                  ? 'Checking depth camera…'
                  : _hasDepthCameraForLens!
                      ? 'Depth camera detected on this lens.'
                      : 'No depth camera on this lens — fail-closed is disabled.',
              style: TextStyle(
                color: _hasDepthCameraForLens == false
                    ? Colors.orange
                    : Colors.grey.shade700,
                fontSize: 12,
              ),
            ),
          ),
          SwitchListTile(
            value: _enableDepth,
            title: const Text('Depth check'),
            onChanged: _busy ? null : (v) => setState(() => _enableDepth = v),
          ),
          SwitchListTile(
            value: _requireDepthHw,
            title: const Text('Require depth hardware (fail-closed)'),
            subtitle: _hasDepthCameraForLens == false
                ? const Text('Disabled: device has no depth camera for this lens.')
                : null,
            onChanged: (_busy ||
                    !_enableDepth ||
                    _hasDepthCameraForLens == false)
                ? null
                : (v) => setState(() => _requireDepthHw = v),
          ),
          SwitchListTile(
            value: _enableMoire,
            title: const Text('Moiré detection'),
            onChanged: _busy ? null : (v) => setState(() => _enableMoire = v),
          ),
          SwitchListTile(
            value: _enableBanding,
            title: const Text('Refresh-banding detection'),
            onChanged: _busy ? null : (v) => setState(() => _enableBanding = v),
          ),
          SwitchListTile(
            value: _enableScreenEdge,
            title: const Text('Screen-edge detection'),
            onChanged: _busy
                ? null
                : (v) => setState(() => _enableScreenEdge = v),
          ),
          FilledButton(
            onPressed: _busy ? null : _runAntiSpoof,
            child: const Text('Run anti-spoof'),
          ),
          const SizedBox(height: 16),
          if (_error != null)
            Text(_error!, style: const TextStyle(color: Colors.red)),
          if (_livenessResult != null) _LivenessSummary(result: _livenessResult!),
          if (_antiSpoofResult != null)
            _AntiSpoofSummary(result: _antiSpoofResult!),
        ],
      ),
    );
  }
}

class _LivenessSummary extends StatelessWidget {
  final FaceCaptureResult result;
  const _LivenessSummary({required this.result});

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const SizedBox(height: 8),
        Text('Saved: ${result.imagePath}'),
        Text('Size: ${result.width}×${result.height}'),
        Text('Motion stddev: ${result.motionStdDev.toStringAsFixed(2)}'),
        if (result.depthRange != null)
          Text('Depth range: ${result.depthRange!.toStringAsFixed(2)}'),
        const SizedBox(height: 8),
        SizedBox(
          height: 240,
          child: Image.file(File(result.imagePath), fit: BoxFit.contain),
        ),
      ],
    );
  }
}

class _AntiSpoofSummary extends StatelessWidget {
  final AntiSpoofResult result;
  const _AntiSpoofSummary({required this.result});

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const SizedBox(height: 8),
        Text('Saved: ${result.imagePath}'),
        Text('Size: ${result.width}×${result.height}'),
        if (result.moireScore != null)
          Text('Moiré score: ${result.moireScore!.toStringAsFixed(3)}'),
        if (result.bandingScore != null)
          Text('Banding score: ${result.bandingScore!.toStringAsFixed(3)}'),
        if (result.depthRange != null)
          Text('Depth range: ${result.depthRange!.toStringAsFixed(2)}'),
        if (result.screenEdgeDetected != null)
          Text('Screen edge detected: ${result.screenEdgeDetected}'),
        const SizedBox(height: 8),
        SizedBox(
          height: 240,
          child: Image.file(File(result.imagePath), fit: BoxFit.contain),
        ),
      ],
    );
  }
}
0
likes
0
points
58
downloads

Publisher

unverified uploader

Weekly Downloads

Self-contained Flutter plugin for camera-based face liveness, depth, and anti-spoof checks before capturing a photo on Android and iOS.

Repository
View/report issues

Topics

#face-detection #liveness #camera #biometrics #anti-spoofing

License

unknown (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on facecore

Packages that implement facecore