/* * PhoneGap is available under *either* the terms of the modified BSD license *or* the * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. * * Copyright 2011 Matt Kane. All rights reserved. * Copyright (c) 2011, IBM Corporation */ #import #import #import //------------------------------------------------------------------------------ // Delegate to handle orientation functions //------------------------------------------------------------------------------ @protocol CDVBarcodeScannerOrientationDelegate - (NSUInteger)supportedInterfaceOrientations; - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation; - (BOOL)shouldAutorotate; @end //------------------------------------------------------------------------------ // Adds a shutter button to the UI, and changes the scan from continuous to // only performing a scan when you click the shutter button. For testing. //------------------------------------------------------------------------------ #define USE_SHUTTER 0 //------------------------------------------------------------------------------ @class CDVbcsProcessor; @class CDVbcsViewController; //------------------------------------------------------------------------------ // plugin class //------------------------------------------------------------------------------ @interface CDVBarcodeScanner : CDVPlugin {} - (NSString*)isScanNotPossible; - (void)scan:(CDVInvokedUrlCommand*)command; - (void)encode:(CDVInvokedUrlCommand*)command; - (void)returnImage:(NSString*)filePath format:(NSString*)format callback:(NSString*)callback; - (void)returnSuccess:(NSString*)scannedText format:(NSString*)format cancelled:(BOOL)cancelled flipped:(BOOL)flipped callback:(NSString*)callback; - (void)returnError:(NSString*)message callback:(NSString*)callback; @end //------------------------------------------------------------------------------ // class that does the grunt work //------------------------------------------------------------------------------ @interface CDVbcsProcessor : NSObject {} @property (nonatomic, retain) CDVBarcodeScanner* plugin; @property (nonatomic, retain) NSString* callback; @property (nonatomic, retain) UIViewController* parentViewController; @property (nonatomic, retain) CDVbcsViewController* viewController; @property (nonatomic, retain) AVCaptureSession* captureSession; @property (nonatomic, retain) AVCaptureVideoPreviewLayer* previewLayer; @property (nonatomic, retain) NSString* alternateXib; @property (nonatomic, retain) NSMutableArray* results; @property (nonatomic, retain) NSString* formats; @property (nonatomic) BOOL is1D; @property (nonatomic) BOOL is2D; @property (nonatomic) BOOL capturing; @property (nonatomic) BOOL isFrontCamera; @property (nonatomic) BOOL isShowFlipCameraButton; @property (nonatomic) BOOL isShowTorchButton; @property (nonatomic) BOOL isFlipped; @property (nonatomic) BOOL isTransitionAnimated; @property (nonatomic) BOOL isSuccessBeepEnabled; - (id)initWithPlugin:(CDVBarcodeScanner*)plugin callback:(NSString*)callback parentViewController:(UIViewController*)parentViewController alterateOverlayXib:(NSString *)alternateXib; - (void)scanBarcode; - (void)barcodeScanSucceeded:(NSString*)text format:(NSString*)format; - (void)barcodeScanFailed:(NSString*)message; - (void)barcodeScanCancelled; - (void)openDialog; - (NSString*)setUpCaptureSession; - (void)captureOutput:(AVCaptureOutput*)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection*)connection; @end //------------------------------------------------------------------------------ // Qr encoder processor //------------------------------------------------------------------------------ @interface CDVqrProcessor: NSObject @property (nonatomic, retain) CDVBarcodeScanner* plugin; @property (nonatomic, retain) NSString* callback; @property (nonatomic, retain) NSString* stringToEncode; @property NSInteger size; - (id)initWithPlugin:(CDVBarcodeScanner*)plugin callback:(NSString*)callback stringToEncode:(NSString*)stringToEncode; - (void)generateImage; @end //------------------------------------------------------------------------------ // view controller for the ui //------------------------------------------------------------------------------ @interface CDVbcsViewController : UIViewController {} @property (nonatomic, retain) CDVbcsProcessor* processor; @property (nonatomic, retain) NSString* alternateXib; @property (nonatomic) BOOL shutterPressed; @property (nonatomic, retain) IBOutlet UIView* overlayView; @property (nonatomic, retain) UIToolbar * toolbar; @property (nonatomic, retain) UIView * reticleView; // unsafe_unretained is equivalent to assign - used to prevent retain cycles in the property below @property (nonatomic, unsafe_unretained) id orientationDelegate; - (id)initWithProcessor:(CDVbcsProcessor*)processor alternateOverlay:(NSString *)alternateXib; - (void)startCapturing; - (UIView*)buildOverlayView; - (UIImage*)buildReticleImage; - (void)shutterButtonPressed; - (IBAction)cancelButtonPressed:(id)sender; - (IBAction)flipCameraButtonPressed:(id)sender; - (IBAction)torchButtonPressed:(id)sender; @end //------------------------------------------------------------------------------ // plugin class //------------------------------------------------------------------------------ @implementation CDVBarcodeScanner //-------------------------------------------------------------------------- - (NSString*)isScanNotPossible { NSString* result = nil; Class aClass = NSClassFromString(@"AVCaptureSession"); if (aClass == nil) { return @"AVFoundation Framework not available"; } return result; } -(BOOL)notHasPermission { AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]; return (authStatus == AVAuthorizationStatusDenied || authStatus == AVAuthorizationStatusRestricted); } -(BOOL)isUsageDescriptionSet { NSDictionary * plist = [[NSBundle mainBundle] infoDictionary]; if ([plist objectForKey:@"NSCameraUsageDescription" ] || [[NSBundle mainBundle] localizedStringForKey: @"NSCameraUsageDescription" value: nil table: @"InfoPlist"]) { return YES; } return NO; } //-------------------------------------------------------------------------- - (void)scan:(CDVInvokedUrlCommand*)command { CDVbcsProcessor* processor; NSString* callback; NSString* capabilityError; callback = command.callbackId; NSDictionary* options; if (command.arguments.count == 0) { options = [NSDictionary dictionary]; } else { options = command.arguments[0]; } BOOL preferFrontCamera = [options[@"preferFrontCamera"] boolValue]; BOOL showFlipCameraButton = [options[@"showFlipCameraButton"] boolValue]; BOOL showTorchButton = [options[@"showTorchButton"] boolValue]; BOOL disableAnimations = [options[@"disableAnimations"] boolValue]; BOOL disableSuccessBeep = [options[@"disableSuccessBeep"] boolValue]; // We allow the user to define an alternate xib file for loading the overlay. NSString *overlayXib = options[@"overlayXib"]; capabilityError = [self isScanNotPossible]; if (capabilityError) { [self returnError:capabilityError callback:callback]; return; } else if ([self notHasPermission]) { NSString * error = NSLocalizedString(@"Access to the camera has been prohibited; please enable it in the Settings app to continue.",nil); [self returnError:error callback:callback]; return; } else if (![self isUsageDescriptionSet]) { NSString * error = NSLocalizedString(@"NSCameraUsageDescription is not set in the info.plist", nil); [self returnError:error callback:callback]; return; } processor = [[CDVbcsProcessor alloc] initWithPlugin:self callback:callback parentViewController:self.viewController alterateOverlayXib:overlayXib ]; // queue [processor scanBarcode] to run on the event loop if (preferFrontCamera) { processor.isFrontCamera = true; } if (showFlipCameraButton) { processor.isShowFlipCameraButton = true; } if (showTorchButton) { processor.isShowTorchButton = true; } processor.isSuccessBeepEnabled = !disableSuccessBeep; processor.isTransitionAnimated = !disableAnimations; processor.formats = options[@"formats"]; [processor performSelector:@selector(scanBarcode) withObject:nil afterDelay:0]; } //-------------------------------------------------------------------------- - (void)encode:(CDVInvokedUrlCommand*)command { if([command.arguments count] < 1) [self returnError:@"Too few arguments!" callback:command.callbackId]; CDVqrProcessor* processor; NSString* callback; callback = command.callbackId; processor = [[CDVqrProcessor alloc] initWithPlugin:self callback:callback stringToEncode: command.arguments[0][@"data"] ]; // queue [processor generateImage] to run on the event loop [processor performSelector:@selector(generateImage) withObject:nil afterDelay:0]; } - (void)returnImage:(NSString*)filePath format:(NSString*)format callback:(NSString*)callback{ NSMutableDictionary* resultDict = [[NSMutableDictionary alloc] init]; resultDict[@"format"] = format; resultDict[@"file"] = filePath; CDVPluginResult* result = [CDVPluginResult resultWithStatus: CDVCommandStatus_OK messageAsDictionary:resultDict ]; [[self commandDelegate] sendPluginResult:result callbackId:callback]; } //-------------------------------------------------------------------------- - (void)returnSuccess:(NSString*)scannedText format:(NSString*)format cancelled:(BOOL)cancelled flipped:(BOOL)flipped callback:(NSString*)callback{ NSNumber* cancelledNumber = @(cancelled ? 1 : 0); NSMutableDictionary* resultDict = [NSMutableDictionary new]; resultDict[@"text"] = scannedText; resultDict[@"format"] = format; resultDict[@"cancelled"] = cancelledNumber; CDVPluginResult* result = [CDVPluginResult resultWithStatus: CDVCommandStatus_OK messageAsDictionary: resultDict ]; [self.commandDelegate sendPluginResult:result callbackId:callback]; } //-------------------------------------------------------------------------- - (void)returnError:(NSString*)message callback:(NSString*)callback { CDVPluginResult* result = [CDVPluginResult resultWithStatus: CDVCommandStatus_ERROR messageAsString: message ]; [self.commandDelegate sendPluginResult:result callbackId:callback]; } @end //------------------------------------------------------------------------------ // class that does the grunt work //------------------------------------------------------------------------------ @implementation CDVbcsProcessor @synthesize plugin = _plugin; @synthesize callback = _callback; @synthesize parentViewController = _parentViewController; @synthesize viewController = _viewController; @synthesize captureSession = _captureSession; @synthesize previewLayer = _previewLayer; @synthesize alternateXib = _alternateXib; @synthesize is1D = _is1D; @synthesize is2D = _is2D; @synthesize capturing = _capturing; @synthesize results = _results; SystemSoundID _soundFileObject; //-------------------------------------------------------------------------- - (id)initWithPlugin:(CDVBarcodeScanner*)plugin callback:(NSString*)callback parentViewController:(UIViewController*)parentViewController alterateOverlayXib:(NSString *)alternateXib { self = [super init]; if (!self) return self; self.plugin = plugin; self.callback = callback; self.parentViewController = parentViewController; self.alternateXib = alternateXib; self.is1D = YES; self.is2D = YES; self.capturing = NO; self.results = [NSMutableArray new]; CFURLRef soundFileURLRef = CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR("CDVBarcodeScanner.bundle/beep"), CFSTR ("caf"), NULL); AudioServicesCreateSystemSoundID(soundFileURLRef, &_soundFileObject); return self; } //-------------------------------------------------------------------------- - (void)dealloc { self.plugin = nil; self.callback = nil; self.parentViewController = nil; self.viewController = nil; self.captureSession = nil; self.previewLayer = nil; self.alternateXib = nil; self.results = nil; self.capturing = NO; AudioServicesRemoveSystemSoundCompletion(_soundFileObject); AudioServicesDisposeSystemSoundID(_soundFileObject); } //-------------------------------------------------------------------------- - (void)scanBarcode { // self.captureSession = nil; // self.previewLayer = nil; NSString* errorMessage = [self setUpCaptureSession]; if (errorMessage) { [self barcodeScanFailed:errorMessage]; return; } self.viewController = [[CDVbcsViewController alloc] initWithProcessor: self alternateOverlay:self.alternateXib]; // here we set the orientation delegate to the MainViewController of the app (orientation controlled in the Project Settings) self.viewController.orientationDelegate = self.plugin.viewController; // delayed [self openDialog]; [self performSelector:@selector(openDialog) withObject:nil afterDelay:1]; } //-------------------------------------------------------------------------- - (void)openDialog { [self.parentViewController presentViewController:self.viewController animated:self.isTransitionAnimated completion:nil ]; } //-------------------------------------------------------------------------- - (void)barcodeScanDone:(void (^)(void))callbackBlock { self.capturing = NO; [self.captureSession stopRunning]; [self.parentViewController dismissViewControllerAnimated:self.isTransitionAnimated completion:callbackBlock]; AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; [device lockForConfiguration:nil]; if([device isAutoFocusRangeRestrictionSupported]) { [device setAutoFocusRangeRestriction:AVCaptureAutoFocusRangeRestrictionNone]; } [device unlockForConfiguration]; // viewcontroller holding onto a reference to us, release them so they // will release us self.viewController = nil; } //-------------------------------------------------------------------------- - (BOOL)checkResult:(NSString *)result { [self.results addObject:result]; NSInteger treshold = 7; if (self.results.count > treshold) { [self.results removeObjectAtIndex:0]; } if (self.results.count < treshold) { return NO; } BOOL allEqual = YES; NSString *compareString = self.results[0]; for (NSString *aResult in self.results) { if (![compareString isEqualToString:aResult]) { allEqual = NO; //NSLog(@"Did not fit: %@",self.results); break; } } return allEqual; } //-------------------------------------------------------------------------- - (void)barcodeScanSucceeded:(NSString*)text format:(NSString*)format { dispatch_sync(dispatch_get_main_queue(), ^{ if (self.isSuccessBeepEnabled) { AudioServicesPlaySystemSound(_soundFileObject); } [self barcodeScanDone:^{ [self.plugin returnSuccess:text format:format cancelled:FALSE flipped:FALSE callback:self.callback]; }]; }); } //-------------------------------------------------------------------------- - (void)barcodeScanFailed:(NSString*)message { dispatch_block_t block = ^{ [self barcodeScanDone:^{ [self.plugin returnError:message callback:self.callback]; }]; }; if ([NSThread isMainThread]) { block(); } else { dispatch_sync(dispatch_get_main_queue(), block); } } //-------------------------------------------------------------------------- - (void)barcodeScanCancelled { [self barcodeScanDone:^{ [self.plugin returnSuccess:@"" format:@"" cancelled:TRUE flipped:self.isFlipped callback:self.callback]; }]; if (self.isFlipped) { self.isFlipped = NO; } } - (void)flipCamera { self.isFlipped = YES; self.isFrontCamera = !self.isFrontCamera; [self barcodeScanDone:^{ if (self.isFlipped) { self.isFlipped = NO; } [self performSelector:@selector(scanBarcode) withObject:nil afterDelay:0.1]; }]; } - (void)toggleTorch { AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; [device lockForConfiguration:nil]; if (device.flashActive) { [device setTorchMode:AVCaptureTorchModeOff]; [device setFlashMode:AVCaptureFlashModeOff]; } else { [device setTorchModeOnWithLevel:AVCaptureMaxAvailableTorchLevel error:nil]; [device setFlashMode:AVCaptureFlashModeOn]; } [device unlockForConfiguration]; } //-------------------------------------------------------------------------- - (NSString*)setUpCaptureSession { NSError* error = nil; AVCaptureSession* captureSession = [[AVCaptureSession alloc] init]; self.captureSession = captureSession; AVCaptureDevice* __block device = nil; if (self.isFrontCamera) { NSArray* devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; [devices enumerateObjectsUsingBlock:^(AVCaptureDevice *obj, NSUInteger idx, BOOL *stop) { if (obj.position == AVCaptureDevicePositionFront) { device = obj; } }]; } else { device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; if (!device) return @"unable to obtain video capture device"; } // set focus params if available to improve focusing [device lockForConfiguration:&error]; if (error == nil) { if([device isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) { [device setFocusMode:AVCaptureFocusModeContinuousAutoFocus]; } if([device isAutoFocusRangeRestrictionSupported]) { [device setAutoFocusRangeRestriction:AVCaptureAutoFocusRangeRestrictionNear]; } } [device unlockForConfiguration]; AVCaptureDeviceInput* input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error]; if (!input) return @"unable to obtain video capture device input"; AVCaptureMetadataOutput* output = [[AVCaptureMetadataOutput alloc] init]; if (!output) return @"unable to obtain video capture output"; [output setMetadataObjectsDelegate:self queue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)]; if ([captureSession canSetSessionPreset:AVCaptureSessionPresetHigh]) { captureSession.sessionPreset = AVCaptureSessionPresetHigh; } else if ([captureSession canSetSessionPreset:AVCaptureSessionPresetMedium]) { captureSession.sessionPreset = AVCaptureSessionPresetMedium; } else { return @"unable to preset high nor medium quality video capture"; } if ([captureSession canAddInput:input]) { [captureSession addInput:input]; } else { return @"unable to add video capture device input to session"; } if ([captureSession canAddOutput:output]) { [captureSession addOutput:output]; } else { return @"unable to add video capture output to session"; } [output setMetadataObjectTypes:[self formatObjectTypes]]; // setup capture preview layer self.previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:captureSession]; self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; // run on next event loop pass [captureSession startRunning] [captureSession performSelector:@selector(startRunning) withObject:nil afterDelay:0]; return nil; } //-------------------------------------------------------------------------- // this method gets sent the captured frames //-------------------------------------------------------------------------- - (void)captureOutput:(AVCaptureOutput*)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection*)connection { if (!self.capturing) return; #if USE_SHUTTER if (!self.viewController.shutterPressed) return; self.viewController.shutterPressed = NO; UIView* flashView = [[UIView alloc] initWithFrame:self.viewController.view.frame]; [flashView setBackgroundColor:[UIColor whiteColor]]; [self.viewController.view.window addSubview:flashView]; [UIView animateWithDuration:.4f animations:^{ [flashView setAlpha:0.f]; } completion:^(BOOL finished){ [flashView removeFromSuperview]; } ]; #endif try { // This will bring in multiple entities if there are multiple 2D codes in frame. for (AVMetadataObject *metaData in metadataObjects) { AVMetadataMachineReadableCodeObject* code = (AVMetadataMachineReadableCodeObject*)[self.previewLayer transformedMetadataObjectForMetadataObject:(AVMetadataMachineReadableCodeObject*)metaData]; if ([self checkResult:code.stringValue]) { [self barcodeScanSucceeded:code.stringValue format:[self formatStringFromMetadata:code]]; } } } catch (...) { // NSLog(@"decoding: unknown exception"); // [self barcodeScanFailed:@"unknown exception decoding barcode"]; } // NSTimeInterval timeElapsed = [NSDate timeIntervalSinceReferenceDate] - timeStart; // NSLog(@"decoding completed in %dms", (int) (timeElapsed * 1000)); } //-------------------------------------------------------------------------- // convert metadata object information to barcode format string //-------------------------------------------------------------------------- - (NSString*)formatStringFromMetadata:(AVMetadataMachineReadableCodeObject*)format { if (format.type == AVMetadataObjectTypeQRCode) return @"QR_CODE"; if (format.type == AVMetadataObjectTypeAztecCode) return @"AZTEC"; if (format.type == AVMetadataObjectTypeDataMatrixCode) return @"DATA_MATRIX"; if (format.type == AVMetadataObjectTypeUPCECode) return @"UPC_E"; // According to Apple documentation, UPC_A is EAN13 with a leading 0. if (format.type == AVMetadataObjectTypeEAN13Code && [format.stringValue characterAtIndex:0] == '0') return @"UPC_A"; if (format.type == AVMetadataObjectTypeEAN8Code) return @"EAN_8"; if (format.type == AVMetadataObjectTypeEAN13Code) return @"EAN_13"; if (format.type == AVMetadataObjectTypeCode128Code) return @"CODE_128"; if (format.type == AVMetadataObjectTypeCode93Code) return @"CODE_93"; if (format.type == AVMetadataObjectTypeCode39Code) return @"CODE_39"; if (format.type == AVMetadataObjectTypeInterleaved2of5Code) return @"ITF"; if (format.type == AVMetadataObjectTypeITF14Code) return @"ITF_14"; if (format.type == AVMetadataObjectTypePDF417Code) return @"PDF_417"; return @"???"; } //-------------------------------------------------------------------------- // convert string formats to metadata objects //-------------------------------------------------------------------------- - (NSArray*) formatObjectTypes { NSArray *supportedFormats = nil; if (self.formats != nil) { supportedFormats = [self.formats componentsSeparatedByString:@","]; } NSMutableArray * formatObjectTypes = [NSMutableArray array]; if (self.formats == nil || [supportedFormats containsObject:@"QR_CODE"]) [formatObjectTypes addObject:AVMetadataObjectTypeQRCode]; if (self.formats == nil || [supportedFormats containsObject:@"AZTEC"]) [formatObjectTypes addObject:AVMetadataObjectTypeAztecCode]; if (self.formats == nil || [supportedFormats containsObject:@"DATA_MATRIX"]) [formatObjectTypes addObject:AVMetadataObjectTypeDataMatrixCode]; if (self.formats == nil || [supportedFormats containsObject:@"UPC_E"]) [formatObjectTypes addObject:AVMetadataObjectTypeUPCECode]; if (self.formats == nil || [supportedFormats containsObject:@"EAN_8"]) [formatObjectTypes addObject:AVMetadataObjectTypeEAN8Code]; if (self.formats == nil || [supportedFormats containsObject:@"EAN_13"]) [formatObjectTypes addObject:AVMetadataObjectTypeEAN13Code]; if (self.formats == nil || [supportedFormats containsObject:@"CODE_128"]) [formatObjectTypes addObject:AVMetadataObjectTypeCode128Code]; if (self.formats == nil || [supportedFormats containsObject:@"CODE_93"]) [formatObjectTypes addObject:AVMetadataObjectTypeCode93Code]; if (self.formats == nil || [supportedFormats containsObject:@"CODE_39"]) [formatObjectTypes addObject:AVMetadataObjectTypeCode39Code]; if (self.formats == nil || [supportedFormats containsObject:@"ITF"]) [formatObjectTypes addObject:AVMetadataObjectTypeInterleaved2of5Code]; if (self.formats == nil || [supportedFormats containsObject:@"ITF_14"]) [formatObjectTypes addObject:AVMetadataObjectTypeITF14Code]; if (self.formats == nil || [supportedFormats containsObject:@"PDF_417"]) [formatObjectTypes addObject:AVMetadataObjectTypePDF417Code]; return formatObjectTypes; } @end //------------------------------------------------------------------------------ // qr encoder processor //------------------------------------------------------------------------------ @implementation CDVqrProcessor @synthesize plugin = _plugin; @synthesize callback = _callback; @synthesize stringToEncode = _stringToEncode; @synthesize size = _size; - (id)initWithPlugin:(CDVBarcodeScanner*)plugin callback:(NSString*)callback stringToEncode:(NSString*)stringToEncode{ self = [super init]; if (!self) return self; self.plugin = plugin; self.callback = callback; self.stringToEncode = stringToEncode; self.size = 300; return self; } //-------------------------------------------------------------------------- - (void)dealloc { self.plugin = nil; self.callback = nil; self.stringToEncode = nil; } //-------------------------------------------------------------------------- - (void)generateImage{ /* setup qr filter */ CIFilter *filter = [CIFilter filterWithName:@"CIQRCodeGenerator"]; [filter setDefaults]; /* set filter's input message * the encoding string has to be convert to a UTF-8 encoded NSData object */ [filter setValue:[self.stringToEncode dataUsingEncoding:NSUTF8StringEncoding] forKey:@"inputMessage"]; /* on ios >= 7.0 set low image error correction level */ if (floor(NSFoundationVersionNumber) >= NSFoundationVersionNumber_iOS_7_0) [filter setValue:@"L" forKey:@"inputCorrectionLevel"]; /* prepare cgImage */ CIImage *outputImage = [filter outputImage]; CIContext *context = [CIContext contextWithOptions:nil]; CGImageRef cgImage = [context createCGImage:outputImage fromRect:[outputImage extent]]; /* returned qr code image */ UIImage *qrImage = [UIImage imageWithCGImage:cgImage scale:1. orientation:UIImageOrientationUp]; /* resize generated image */ CGFloat width = _size; CGFloat height = _size; UIGraphicsBeginImageContext(CGSizeMake(width, height)); CGContextRef ctx = UIGraphicsGetCurrentContext(); CGContextSetInterpolationQuality(ctx, kCGInterpolationNone); [qrImage drawInRect:CGRectMake(0, 0, width, height)]; qrImage = UIGraphicsGetImageFromCurrentImageContext(); /* clean up */ UIGraphicsEndImageContext(); CGImageRelease(cgImage); /* save image to file */ NSString* fileName = [[[NSProcessInfo processInfo] globallyUniqueString] stringByAppendingString:@".jpg"]; NSString* filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:fileName]; [UIImageJPEGRepresentation(qrImage, 1.0) writeToFile:filePath atomically:YES]; /* return file path back to cordova */ [self.plugin returnImage:filePath format:@"QR_CODE" callback: self.callback]; } @end //------------------------------------------------------------------------------ // view controller for the ui //------------------------------------------------------------------------------ @implementation CDVbcsViewController @synthesize processor = _processor; @synthesize shutterPressed = _shutterPressed; @synthesize alternateXib = _alternateXib; @synthesize overlayView = _overlayView; //-------------------------------------------------------------------------- - (id)initWithProcessor:(CDVbcsProcessor*)processor alternateOverlay:(NSString *)alternateXib { self = [super init]; if (!self) return self; self.processor = processor; self.shutterPressed = NO; self.alternateXib = alternateXib; self.overlayView = nil; return self; } //-------------------------------------------------------------------------- - (void)dealloc { self.view = nil; self.processor = nil; self.shutterPressed = NO; self.alternateXib = nil; self.overlayView = nil; } //-------------------------------------------------------------------------- - (void)loadView { self.view = [[UIView alloc] initWithFrame: self.processor.parentViewController.view.frame]; } //-------------------------------------------------------------------------- - (void)viewWillAppear:(BOOL)animated { // set video orientation to what the camera sees self.processor.previewLayer.connection.videoOrientation = [self interfaceOrientationToVideoOrientation:[UIApplication sharedApplication].statusBarOrientation]; // this fixes the bug when the statusbar is landscape, and the preview layer // starts up in portrait (not filling the whole view) self.processor.previewLayer.frame = self.view.bounds; } //-------------------------------------------------------------------------- - (void)viewDidAppear:(BOOL)animated { // setup capture preview layer AVCaptureVideoPreviewLayer* previewLayer = self.processor.previewLayer; previewLayer.frame = self.view.bounds; previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; if ([previewLayer.connection isVideoOrientationSupported]) { previewLayer.connection.videoOrientation = [self interfaceOrientationToVideoOrientation:[UIApplication sharedApplication].statusBarOrientation]; } [self.view.layer insertSublayer:previewLayer below:[[self.view.layer sublayers] objectAtIndex:0]]; [self.view addSubview:[self buildOverlayView]]; [self startCapturing]; [super viewDidAppear:animated]; } - (AVCaptureVideoOrientation)interfaceOrientationToVideoOrientation:(UIInterfaceOrientation)orientation { switch (orientation) { case UIInterfaceOrientationPortrait: return AVCaptureVideoOrientationPortrait; case UIInterfaceOrientationPortraitUpsideDown: return AVCaptureVideoOrientationPortraitUpsideDown; case UIInterfaceOrientationLandscapeLeft: return AVCaptureVideoOrientationLandscapeLeft; case UIInterfaceOrientationLandscapeRight: return AVCaptureVideoOrientationLandscapeRight; default: return AVCaptureVideoOrientationPortrait; } } //-------------------------------------------------------------------------- - (void)startCapturing { self.processor.capturing = YES; } //-------------------------------------------------------------------------- - (IBAction)shutterButtonPressed { self.shutterPressed = YES; } //-------------------------------------------------------------------------- - (IBAction)cancelButtonPressed:(id)sender { [self.processor performSelector:@selector(barcodeScanCancelled) withObject:nil afterDelay:0]; } - (IBAction)flipCameraButtonPressed:(id)sender { [self.processor performSelector:@selector(flipCamera) withObject:nil afterDelay:0]; } - (IBAction)torchButtonPressed:(id)sender { [self.processor performSelector:@selector(toggleTorch) withObject:nil afterDelay:0]; } //-------------------------------------------------------------------------- - (UIView *)buildOverlayViewFromXib { [[NSBundle mainBundle] loadNibNamed:self.alternateXib owner:self options:NULL]; if ( self.overlayView == nil ) { NSLog(@"%@", @"An error occurred loading the overlay xib. It appears that the overlayView outlet is not set."); return nil; } self.overlayView.autoresizesSubviews = YES; self.overlayView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; self.overlayView.opaque = NO; CGRect bounds = self.view.bounds; bounds = CGRectMake(0, 0, bounds.size.width, bounds.size.height); [self.overlayView setFrame:bounds]; return self.overlayView; } //-------------------------------------------------------------------------- - (UIView*)buildOverlayView { if ( nil != self.alternateXib ) { return [self buildOverlayViewFromXib]; } CGRect bounds = self.view.frame; bounds = CGRectMake(0, 0, bounds.size.width, bounds.size.height); UIView* overlayView = [[UIView alloc] initWithFrame:bounds]; overlayView.autoresizesSubviews = YES; overlayView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; overlayView.opaque = NO; self.toolbar = [[UIToolbar alloc] init]; self.toolbar.autoresizingMask = UIViewAutoresizingFlexibleWidth; id cancelButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:(id)self action:@selector(cancelButtonPressed:) ]; id flexSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil ]; id flipCamera = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCamera target:(id)self action:@selector(flipCameraButtonPressed:) ]; NSMutableArray *items; #if USE_SHUTTER id shutterButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCamera target:(id)self action:@selector(shutterButtonPressed) ]; if (_processor.isShowFlipCameraButton) { items = [NSMutableArray arrayWithObjects:flexSpace, cancelButton, flexSpace, flipCamera, shutterButton, nil]; } else { items = [NSMutableArray arrayWithObjects:flexSpace, cancelButton, flexSpace, shutterButton, nil]; } #else if (_processor.isShowFlipCameraButton) { items = [@[flexSpace, cancelButton, flexSpace, flipCamera] mutableCopy]; } else { items = [@[flexSpace, cancelButton, flexSpace] mutableCopy]; } #endif if (_processor.isShowTorchButton && !_processor.isFrontCamera) { AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; if ([device hasTorch] && [device hasFlash]) { NSURL *bundleURL = [[NSBundle mainBundle] URLForResource:@"CDVBarcodeScanner" withExtension:@"bundle"]; NSBundle *bundle = [NSBundle bundleWithURL:bundleURL]; NSString *imagePath = [bundle pathForResource:@"torch" ofType:@"png"]; UIImage *image = [UIImage imageWithContentsOfFile:imagePath]; id torchButton = [[UIBarButtonItem alloc] initWithImage:image style:UIBarButtonItemStylePlain target:(id)self action:@selector(torchButtonPressed:) ]; [items insertObject:torchButton atIndex:0]; } } self.toolbar.items = items; [overlayView addSubview: self.toolbar]; UIImage* reticleImage = [self buildReticleImage]; self.reticleView = [[UIImageView alloc] initWithImage:reticleImage]; self.reticleView.opaque = NO; self.reticleView.contentMode = UIViewContentModeScaleAspectFit; self.reticleView.autoresizingMask = (UIViewAutoresizing) (0 | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin) ; [overlayView addSubview: self.reticleView]; [self resizeElements]; return overlayView; } //-------------------------------------------------------------------------- #define RETICLE_SIZE 500.0f #define RETICLE_WIDTH 10.0f #define RETICLE_OFFSET 60.0f #define RETICLE_ALPHA 0.4f //------------------------------------------------------------------------- // builds the green box and red line //------------------------------------------------------------------------- - (UIImage*)buildReticleImage { UIImage* result; UIGraphicsBeginImageContext(CGSizeMake(RETICLE_SIZE, RETICLE_SIZE)); CGContextRef context = UIGraphicsGetCurrentContext(); if (self.processor.is1D) { UIColor* color = [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:RETICLE_ALPHA]; CGContextSetStrokeColorWithColor(context, color.CGColor); CGContextSetLineWidth(context, RETICLE_WIDTH); CGContextBeginPath(context); CGFloat lineOffset = (CGFloat) (RETICLE_OFFSET+(0.5*RETICLE_WIDTH)); CGContextMoveToPoint(context, lineOffset, RETICLE_SIZE/2); CGContextAddLineToPoint(context, RETICLE_SIZE-lineOffset, (CGFloat) (0.5*RETICLE_SIZE)); CGContextStrokePath(context); } if (self.processor.is2D) { UIColor* color = [UIColor colorWithRed:0.0 green:1.0 blue:0.0 alpha:RETICLE_ALPHA]; CGContextSetStrokeColorWithColor(context, color.CGColor); CGContextSetLineWidth(context, RETICLE_WIDTH); CGContextStrokeRect(context, CGRectMake( RETICLE_OFFSET, RETICLE_OFFSET, RETICLE_SIZE-2*RETICLE_OFFSET, RETICLE_SIZE-2*RETICLE_OFFSET ) ); } result = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return result; } #pragma mark CDVBarcodeScannerOrientationDelegate - (BOOL)shouldAutorotate { return YES; } - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation { return [[UIApplication sharedApplication] statusBarOrientation]; } - (NSUInteger)supportedInterfaceOrientations { return UIInterfaceOrientationMaskAll; } - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { if ((self.orientationDelegate != nil) && [self.orientationDelegate respondsToSelector:@selector(shouldAutorotateToInterfaceOrientation:)]) { return [self.orientationDelegate shouldAutorotateToInterfaceOrientation:interfaceOrientation]; } return YES; } - (void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)orientation duration:(NSTimeInterval)duration { [UIView setAnimationsEnabled:NO]; AVCaptureVideoPreviewLayer* previewLayer = self.processor.previewLayer; previewLayer.frame = self.view.bounds; if (orientation == UIInterfaceOrientationLandscapeLeft) { [previewLayer setOrientation:AVCaptureVideoOrientationLandscapeLeft]; } else if (orientation == UIInterfaceOrientationLandscapeRight) { [previewLayer setOrientation:AVCaptureVideoOrientationLandscapeRight]; } else if (orientation == UIInterfaceOrientationPortrait) { [previewLayer setOrientation:AVCaptureVideoOrientationPortrait]; } else if (orientation == UIInterfaceOrientationPortraitUpsideDown) { [previewLayer setOrientation:AVCaptureVideoOrientationPortraitUpsideDown]; } previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; [self resizeElements]; [UIView setAnimationsEnabled:YES]; } -(void) resizeElements { CGRect bounds = self.view.bounds; if (@available(iOS 11.0, *)) { bounds = CGRectMake(bounds.origin.x, bounds.origin.y, bounds.size.width, self.view.safeAreaLayoutGuide.layoutFrame.size.height+self.view.safeAreaLayoutGuide.layoutFrame.origin.y); } [self.toolbar sizeToFit]; CGFloat toolbarHeight = [self.toolbar frame].size.height; CGFloat rootViewHeight = CGRectGetHeight(bounds); CGFloat rootViewWidth = CGRectGetWidth(bounds); CGRect rectArea = CGRectMake(0, rootViewHeight - toolbarHeight, rootViewWidth, toolbarHeight); [self.toolbar setFrame:rectArea]; CGFloat minAxis = MIN(rootViewHeight, rootViewWidth); rectArea = CGRectMake( (CGFloat) (0.5 * (rootViewWidth - minAxis)), (CGFloat) (0.5 * (rootViewHeight - minAxis)), minAxis, minAxis ); [self.reticleView setFrame:rectArea]; self.reticleView.center = CGPointMake(self.view.center.x, self.view.center.y-self.toolbar.frame.size.height/2); } @end