From 42a81b9309352a9090674d81a9d7c58d47863885 Mon Sep 17 00:00:00 2001 From: otsmr Date: Sun, 25 Jan 2026 16:46:41 +0100 Subject: [PATCH] adds new filter --- assets/filters/beard_upper_lip_green.webp | Bin 0 -> 3990 bytes assets/filters/hat_black.webp | Bin 0 -> 4092 bytes .../face_filters.dart | 5 ++- .../main_camera_controller.dart | 33 ++++++++++-------- .../face_filters/beard_filter_painter.dart | 30 ++++++++++++---- 5 files changed, 47 insertions(+), 21 deletions(-) create mode 100644 assets/filters/beard_upper_lip_green.webp create mode 100644 assets/filters/hat_black.webp diff --git a/assets/filters/beard_upper_lip_green.webp b/assets/filters/beard_upper_lip_green.webp new file mode 100644 index 0000000000000000000000000000000000000000..4046f6516fbf8f9fcdddde615784149d25588890 GIT binary patch literal 3990 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 literal 0 HcmV?d00001 diff --git a/lib/src/views/camera/camera_preview_components/face_filters.dart b/lib/src/views/camera/camera_preview_components/face_filters.dart index 8c880a7..4913ed4 100644 --- a/lib/src/views/camera/camera_preview_components/face_filters.dart +++ b/lib/src/views/camera/camera_preview_components/face_filters.dart @@ -5,6 +5,7 @@ import 'package:twonly/src/views/camera/camera_preview_components/painters/face_ enum FaceFilterType { none, dogBrown, + beardUpperLipGreen, beardUpperLip, } @@ -27,7 +28,9 @@ extension FaceFilterTypeExtension on FaceFilterType { case FaceFilterType.dogBrown: return DogFilterPainter.getPreview(); case FaceFilterType.beardUpperLip: - return BeardFilterPainter.getPreview(); + return BeardFilterPainter.getPreview(this); + case FaceFilterType.beardUpperLipGreen: + return BeardFilterPainter.getPreview(this); } } } diff --git a/lib/src/views/camera/camera_preview_components/main_camera_controller.dart b/lib/src/views/camera/camera_preview_components/main_camera_controller.dart index 7b32001..dd5364f 100644 --- a/lib/src/views/camera/camera_preview_components/main_camera_controller.dart +++ b/lib/src/views/camera/camera_preview_components/main_camera_controller.dart @@ -397,20 +397,25 @@ class MainCameraController { cameraController != null) { if (faces.isNotEmpty) { CustomPainter? painter; - if (_currentFilterType == FaceFilterType.dogBrown) { - painter = DogFilterPainter( - faces, - inputImage.metadata!.size, - inputImage.metadata!.rotation, - cameraController!.description.lensDirection, - ); - } else if (_currentFilterType == FaceFilterType.beardUpperLip) { - painter = BeardFilterPainter( - faces, - inputImage.metadata!.size, - inputImage.metadata!.rotation, - cameraController!.description.lensDirection, - ); + switch (_currentFilterType) { + case FaceFilterType.dogBrown: + painter = DogFilterPainter( + faces, + inputImage.metadata!.size, + inputImage.metadata!.rotation, + cameraController!.description.lensDirection, + ); + case FaceFilterType.beardUpperLip: + case FaceFilterType.beardUpperLipGreen: + painter = BeardFilterPainter( + _currentFilterType, + faces, + inputImage.metadata!.size, + inputImage.metadata!.rotation, + cameraController!.description.lensDirection, + ); + case FaceFilterType.none: + break; } if (painter != null) { diff --git a/lib/src/views/camera/camera_preview_components/painters/face_filters/beard_filter_painter.dart b/lib/src/views/camera/camera_preview_components/painters/face_filters/beard_filter_painter.dart index 3477bb6..35a478d 100644 --- a/lib/src/views/camera/camera_preview_components/painters/face_filters/beard_filter_painter.dart +++ b/lib/src/views/camera/camera_preview_components/painters/face_filters/beard_filter_painter.dart @@ -6,27 +6,45 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:google_mlkit_face_detection/google_mlkit_face_detection.dart'; import 'package:twonly/src/utils/log.dart'; +import 'package:twonly/src/views/camera/camera_preview_components/face_filters.dart'; import 'package:twonly/src/views/camera/camera_preview_components/painters/coordinates_translator.dart'; import 'package:twonly/src/views/camera/camera_preview_components/painters/face_filters/face_filter_painter.dart'; class BeardFilterPainter extends FaceFilterPainter { BeardFilterPainter( + FaceFilterType beardType, super.faces, super.imageSize, super.rotation, super.cameraLensDirection, ) { - _loadAssets(); + _loadAssets(beardType); } + static FaceFilterType? _lastLoadedBeardType; static ui.Image? _beardImage; static bool _loading = false; - static Future _loadAssets() async { - if (_loading || _beardImage != null) return; + static String getAssetPath(FaceFilterType beardType) { + switch (beardType) { + case FaceFilterType.beardUpperLip: + return 'assets/filters/beard_upper_lip.webp'; + case FaceFilterType.beardUpperLipGreen: + return 'assets/filters/beard_upper_lip_green.webp'; + case FaceFilterType.dogBrown: + case FaceFilterType.none: + return ''; + } + } + + static Future _loadAssets(FaceFilterType beardType) async { + if ((_loading || _beardImage != null) && + _lastLoadedBeardType == beardType) { + return; + } _loading = true; try { - _beardImage = await _loadImage('assets/filters/beard_upper_lip.webp'); + _beardImage = await _loadImage(getAssetPath(beardType)); } catch (e) { Log.error('Failed to load filter assets: $e'); } finally { @@ -161,12 +179,12 @@ class BeardFilterPainter extends FaceFilterPainter { ..restore(); } - static Widget getPreview() { + static Widget getPreview(FaceFilterType beardType) { return Preview( child: Padding( padding: const EdgeInsets.all(8), child: Image.asset( - 'assets/filters/beard_upper_lip.webp', + getAssetPath(beardType), fit: BoxFit.contain, ), ),