| // 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/barcode_detection_impl_mac_vision.h" |
| |
| #include <Foundation/Foundation.h> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/containers/flat_set.h" |
| #include "base/logging.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "third_party/skia/include/core/SkBitmap.h" |
| |
| namespace shape_detection { |
| |
| namespace { |
| |
| mojom::BarcodeFormat ToBarcodeFormat(NSString* symbology) { |
| if ([symbology isEqual:@"VNBarcodeSymbologyAztec"]) |
| return mojom::BarcodeFormat::AZTEC; |
| if ([symbology isEqual:@"VNBarcodeSymbologyCode128"]) |
| return mojom::BarcodeFormat::CODE_128; |
| if ([symbology isEqual:@"VNBarcodeSymbologyCode39"] || |
| [symbology isEqual:@"VNBarcodeSymbologyCode39Checksum"] || |
| [symbology isEqual:@"VNBarcodeSymbologyCode39FullASCII"] || |
| [symbology isEqual:@"VNBarcodeSymbologyCode39FullASCIIChecksum"]) { |
| return mojom::BarcodeFormat::CODE_39; |
| } |
| if ([symbology isEqual:@"VNBarcodeSymbologyCode93"] || |
| [symbology isEqual:@"VNBarcodeSymbologyCode93i"]) { |
| return mojom::BarcodeFormat::CODE_93; |
| } |
| if ([symbology isEqual:@"VNBarcodeSymbologyDataMatrix"]) |
| return mojom::BarcodeFormat::DATA_MATRIX; |
| if ([symbology isEqual:@"VNBarcodeSymbologyEAN13"]) |
| return mojom::BarcodeFormat::EAN_13; |
| if ([symbology isEqual:@"VNBarcodeSymbologyEAN8"]) |
| return mojom::BarcodeFormat::EAN_8; |
| if ([symbology isEqual:@"VNBarcodeSymbologyITF14"] || |
| [symbology isEqual:@"VNBarcodeSymbologyI2of5"] || |
| [symbology isEqual:@"VNBarcodeSymbologyI2of5Checksum"]) { |
| return mojom::BarcodeFormat::ITF; |
| } |
| if ([symbology isEqual:@"VNBarcodeSymbologyPDF417"]) |
| return mojom::BarcodeFormat::PDF417; |
| if ([symbology isEqual:@"VNBarcodeSymbologyQR"]) |
| return mojom::BarcodeFormat::QR_CODE; |
| if ([symbology isEqual:@"VNBarcodeSymbologyUPCE"]) |
| return mojom::BarcodeFormat::UPC_E; |
| return mojom::BarcodeFormat::UNKNOWN; |
| } |
| |
| void UpdateSymbologyHint(mojom::BarcodeFormat format, |
| NSMutableSet<NSString*>* hint) { |
| // TODO(crbug/943106): use SDK header-declared constants after updating SDK. |
| switch (format) { |
| case mojom::BarcodeFormat::AZTEC: |
| [hint addObject:@"VNBarcodeSymbologyAztec"]; |
| return; |
| case mojom::BarcodeFormat::CODE_128: |
| [hint addObject:@"VNBarcodeSymbologyCode128"]; |
| return; |
| case mojom::BarcodeFormat::CODE_39: |
| [hint addObjectsFromArray:@[ |
| @"VNBarcodeSymbologyCode39", @"VNBarcodeSymbologyCode39Checksum", |
| @"VNBarcodeSymbologyCode39FullASCII", |
| @"VNBarcodeSymbologyCode39FullASCIIChecksum" |
| ]]; |
| return; |
| case mojom::BarcodeFormat::CODE_93: |
| [hint addObjectsFromArray:@[ |
| @"VNBarcodeSymbologyCode93", @"VNBarcodeSymbologyCode93i" |
| ]]; |
| return; |
| case mojom::BarcodeFormat::CODABAR: |
| return; |
| case mojom::BarcodeFormat::DATA_MATRIX: |
| [hint addObject:@"VNBarcodeSymbologyDataMatrix"]; |
| return; |
| case mojom::BarcodeFormat::EAN_13: |
| [hint addObject:@"VNBarcodeSymbologyEAN13"]; |
| return; |
| case mojom::BarcodeFormat::EAN_8: |
| [hint addObject:@"VNBarcodeSymbologyEAN8"]; |
| return; |
| case mojom::BarcodeFormat::ITF: |
| [hint addObjectsFromArray:@[ |
| @"VNBarcodeSymbologyITF14", @"VNBarcodeSymbologyI2of5", |
| @"VNBarcodeSymbologyI2of5Checksum" |
| ]]; |
| return; |
| case mojom::BarcodeFormat::PDF417: |
| [hint addObject:@"VNBarcodeSymbologyPDF417"]; |
| return; |
| case mojom::BarcodeFormat::QR_CODE: |
| [hint addObject:@"VNBarcodeSymbologyQR"]; |
| return; |
| case mojom::BarcodeFormat::UPC_A: |
| return; |
| case mojom::BarcodeFormat::UPC_E: |
| [hint addObject:@"VNBarcodeSymbologyUPC_E"]; |
| return; |
| case mojom::BarcodeFormat::UNKNOWN: |
| NOTREACHED(); |
| return; |
| } |
| } |
| |
| } // unnamed namespace |
| |
| // static |
| bool BarcodeDetectionImplMacVision::IsBlockedMacOSVersion() { |
| static NSOperatingSystemVersion version = |
| [[NSProcessInfo processInfo] operatingSystemVersion]; |
| DCHECK_EQ(version.majorVersion, 10); |
| // Vision Framework doesn't work properly on 10.14.{0,1,2}: crbug.com/921968. |
| return version.minorVersion == 14 && version.patchVersion < 3; |
| } |
| |
| BarcodeDetectionImplMacVision::BarcodeDetectionImplMacVision( |
| mojom::BarcodeDetectorOptionsPtr options) |
| : weak_factory_(this) { |
| Class request_class = NSClassFromString(@"VNDetectBarcodesRequest"); |
| if (!request_class) { |
| DLOG(ERROR) << "Failed to load VNDetectBarcodesRequest class"; |
| return; |
| } |
| |
| NSMutableSet<NSString*>* symbology_hints = [NSMutableSet set]; |
| for (const auto& hint : options->formats) { |
| if (hint == mojom::BarcodeFormat::UNKNOWN) { |
| mojo::ReportBadMessage("Formats hint contains UNKNOWN BarcodeFormat."); |
| return; |
| } |
| |
| UpdateSymbologyHint(hint, symbology_hints); |
| } |
| symbology_hints_.reset([symbology_hints retain]); |
| |
| // The repeating callback will not be run if BarcodeDetectionImplMacVision |
| // object has already been destroyed. |
| barcodes_async_request_ = VisionAPIAsyncRequestMac::Create( |
| request_class, |
| base::BindRepeating(&BarcodeDetectionImplMacVision::OnBarcodesDetected, |
| weak_factory_.GetWeakPtr()), |
| symbology_hints_.get()); |
| } |
| |
| BarcodeDetectionImplMacVision::~BarcodeDetectionImplMacVision() = default; |
| |
| void BarcodeDetectionImplMacVision::Detect(const SkBitmap& bitmap, |
| DetectCallback callback) { |
| DCHECK(barcodes_async_request_); |
| |
| if (!barcodes_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 (binding_) // Can be unbound in unit testing. |
| binding_->PauseIncomingMethodCallProcessing(); |
| } |
| |
| void BarcodeDetectionImplMacVision::OnBarcodesDetected(VNRequest* request, |
| NSError* error) { |
| if (binding_) // Can be unbound in unit testing. |
| binding_->ResumeIncomingMethodCallProcessing(); |
| |
| if ([request.results count] == 0 || error) { |
| std::move(detected_callback_).Run({}); |
| return; |
| } |
| |
| std::vector<mojom::BarcodeDetectionResultPtr> results; |
| for (VNBarcodeObservation* const observation in request.results) { |
| auto barcode = mojom::BarcodeDetectionResult::New(); |
| // The coordinates are normalized to the dimensions of the processed image. |
| barcode->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); |
| |
| // Enumerate corner points starting from top-left in clockwise fashion: |
| // https://wicg.github.io/shape-detection-api/#dom-detectedbarcode-cornerpoints |
| barcode->corner_points.emplace_back( |
| observation.topLeft.x * image_size_.width, |
| (1 - observation.topLeft.y) * image_size_.height); |
| barcode->corner_points.emplace_back( |
| observation.topRight.x * image_size_.width, |
| (1 - observation.topRight.y) * image_size_.height); |
| barcode->corner_points.emplace_back( |
| observation.bottomRight.x * image_size_.width, |
| (1 - observation.bottomRight.y) * image_size_.height); |
| barcode->corner_points.emplace_back( |
| observation.bottomLeft.x * image_size_.width, |
| (1 - observation.bottomLeft.y) * image_size_.height); |
| |
| barcode->raw_value = |
| base::SysNSStringToUTF8(observation.payloadStringValue); |
| |
| barcode->format = ToBarcodeFormat(observation.symbology); |
| |
| results.push_back(std::move(barcode)); |
| } |
| std::move(detected_callback_).Run(std::move(results)); |
| } |
| |
| // static |
| std::vector<shape_detection::mojom::BarcodeFormat> |
| BarcodeDetectionImplMacVision::GetSupportedSymbologies( |
| VisionAPIInterface* vision_api) { |
| std::unique_ptr<VisionAPIInterface> scoped_vision_api; |
| if (!vision_api) { |
| scoped_vision_api = VisionAPIInterface::Create(); |
| vision_api = scoped_vision_api.get(); |
| } |
| base::flat_set<shape_detection::mojom::BarcodeFormat> results; |
| NSArray<NSString*>* symbologies = vision_api->GetSupportedSymbologies(); |
| |
| results.reserve([symbologies count]); |
| for (NSString* symbology : symbologies) { |
| auto converted = ToBarcodeFormat(symbology); |
| if (converted == shape_detection::mojom::BarcodeFormat::UNKNOWN) { |
| DLOG(WARNING) << "Symbology " << base::SysNSStringToUTF8(symbology) |
| << " unknown to spec."; |
| continue; |
| } |
| results.insert(converted); |
| } |
| return std::vector<shape_detection::mojom::BarcodeFormat>(results.begin(), |
| results.end()); |
| } |
| |
| NSSet<NSString*>* BarcodeDetectionImplMacVision::GetSymbologyHintsForTesting() { |
| return symbology_hints_.get(); |
| } |
| |
| } // namespace shape_detection |