| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "services/shape_detection/face_detection_impl_mac_vision.h" |
| |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "third_party/skia/include/core/SkBitmap.h" |
| |
| namespace shape_detection { |
| |
| namespace { |
| |
| mojom::LandmarkPtr API_AVAILABLE(macos(10.13)) |
| BuildLandmark(VNFaceLandmarkRegion2D* landmark_region, |
| mojom::LandmarkType landmark_type, |
| gfx::RectF bounding_box) { |
| auto landmark = mojom::Landmark::New(); |
| landmark->type = landmark_type; |
| landmark->locations.reserve(landmark_region.pointCount); |
| for (NSUInteger i = 0; i < landmark_region.pointCount; ++i) { |
| // The points are normalized to the bounding box of the detected face. |
| landmark->locations.emplace_back( |
| landmark_region.normalizedPoints[i].x * bounding_box.width() + |
| bounding_box.x(), |
| (1 - landmark_region.normalizedPoints[i].y) * bounding_box.height() + |
| bounding_box.y()); |
| } |
| return landmark; |
| } |
| } |
| |
| FaceDetectionImplMacVision::FaceDetectionImplMacVision() : weak_factory_(this) { |
| Class request_class = NSClassFromString(@"VNDetectFaceLandmarksRequest"); |
| if (!request_class) { |
| DLOG(ERROR) << "Failed to load VNDetectFaceLandmarksRequest class"; |
| return; |
| } |
| // The repeating callback will not be run if FaceDetectionImplMacVision object |
| // has already been destroyed. |
| landmarks_async_request_ = VisionAPIAsyncRequestMac::Create( |
| request_class, |
| base::BindRepeating(&FaceDetectionImplMacVision::OnFacesDetected, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| FaceDetectionImplMacVision::~FaceDetectionImplMacVision() = default; |
| |
| void FaceDetectionImplMacVision::Detect(const SkBitmap& bitmap, |
| DetectCallback callback) { |
| DCHECK(landmarks_async_request_); |
| |
| if (!landmarks_async_request_->PerformRequest(bitmap)) { |
| std::move(callback).Run({}); |
| return; |
| } |
| |
| image_size_ = CGSizeMake(bitmap.width(), bitmap.height()); |
| // Hold on the callback until async request completes. |
| detected_callback_ = std::move(callback); |
| // This prevents the Detect function from being called before the |
| // VisionAPIAsyncRequestMac completes. |
| if (receiver_) // Can be unbound in unit testing. |
| receiver_->PauseIncomingMethodCallProcessing(); |
| } |
| |
| void FaceDetectionImplMacVision::OnFacesDetected(VNRequest* request, |
| NSError* error) { |
| if (receiver_) // Can be unbound in unit testing. |
| receiver_->ResumeIncomingMethodCallProcessing(); |
| |
| if (![request.results count] || error) { |
| std::move(detected_callback_).Run({}); |
| return; |
| } |
| |
| std::vector<mojom::FaceDetectionResultPtr> results; |
| for (VNFaceObservation* const observation in request.results) { |
| auto face = mojom::FaceDetectionResult::New(); |
| // The coordinate are normalized to the dimensions of the processed image. |
| face->bounding_box = ConvertCGToGfxCoordinates( |
| CGRectMake(observation.boundingBox.origin.x * image_size_.width, |
| observation.boundingBox.origin.y * image_size_.height, |
| observation.boundingBox.size.width * image_size_.width, |
| observation.boundingBox.size.height * image_size_.height), |
| image_size_.height); |
| |
| if (VNFaceLandmarkRegion2D* leftEye = observation.landmarks.leftEye) { |
| face->landmarks.push_back( |
| BuildLandmark(leftEye, mojom::LandmarkType::EYE, face->bounding_box)); |
| } |
| if (VNFaceLandmarkRegion2D* rightEye = observation.landmarks.rightEye) { |
| face->landmarks.push_back(BuildLandmark( |
| rightEye, mojom::LandmarkType::EYE, face->bounding_box)); |
| } |
| if (VNFaceLandmarkRegion2D* outerLips = observation.landmarks.outerLips) { |
| face->landmarks.push_back(BuildLandmark( |
| outerLips, mojom::LandmarkType::MOUTH, face->bounding_box)); |
| } |
| if (VNFaceLandmarkRegion2D* nose = observation.landmarks.nose) { |
| face->landmarks.push_back( |
| BuildLandmark(nose, mojom::LandmarkType::NOSE, face->bounding_box)); |
| } |
| |
| results.push_back(std::move(face)); |
| } |
| std::move(detected_callback_).Run(std::move(results)); |
| |
| return; |
| } |
| |
| } // namespace shape_detection |