blob: 562a49b539e5b4af631d97b917f0d7a1d264799d [file] [log] [blame]
/**
* Copyright 2009 Jeff Verkoeyen
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "ZXingWidgetController.h"
#import "Decoder.h"
#import "NSString+HTML.h"
#import "ResultParser.h"
#import "ParsedResult.h"
#import "ResultAction.h"
#import "TwoDDecoderResult.h"
#include <sys/types.h>
#include <sys/sysctl.h>
#import <AVFoundation/AVFoundation.h>
#define CAMERA_SCALAR 1.12412 // scalar = (480 / (2048 / 480))
#define FIRST_TAKE_DELAY 1.0
#define ONE_D_BAND_HEIGHT 10.0
@interface ZXingWidgetController ()
@property BOOL showCancel;
@property BOOL oneDMode;
@property BOOL isStatusBarHidden;
- (void)initCapture;
- (void)stopCapture;
@end
@implementation ZXingWidgetController
#if HAS_AVFF
@synthesize captureSession;
@synthesize prevLayer;
#endif
@synthesize result, delegate, soundToPlay;
@synthesize overlayView;
@synthesize oneDMode, showCancel, isStatusBarHidden;
@synthesize readers;
- (id)initWithDelegate:(id<ZXingDelegate>)scanDelegate showCancel:(BOOL)shouldShowCancel OneDMode:(BOOL)shouldUseoOneDMode {
self = [super init];
if (self) {
[self setDelegate:scanDelegate];
self.oneDMode = shouldUseoOneDMode;
self.showCancel = shouldShowCancel;
self.wantsFullScreenLayout = YES;
beepSound = -1;
decoding = NO;
OverlayView *theOverLayView = [[OverlayView alloc] initWithFrame:[UIScreen mainScreen].bounds
cancelEnabled:showCancel
oneDMode:oneDMode];
[theOverLayView setDelegate:self];
self.overlayView = theOverLayView;
[theOverLayView release];
}
return self;
}
- (void)dealloc {
if (beepSound != (SystemSoundID)-1) {
AudioServicesDisposeSystemSoundID(beepSound);
}
[self stopCapture];
[soundToPlay release];
[overlayView release];
[readers release];
[super dealloc];
}
- (void)cancelled {
[self stopCapture];
if (!self.isStatusBarHidden) {
[[UIApplication sharedApplication] setStatusBarHidden:NO];
}
wasCancelled = YES;
if (delegate != nil) {
[delegate zxingControllerDidCancel:self];
}
}
- (NSString *)getPlatform {
size_t size;
sysctlbyname("hw.machine", NULL, &size, NULL, 0);
char *machine = malloc(size);
sysctlbyname("hw.machine", machine, &size, NULL, 0);
NSString *platform = [NSString stringWithCString:machine encoding:NSASCIIStringEncoding];
free(machine);
return platform;
}
- (BOOL)fixedFocus {
NSString *platform = [self getPlatform];
if ([platform isEqualToString:@"iPhone1,1"] ||
[platform isEqualToString:@"iPhone1,2"]) return YES;
return NO;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.wantsFullScreenLayout = YES;
if ([self soundToPlay] != nil) {
OSStatus error = AudioServicesCreateSystemSoundID((CFURLRef)[self soundToPlay], &beepSound);
if (error != kAudioServicesNoError) {
NSLog(@"Problem loading nearSound.caf");
}
}
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.isStatusBarHidden = [[UIApplication sharedApplication] isStatusBarHidden];
if (!isStatusBarHidden)
[[UIApplication sharedApplication] setStatusBarHidden:YES];
decoding = YES;
[self initCapture];
[self.view addSubview:overlayView];
[overlayView setPoints:nil];
wasCancelled = NO;
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
if (!isStatusBarHidden)
[[UIApplication sharedApplication] setStatusBarHidden:NO];
[self.overlayView removeFromSuperview];
[self stopCapture];
}
- (CGImageRef)CGImageRotated90:(CGImageRef)imgRef
{
CGFloat angleInRadians = -90 * (M_PI / 180);
CGFloat width = CGImageGetWidth(imgRef);
CGFloat height = CGImageGetHeight(imgRef);
CGRect imgRect = CGRectMake(0, 0, width, height);
CGAffineTransform transform = CGAffineTransformMakeRotation(angleInRadians);
CGRect rotatedRect = CGRectApplyAffineTransform(imgRect, transform);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef bmContext = CGBitmapContextCreate(NULL,
rotatedRect.size.width,
rotatedRect.size.height,
8,
0,
colorSpace,
kCGImageAlphaPremultipliedFirst);
CGContextSetAllowsAntialiasing(bmContext, FALSE);
CGContextSetInterpolationQuality(bmContext, kCGInterpolationNone);
CGColorSpaceRelease(colorSpace);
// CGContextTranslateCTM(bmContext,
// +(rotatedRect.size.width/2),
// +(rotatedRect.size.height/2));
CGContextScaleCTM(bmContext, rotatedRect.size.width/rotatedRect.size.height, 1.0);
CGContextTranslateCTM(bmContext, 0.0, rotatedRect.size.height);
CGContextRotateCTM(bmContext, angleInRadians);
// CGContextTranslateCTM(bmContext,
// -(rotatedRect.size.width/2),
// -(rotatedRect.size.height/2));
CGContextDrawImage(bmContext, CGRectMake(0, 0,
rotatedRect.size.width,
rotatedRect.size.height),
imgRef);
CGImageRef rotatedImage = CGBitmapContextCreateImage(bmContext);
CFRelease(bmContext);
[(id)rotatedImage autorelease];
return rotatedImage;
}
- (CGImageRef)CGImageRotated180:(CGImageRef)imgRef
{
CGFloat angleInRadians = M_PI;
CGFloat width = CGImageGetWidth(imgRef);
CGFloat height = CGImageGetHeight(imgRef);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef bmContext = CGBitmapContextCreate(NULL,
width,
height,
8,
0,
colorSpace,
kCGImageAlphaPremultipliedFirst);
CGContextSetAllowsAntialiasing(bmContext, FALSE);
CGContextSetInterpolationQuality(bmContext, kCGInterpolationNone);
CGColorSpaceRelease(colorSpace);
CGContextTranslateCTM(bmContext,
+(width/2),
+(height/2));
CGContextRotateCTM(bmContext, angleInRadians);
CGContextTranslateCTM(bmContext,
-(width/2),
-(height/2));
CGContextDrawImage(bmContext, CGRectMake(0, 0, width, height), imgRef);
CGImageRef rotatedImage = CGBitmapContextCreateImage(bmContext);
CFRelease(bmContext);
[(id)rotatedImage autorelease];
return rotatedImage;
}
// DecoderDelegate methods
- (void)decoder:(Decoder *)decoder willDecodeImage:(UIImage *)image usingSubset:(UIImage *)subset{
#ifdef DEBUG
NSLog(@"DecoderViewController MessageWhileDecodingWithDimensions: Decoding image (%.0fx%.0f) ...", image.size.width, image.size.height);
#endif
}
- (void)decoder:(Decoder *)decoder
decodingImage:(UIImage *)image
usingSubset:(UIImage *)subset {
}
- (void)presentResultForString:(NSString *)resultString {
self.result = [ResultParser parsedResultForString:resultString];
if (beepSound != (SystemSoundID)-1) {
AudioServicesPlaySystemSound(beepSound);
}
#ifdef DEBUG
NSLog(@"result string = %@", resultString);
#endif
}
- (void)presentResultPoints:(NSArray *)resultPoints
forImage:(UIImage *)image
usingSubset:(UIImage *)subset {
// simply add the points to the image view
NSMutableArray *mutableArray = [[NSMutableArray alloc] initWithArray:resultPoints];
[overlayView setPoints:mutableArray];
[mutableArray release];
}
- (void)decoder:(Decoder *)decoder didDecodeImage:(UIImage *)image usingSubset:(UIImage *)subset withResult:(TwoDDecoderResult *)twoDResult {
[self presentResultForString:[twoDResult text]];
[self presentResultPoints:[twoDResult points] forImage:image usingSubset:subset];
// now, in a selector, call the delegate to give this overlay time to show the points
[self performSelector:@selector(alertDelegate:) withObject:[[twoDResult text] copy] afterDelay:0.0];
decoder.delegate = nil;
}
- (void)alertDelegate:(id)text {
if (!isStatusBarHidden)
[[UIApplication sharedApplication] setStatusBarHidden:NO];
if (delegate != nil) {
[delegate zxingController:self didScanResult:text];
}
[text release];
}
- (void)decoder:(Decoder *)decoder failedToDecodeImage:(UIImage *)image usingSubset:(UIImage *)subset reason:(NSString *)reason {
decoder.delegate = nil;
[overlayView setPoints:nil];
}
- (void)decoder:(Decoder *)decoder foundPossibleResultPoint:(CGPoint)point {
[overlayView setPoint:point];
}
/*
- (void)stopPreview:(NSNotification*)notification {
// NSLog(@"stop preview");
}
- (void)notification:(NSNotification*)notification {
// NSLog(@"notification %@", notification.name);
}
*/
#pragma mark -
#pragma mark AVFoundation
- (void)initCapture {
#if HAS_AVFF
AVCaptureDeviceInput *captureInput =
[AVCaptureDeviceInput deviceInputWithDevice:
[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]
error:nil];
AVCaptureVideoDataOutput *captureOutput = [[AVCaptureVideoDataOutput alloc] init];
captureOutput.alwaysDiscardsLateVideoFrames = YES;
[captureOutput setSampleBufferDelegate:self queue:dispatch_get_main_queue()];
NSString* key = (NSString*)kCVPixelBufferPixelFormatTypeKey;
NSNumber* value = [NSNumber numberWithUnsignedInt:kCVPixelFormatType_32BGRA];
NSDictionary* videoSettings = [NSDictionary dictionaryWithObject:value forKey:key];
[captureOutput setVideoSettings:videoSettings];
self.captureSession = [[AVCaptureSession alloc] init];
[self.captureSession release];
self.captureSession.sessionPreset = AVCaptureSessionPresetMedium; // 480x360 on a 4
[self.captureSession addInput:captureInput];
[self.captureSession addOutput:captureOutput];
[captureOutput release];
/*
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(stopPreview:)
name:AVCaptureSessionDidStopRunningNotification
object:self.captureSession];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(notification:)
name:AVCaptureSessionDidStopRunningNotification
object:self.captureSession];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(notification:)
name:AVCaptureSessionRuntimeErrorNotification
object:self.captureSession];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(notification:)
name:AVCaptureSessionDidStartRunningNotification
object:self.captureSession];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(notification:)
name:AVCaptureSessionWasInterruptedNotification
object:self.captureSession];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(notification:)
name:AVCaptureSessionInterruptionEndedNotification
object:self.captureSession];
*/
if (!self.prevLayer) {
self.prevLayer = [AVCaptureVideoPreviewLayer layerWithSession:self.captureSession];
}
// NSLog(@"prev %p %@", self.prevLayer, self.prevLayer);
self.prevLayer.frame = self.view.bounds;
self.prevLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
[self.view.layer addSublayer: self.prevLayer];
[self.captureSession startRunning];
#endif
}
#if HAS_AVFF
- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *)connection
{
if (!decoding) {
return;
}
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
/*Lock the image buffer*/
CVPixelBufferLockBaseAddress(imageBuffer,0);
/*Get information about the image*/
size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
size_t width = CVPixelBufferGetWidth(imageBuffer);
size_t height = CVPixelBufferGetHeight(imageBuffer);
uint8_t* baseAddress = CVPixelBufferGetBaseAddress(imageBuffer);
void* free_me = 0;
if (true) { // iOS bug?
uint8_t* tmp = baseAddress;
int bytes = bytesPerRow*height;
free_me = baseAddress = (uint8_t*)malloc(bytes);
baseAddress[0] = 0xdb;
memcpy(baseAddress,tmp,bytes);
}
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef newContext =
CGBitmapContextCreate(baseAddress, width, height, 8, bytesPerRow, colorSpace,
kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst);
CGImageRef capture = CGBitmapContextCreateImage(newContext);
CVPixelBufferUnlockBaseAddress(imageBuffer,0);
free(free_me);
CGContextRelease(newContext);
CGColorSpaceRelease(colorSpace);
CGRect cropRect = [overlayView cropRect];
if (oneDMode) {
// let's just give the decoder a vertical band right above the red line
cropRect.origin.x = cropRect.origin.x + (cropRect.size.width / 2) - (ONE_D_BAND_HEIGHT + 1);
cropRect.size.width = ONE_D_BAND_HEIGHT;
// do a rotate
CGImageRef croppedImg = CGImageCreateWithImageInRect(capture, cropRect);
capture = [self CGImageRotated90:croppedImg];
capture = [self CGImageRotated180:capture];
// UIImageWriteToSavedPhotosAlbum([UIImage imageWithCGImage:capture], nil, nil, nil);
CGImageRelease(croppedImg);
cropRect.origin.x = 0.0;
cropRect.origin.y = 0.0;
cropRect.size.width = CGImageGetWidth(capture);
cropRect.size.height = CGImageGetHeight(capture);
}
// Won't work if the overlay becomes uncentered ...
// iOS always takes videos in landscape
// images are always 4x3; device is not
// iOS uses virtual pixels for non-image stuff
{
float height = CGImageGetHeight(capture);
float width = CGImageGetWidth(capture);
CGRect screen = UIScreen.mainScreen.bounds;
float tmp = screen.size.width;
screen.size.width = screen.size.height;;
screen.size.height = tmp;
cropRect.origin.x = (width-cropRect.size.width)/2;
cropRect.origin.y = (height-cropRect.size.height)/2;
}
CGImageRef newImage = CGImageCreateWithImageInRect(capture, cropRect);
CGImageRelease(capture);
UIImage *scrn = [[UIImage alloc] initWithCGImage:newImage];
CGImageRelease(newImage);
Decoder *d = [[Decoder alloc] init];
d.readers = readers;
d.delegate = self;
cropRect.origin.x = 0.0;
cropRect.origin.y = 0.0;
decoding = [d decodeImage:scrn cropRect:cropRect] == YES ? NO : YES;
[d release];
[scrn release];
}
#endif
- (void)stopCapture {
decoding = NO;
#if HAS_AVFF
[captureSession stopRunning];
AVCaptureInput* input = [captureSession.inputs objectAtIndex:0];
[captureSession removeInput:input];
AVCaptureVideoDataOutput* output = (AVCaptureVideoDataOutput*)[captureSession.outputs objectAtIndex:0];
[captureSession removeOutput:output];
[self.prevLayer removeFromSuperlayer];
/*
// heebee jeebees here ... is iOS still writing into the layer?
if (self.prevLayer) {
layer.session = nil;
AVCaptureVideoPreviewLayer* layer = prevLayer;
[self.prevLayer retain];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 12000000000), dispatch_get_main_queue(), ^{
[layer release];
});
}
*/
self.prevLayer = nil;
self.captureSession = nil;
#endif
}
#pragma mark - Torch
- (void)setTorch:(BOOL)status {
Class captureDeviceClass = NSClassFromString(@"AVCaptureDevice");
if (captureDeviceClass != nil) {
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
[device lockForConfiguration:nil];
if ( [device hasTorch] ) {
if ( status ) {
[device setTorchMode:AVCaptureTorchModeOn];
} else {
[device setTorchMode:AVCaptureTorchModeOn];
}
}
[device unlockForConfiguration];
}
}
- (BOOL)torchIsOn {
Class captureDeviceClass = NSClassFromString(@"AVCaptureDevice");
if (captureDeviceClass != nil) {
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
if ( [device hasTorch] ) {
return [device torchMode] == AVCaptureTorchModeOn;
}
[device unlockForConfiguration];
}
return NO;
}
@end