BracketStripes/BracketStripesCameraViewController.m
/* |
Copyright (C) 2016 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
Abstract: |
Camera view controller |
*/ |
@import AVFoundation; |
@import CoreMedia; |
#import "BracketStripesCameraViewController.h" |
#import "StripedImage.h" |
#import "BracketStripesCapturePreviewView.h" |
#import "BracketStripesImageViewController.h" |
// Completion handler prototypes |
typedef void(^AAPLCompletion)(BOOL success); |
typedef void(^AAPLCompletionWithError)(BOOL success, NSError *error); |
typedef void(^AAPLCompletionWithImage)(UIImage *image); |
@interface BracketStripesCameraViewController () <BracketStripesImageViewDelegate> |
// Convenience for enable/disable UI controls |
@property (nonatomic, assign) BOOL userInterfaceEnabled; |
@end |
@implementation BracketStripesCameraViewController { |
// Capture |
AVCaptureSession *_captureSession; |
AVCaptureDevice *_captureDevice; |
AVCaptureDeviceFormat *_captureDeviceFormat; |
AVCaptureStillImageOutput *_stillImageOutput; |
// Brackets |
NSUInteger _maxBracketCount; |
NSArray *_bracketSettings; |
// UI |
IBOutlet BracketStripesCapturePreviewView *_cameraPreviewView; |
IBOutlet UIButton *_cameraShutterButton; |
IBOutlet UISegmentedControl *_bracketModeControl; |
// Striped rendered brackets |
StripedImage *_imageStripes; |
} |
- (void)setUserInterfaceEnabled:(BOOL)userInterfaceEnabled |
{ |
_cameraShutterButton.enabled = |
_bracketModeControl.enabled = |
userInterfaceEnabled; |
} |
- (BOOL)userInterfaceEnabled |
{ |
return _cameraShutterButton.enabled; |
} |
- (AVCaptureDevice *)_cameraDeviceForPosition:(AVCaptureDevicePosition)position |
{ |
for (AVCaptureDevice *device in [AVCaptureDevice devices]) { |
if (device.position == position) { |
return device; |
} |
} |
return nil; |
} |
- (void)_showErrorMessage:(NSString *)message title:(NSString *)title |
{ |
UIAlertView *alert = [[UIAlertView alloc] init]; |
alert.title = title; |
alert.message = message; |
[alert addButtonWithTitle:NSLocalizedString(@"title-ok", @"OK Button Title")]; |
[alert show]; |
} |
- (void)_startCameraWithCompletionHandler:(AAPLCompletionWithError)completion |
{ |
// Capture session |
_captureSession = [[AVCaptureSession alloc] init]; |
[_captureSession beginConfiguration]; |
// Obtain back facing camera |
_captureDevice = [self _cameraDeviceForPosition:AVCaptureDevicePositionBack]; |
if (!_captureDevice) { |
NSString *message = NSLocalizedString(@"message-back-camera-not-found", @"Error message back camera - not found"); |
NSString *title = NSLocalizedString(@"title-back-camera-not-found", @"Error title back camera - not found"); |
[self _showErrorMessage:message title:title]; |
return; |
} |
NSError *error = nil; |
AVCaptureDeviceInput *deviceInput = [AVCaptureDeviceInput deviceInputWithDevice:_captureDevice error:&error]; |
if (!deviceInput) { |
NSLog(@"This error should be handled appropriately in your app -- obtain device input: %@", error); |
NSString *message = NSLocalizedString(@"message-back-camera-open-failed", @"Error message back camera - can't open."); |
NSString *title = NSLocalizedString(@"title-back-camera-open-failed", @"Error title for back camera - can't open."); |
[self _showErrorMessage:message title:title]; |
return; |
} |
[_captureSession addInput:deviceInput]; |
// Still image output |
_stillImageOutput = [[AVCaptureStillImageOutput alloc] init]; |
[_stillImageOutput setOutputSettings:@{ |
// JPEG output |
AVVideoCodecKey: AVVideoCodecJPEG |
/* |
* Or instead of JPEG, we can use one of the following pixel formats: |
* |
// BGRA |
(id)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32BGRA) |
* |
// 420f output |
(id)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) |
* |
*/ |
}]; |
[_captureSession addOutput:_stillImageOutput]; |
// Capture preview |
[_cameraPreviewView configureCaptureSession:_captureSession captureOutput:_stillImageOutput]; |
// Configure for high resolution still image photography |
[_captureSession setSessionPreset:AVCaptureSessionPresetPhoto]; |
// Track the device's active format (we don't change this later) |
_captureDeviceFormat = _captureDevice.activeFormat; |
[_captureSession commitConfiguration]; |
// Start the AV session |
[_captureSession startRunning]; |
// We make sure not to exceed the maximum number of supported brackets |
_maxBracketCount = [_stillImageOutput maxBracketedCaptureStillImageCount]; |
// Construct capture bracket settings and warmup |
[self _prepareBracketsWithCompletionHandler:completion]; |
} |
- (void)_prepareBracketsWithCompletionHandler:(AAPLCompletionWithError)completion |
{ |
// Construct the list of brackets |
switch (_bracketModeControl.selectedSegmentIndex) { |
case 0: |
NSLog(@"Configuring auto-exposure brackets..."); |
_bracketSettings = [self _exposureBrackets]; |
break; |
case 1: |
NSLog(@"Configuring duration/ISO brackets..."); |
_bracketSettings = [self _durationISOBrackets]; |
break; |
} |
// Prime striped image buffer |
const CMVideoDimensions dimensions = CMVideoFormatDescriptionGetDimensions([[_captureDevice activeFormat] formatDescription]); |
_imageStripes = [[StripedImage alloc] initForSize:CGSizeMake(dimensions.width, dimensions.height) stripWidth:dimensions.width/12.0 stride:[_bracketSettings count]]; |
// Warm up bracketed capture |
NSLog(@"Warming brackets: %@", _bracketSettings); |
AVCaptureConnection *connection = [_stillImageOutput connectionWithMediaType:AVMediaTypeVideo]; |
[_stillImageOutput prepareToCaptureStillImageBracketFromConnection:connection |
withSettingsArray:_bracketSettings |
completionHandler:^(BOOL prepared, NSError *error) { |
completion(prepared, error); |
}]; |
} |
- (NSArray *)_exposureBrackets |
{ |
NSMutableArray *brackets = [[NSMutableArray alloc] initWithCapacity:_maxBracketCount]; |
// Fixed bracket settings |
const int fixedBracketCount = 3; |
const float biasValues[] = { |
-2.0, 0.0, +2.0, |
}; |
for (int index = 0; index < MIN(fixedBracketCount, _maxBracketCount); index++) { |
const float biasValue = biasValues[index]; |
AVCaptureAutoExposureBracketedStillImageSettings *settings = [AVCaptureAutoExposureBracketedStillImageSettings autoExposureSettingsWithExposureTargetBias:biasValue]; |
[brackets addObject:settings]; |
} |
return brackets; |
} |
- (NSArray *)_durationISOBrackets |
{ |
NSMutableArray *brackets = [[NSMutableArray alloc] initWithCapacity:_maxBracketCount]; |
// ISO and Duration are hardware dependent |
NSLog(@"Camera device ISO range: [%.2f, %.2f]", _captureDeviceFormat.minISO, _captureDeviceFormat.maxISO); |
NSLog(@"Camera device Duration range: [%.4f, %.4f]", CMTimeGetSeconds(_captureDeviceFormat.minExposureDuration), CMTimeGetSeconds(_captureDeviceFormat.maxExposureDuration)); |
// Fixed bracket settings |
const int fixedBracketCount = 3; |
const float ISOValues[] = { |
50, 60, 500, |
}; |
const Float64 durationSecondsValues[] = { |
0.250, 0.050, 0.005, |
}; |
for (int index = 0; index < MIN(fixedBracketCount, _maxBracketCount); index++) { |
// Clamp fixed settings to the device limits |
const float ISO = CLAMP( |
ISOValues[index], |
_captureDeviceFormat.minISO, |
_captureDeviceFormat.maxISO |
); |
const Float64 durationSeconds = CLAMP( |
durationSecondsValues[index], |
CMTimeGetSeconds(_captureDeviceFormat.minExposureDuration), |
CMTimeGetSeconds(_captureDeviceFormat.maxExposureDuration) |
); |
const CMTime duration = CMTimeMakeWithSeconds(durationSeconds, 1e3); |
// Create bracket settings |
AVCaptureManualExposureBracketedStillImageSettings *settings = [AVCaptureManualExposureBracketedStillImageSettings manualExposureSettingsWithExposureDuration:duration ISO:ISO]; |
[brackets addObject:settings]; |
} |
return brackets; |
} |
- (void)_performBrackedCaptureWithCompletionHandler:(AAPLCompletionWithImage)completion |
{ |
// Number of brackets to capture |
__block int todo = (int)[_bracketSettings count]; |
// Number of failed bracket captures |
__block int failed = 0; |
NSLog(@"Performing bracketed capture: %@", _bracketSettings); |
AVCaptureConnection *connection = [_stillImageOutput connectionWithMediaType:AVMediaTypeVideo]; |
[_stillImageOutput captureStillImageBracketAsynchronouslyFromConnection:connection |
withSettingsArray:_bracketSettings |
completionHandler:^( |
CMSampleBufferRef sampleBuffer, |
AVCaptureBracketedStillImageSettings *stillImageSettings, |
NSError *error |
) { |
--todo; |
if (!error) { |
NSLog(@"Bracket %@", stillImageSettings); |
// Process this sample buffer while we wait for the next bracketed image to be captured. |
// You would insert your own HDR algorithm here. |
[_imageStripes addSampleBuffer:sampleBuffer]; |
} |
else { |
NSLog(@"This error should be handled appropriately in your app -- Bracket %@ ERROR: %@", stillImageSettings, error); |
++failed; |
} |
// Return the rendered image strip when the capture completes |
if (!todo) { |
NSLog(@"All %d bracket(s) have been captured %@ error.", (int)[_bracketSettings count], (failed) ? @"with" : @"without"); |
// This demo is restricted to portrait orientation for simplicity, where we hard-code the rendered striped image orientation. |
UIImage *image = |
(!failed) |
? [_imageStripes imageWithOrientation:UIImageOrientationRight] |
: nil; |
// Don't assume we're on the main thread |
dispatch_async(dispatch_get_main_queue(), ^{ |
completion(image); |
}); |
} |
}]; |
} |
- (IBAction)_bracketModeDidChange:(id)sender |
{ |
self.userInterfaceEnabled = NO; |
// Prepare for the new bracket settings |
[self _prepareBracketsWithCompletionHandler:^(BOOL success, NSError *error) { |
self.userInterfaceEnabled = YES; |
}]; |
} |
- (IBAction)_cameraShutterDidPress:(id)sender |
{ |
if (![_captureSession isRunning]) { |
return; |
} |
self.userInterfaceEnabled = NO; |
[self _performBrackedCaptureWithCompletionHandler:^(UIImage *image) { |
BracketStripesImageViewController *controller = [[BracketStripesImageViewController alloc] initWithImage:image]; |
controller.delegate = self; |
controller.title = NSLocalizedString(@"title-bracket-stripes", @"Bracket Viewer Title"); |
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:controller]; |
navController.modalTransitionStyle = UIModalTransitionStyleCoverVertical; |
[self presentViewController:navController animated:YES completion:nil]; |
}]; |
} |
- (void)viewDidLoad |
{ |
[super viewDidLoad]; |
self.userInterfaceEnabled = NO; |
} |
- (void)viewDidAppear:(BOOL)animated { |
[super viewDidAppear:animated]; |
[self _startCameraWithCompletionHandler:^(BOOL success, NSError *error) { |
if (success) { |
self.userInterfaceEnabled = YES; |
} |
else { |
NSLog(@"This error should be handled appropriately in your app -- start camera completion: %@", error); |
} |
}]; |
} |
#pragma mark - BracketStripesImageViewDelegate |
- (void)imageViewControllerDidFinish:(BracketStripesImageViewController *)controller |
{ |
[controller dismissViewControllerAnimated:YES completion:^{ |
self.userInterfaceEnabled = YES; |
}]; |
} |
@end |
Copyright © 2016 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2016-09-28