Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 170 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,178 @@ Here is a list of properties available to customize your widget:
| lensControlIcon | Widget | use this to render a custom widget for camera lens control |
| flashControlBuilder | FlashControlBuilder | use this to build custom widgets for flash control based on camera flash mode |
| messageBuilder | MessageBuilder | use this to build custom messages based on face position |
| indicatorShape | IndicatorShape | use this to change the shape of the face indicator |
| indicatorShape | IndicatorShape | use this to change the shape of the face indicator (defaultShape, square, circle, triangle, triangleInverted, image, fixedFrame, none) |
| indicatorAssetImage | String | use this to pass an asset image when IndicatorShape is set to image |
| indicatorBuilder | IndicatorBuilder | use this to build custom widgets for the face indicator |
| captureControlBuilder | CaptureControlBuilder | use this to build custom widgets for capture control |
| autoDisableCaptureControl | bool | set true to disable capture control widget when no face is detected |

#### Fixed Frame Mode

The `IndicatorShape.fixedFrame` option displays a centered, fixed-size frame (70% of screen) instead of following the detected face. This provides a better user experience for face capture with real-time positioning guidance:

**Frame Colors:**
* **White/Gray Frame**: No face detected
* **Red Frame**: Face detected but requirements not met
* **Green Frame**: Face properly positioned, countdown starts

**Detection Constraints:**

For the frame to turn green and trigger auto-capture, the following conditions must ALL be met:

1. **Face Distance**
- Eye distance must be between 0.14 - 0.28 (normalized Euclidean distance)
- Too close (> 0.28) → "Move back"
- Too far (< 0.14) → "Move closer"

2. **Landmark Positioning**
- All 6 facial landmarks (both eyes, nose, 3 mouth points) must be inside the frame bounds (15% - 85% of screen)
- Provides guidance: "Move left", "Move right", "Move up", "Move down"

3. **Face Centering**
- Nose must be near the center of the frame (40% - 60% range)
- Ensures face is not just inside, but properly centered

4. **Head Orientation**
- Head rotation Y (left/right): ≤ 12 degrees
- Head rotation Z (tilt): ≤ 12 degrees
- User must face forward with head straight

**Basic Usage:**

```dart
FaceCameraController(
autoCapture: true,
captureDelay: 3,
defaultCameraLens: CameraLens.front,
showDebugLandmarks: false,
onCapture: (File? image) {
// Handle captured image
},
)

SmartFaceCamera(
controller: controller,
indicatorShape: IndicatorShape.fixedFrame,
messageBuilder: (context, face) {
if (controller.value.countdown != null) {
return _buildMessage('Hold still...');
}

final guidance = controller.getFacePositionGuidance();
return _buildMessage(guidance ?? 'Ready');
},
)

Widget _buildMessage(String message) => Padding(
padding: EdgeInsets.symmetric(horizontal: 55, vertical: 15),
child: Text(
message,
textAlign: TextAlign.center,
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w400),
),
);
```

**Customizing Guidance Messages:**

You can fully customize the positioning guidance messages:

```dart
messageBuilder: (context, face) {
// Show countdown message
if (controller.value.countdown != null) {
return _buildMessage('Hold still... ${controller.value.countdown}');
}

// Get default guidance
final guidance = controller.getFacePositionGuidance();

// Customize messages
String message;
switch (guidance) {
case 'Move closer':
message = 'Come closer to the camera';
break;
case 'Move back':
message = 'Move away from the camera';
break;
case 'Move left':
message = 'Move to your left';
break;
case 'Move right':
message = 'Move to your right';
break;
case 'Move up':
message = 'Lift your phone higher';
break;
case 'Move down':
message = 'Lower your phone';
break;
case 'Center your face':
message = 'Almost there, center your face';
break;
case 'Place your face in the frame':
message = 'Position your face in the frame';
break;
default:
message = 'Perfect! Capturing...';
}

return _buildMessage(message);
}
```

**Multi-language Support:**

```dart
messageBuilder: (context, face) {
if (controller.value.countdown != null) {
return _buildMessage(AppLocalizations.of(context).holdStill);
}

final guidance = controller.getFacePositionGuidance();
final translations = {
'Move closer': AppLocalizations.of(context).moveCloser,
'Move back': AppLocalizations.of(context).moveBack,
'Move left': AppLocalizations.of(context).moveLeft,
'Move right': AppLocalizations.of(context).moveRight,
// ... more translations
};

return _buildMessage(
translations[guidance] ?? AppLocalizations.of(context).ready
);
}
```

**Debug Mode:**

Enable `showDebugLandmarks: true` in the controller to visualize facial landmarks with colored markers:
- Blue: Eyes
- Green: Nose
- Red: Mouth
- Yellow: Cheeks
- Purple: Ears
- Cyan: Face bounding box

This mode is ideal for KYC, document verification, and any application requiring consistent, high-quality face capture.

#### Capture Countdown

When `autoCapture` is enabled, you can add a countdown delay before the photo is taken. This gives users time to prepare and ensures they're ready:

```dart
FaceCameraController(
autoCapture: true,
captureDelay: 3, // 3-second countdown (default)
onCapture: (File? image) {
// Handle captured image
},
)
```

The countdown is displayed as a large centered number (3, 2, 1) and only starts when the face is properly positioned. If the face moves out of position during countdown, the timer resets. Set `captureDelay: 0` for immediate capture without countdown.

\
\
Expand All @@ -128,9 +294,11 @@ Here is a list of properties available to customize your widget from the control
| defaultFlashMode | CameraFlashMode | use this to set initial flash mode |
| enableAudio | bool | set false to disable capture sound |
| autoCapture | bool | set true to capture image on face detected |
| captureDelay | int | countdown delay in seconds before auto-capture (default: 3, set 0 for immediate) |
| showDebugLandmarks | bool | set true to show colored markers for facial landmarks (eyes, nose, mouth, etc.) |
| ignoreFacePositioning | bool | set true to trigger onCapture even when the face is not well positioned |
| orientation | CameraOrientation | use this to lock camera orientation |
| performanceMode | FaceDetectorMode | Use this to set your preferred performance mode |
| performanceMode | FaceDetectorMode | use this to set your preferred performance mode |


### Contributions
Expand Down
37 changes: 33 additions & 4 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ class _MyAppState extends State<MyApp> {
void initState() {
controller = FaceCameraController(
autoCapture: true,
captureDelay: 3,
defaultCameraLens: CameraLens.front,
showDebugLandmarks: false, // Set true to see facial landmarks
onCapture: (File? image) {
setState(() => _capturedImage = image);
},
Expand Down Expand Up @@ -74,13 +76,19 @@ class _MyAppState extends State<MyApp> {
}
return SmartFaceCamera(
controller: controller,
indicatorShape: IndicatorShape.fixedFrame,
messageBuilder: (context, face) {
if (face == null) {
return _message('Place your face in the camera');
// Show countdown message
if (controller.value.countdown != null) {
return _message('Hold still...');
}
if (!face.wellPositioned) {
return _message('Center your face in the square');

// Get positioning guidance
final guidance = controller.getFacePositionGuidance();
if (guidance != null) {
return _message(_customizeMessage(guidance));
}

return const SizedBox.shrink();
});
})),
Expand All @@ -95,6 +103,27 @@ class _MyAppState extends State<MyApp> {
fontSize: 14, height: 1.5, fontWeight: FontWeight.w400)),
);

String _customizeMessage(String guidance) {
// Customize guidance messages here
switch (guidance) {
case 'Move closer':
return 'Come closer to the camera';
case 'Move back':
return 'Move away from the camera';
case 'Move left':
case 'Move right':
case 'Move up':
case 'Move down':
return guidance; // Keep directional messages as-is
case 'Center your face':
return 'Almost there! Center your face';
case 'Place your face in the frame':
return 'Position your face in the frame';
default:
return guidance;
}
}

@override
void dispose() {
controller.dispose();
Expand Down
Loading