[iphone] all memory leaks fixed. A number of minor bugs corrected
[zxing.git] / iphone / ZXingWidget / Classes / ZXingWidgetController.m
1 /**
2  * Copyright 2009 Jeff Verkoeyen
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *    http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #import "ZXingWidgetController.h"
18 #import "Decoder.h"
19 #import "NSString+HTML.h"
20 #import "ResultParser.h"
21 #import "ParsedResult.h"
22 #import "ResultAction.h"
23 #include <sys/types.h>
24 #include <sys/sysctl.h>
25
26 #define CAMERA_SCALAR 1.12412 // scalar = (480 / (2048 / 480))
27 #define FIRST_TAKE_DELAY 1.0
28 #define ONE_D_BAND_HEIGHT 10.0
29
30 CGImageRef UIGetScreenImage(void);
31
32 @interface ZXingWidgetController ()
33
34 @property BOOL showCancel;
35 @property BOOL oneDMode;
36
37 @end
38
39
40
41
42
43 @implementation ZXingWidgetController
44 @synthesize result, actions, delegate, soundToPlay;
45 @synthesize overlayView;
46 @synthesize oneDMode, showCancel;
47
48 - (id)initWithDelegate:(id<ZXingDelegate>)scanDelegate showCancel:(BOOL)shouldShowCancel OneDMode:(BOOL)shouldUseoOneDMode {
49   if (self = [super init]) {
50     [self setDelegate:scanDelegate];
51     self.oneDMode = shouldUseoOneDMode;
52     self.showCancel = shouldShowCancel;
53     beepSound = -1;
54     self.wantsFullScreenLayout = YES;
55     if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])
56       self.sourceType = UIImagePickerControllerSourceTypeCamera;
57     float zoomFactor = CAMERA_SCALAR;
58     if ([self fixedFocus]) {
59       zoomFactor *= 2.0;
60     }
61     if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])
62       self.cameraViewTransform = CGAffineTransformScale(
63                                                       self.cameraViewTransform, zoomFactor, zoomFactor);
64     overlayView = [OverlayView alloc];
65     [overlayView setOneDMode:oneDMode];
66     overlayView = [overlayView initWithCancelEnabled:showCancel frame:[UIScreen mainScreen].bounds];
67     [overlayView setDelegate:self];
68     if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])
69     {
70       self.showsCameraControls = NO;
71       self.cameraOverlayView = overlayView;
72       self.allowsEditing = NO; // [[NSUserDefaults standardUserDefaults] boolForKey:@"allowEditing"];
73     }
74   }
75   
76   return self;
77 }
78
79 - (void)dealloc {
80   if (beepSound != -1) {
81     AudioServicesDisposeSystemSoundID(beepSound);
82   }
83   self.cameraOverlayView = nil;
84   [overlayView release];
85   [super dealloc];
86 }
87
88 - (void)cancelled {
89   NSLog(@"cancelled called in ZXingWidgetController");
90   wasCancelled = true;
91   if (delegate != nil) {
92     [delegate cancelled];
93   }
94 }
95
96 - (NSString *)getPlatform {
97   size_t size;
98   sysctlbyname("hw.machine", NULL, &size, NULL, 0);
99   char *machine = malloc(size);
100   sysctlbyname("hw.machine", machine, &size, NULL, 0);
101   NSString *platform = [NSString stringWithCString:machine encoding:NSASCIIStringEncoding];
102   free(machine);
103   return platform;
104 }
105
106 - (BOOL)fixedFocus {
107   NSString *platform = [self getPlatform];
108   if ([platform isEqualToString:@"iPhone1,1"] ||
109       [platform isEqualToString:@"iPhone1,2"]) return true;
110   return false;
111 }
112
113 - (void)viewWillAppear:(BOOL)animated {
114   [super viewWillAppear:animated];
115   if ([self soundToPlay] != nil) {
116     OSStatus error = AudioServicesCreateSystemSoundID((CFURLRef)[self soundToPlay], &beepSound);
117     if (error != kAudioServicesNoError) {
118       NSLog(@"Problem loading nearSound.caf");
119     }
120   }
121 }
122
123 - (void)viewDidAppear:(BOOL)animated {
124   [super viewDidAppear:animated];
125   [overlayView setPoints:nil];
126   wasCancelled = false;
127   [NSTimer scheduledTimerWithTimeInterval: FIRST_TAKE_DELAY
128                                    target: self
129                                  selector: @selector(takePicture:)
130                                  userInfo: nil
131                                   repeats: NO];
132 }
133
134 - (CGImageRef)CGImageRotated90:(CGImageRef)imgRef
135 {
136   CGFloat angleInRadians = -90 * (M_PI / 180);
137   CGFloat width = CGImageGetWidth(imgRef);
138   CGFloat height = CGImageGetHeight(imgRef);
139   
140   CGRect imgRect = CGRectMake(0, 0, width, height);
141   CGAffineTransform transform = CGAffineTransformMakeRotation(angleInRadians);
142   CGRect rotatedRect = CGRectApplyAffineTransform(imgRect, transform);
143   
144   CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
145   CGContextRef bmContext = CGBitmapContextCreate(NULL,
146                                                  rotatedRect.size.width,
147                                                  rotatedRect.size.height,
148                                                  8,
149                                                  0,
150                                                  colorSpace,
151                                                  kCGImageAlphaPremultipliedFirst);
152   CGContextSetAllowsAntialiasing(bmContext, FALSE);
153   CGContextSetInterpolationQuality(bmContext, kCGInterpolationNone);
154   CGColorSpaceRelease(colorSpace);
155   //      CGContextTranslateCTM(bmContext,
156   //                                                +(rotatedRect.size.width/2),
157   //                                                +(rotatedRect.size.height/2));
158   CGContextScaleCTM(bmContext, rotatedRect.size.width/rotatedRect.size.height, 1.0);
159   CGContextTranslateCTM(bmContext, 0.0, rotatedRect.size.height);
160   CGContextRotateCTM(bmContext, angleInRadians);
161   //      CGContextTranslateCTM(bmContext,
162   //                                                -(rotatedRect.size.width/2),
163   //                                                -(rotatedRect.size.height/2));
164   CGContextDrawImage(bmContext, CGRectMake(0, 0,
165                                            rotatedRect.size.width,
166                                            rotatedRect.size.height),
167                      imgRef);
168   
169   CGImageRef rotatedImage = CGBitmapContextCreateImage(bmContext);
170   CFRelease(bmContext);
171   [(id)rotatedImage autorelease];
172   
173   return rotatedImage;
174 }
175
176 - (CGImageRef)CGImageRotated180:(CGImageRef)imgRef
177 {
178   CGFloat angleInRadians = M_PI;
179   CGFloat width = CGImageGetWidth(imgRef);
180   CGFloat height = CGImageGetHeight(imgRef);
181   
182   CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
183   CGContextRef bmContext = CGBitmapContextCreate(NULL,
184                                                  width,
185                                                  height,
186                                                  8,
187                                                  0,
188                                                  colorSpace,
189                                                  kCGImageAlphaPremultipliedFirst);
190   CGContextSetAllowsAntialiasing(bmContext, FALSE);
191   CGContextSetInterpolationQuality(bmContext, kCGInterpolationNone);
192   CGColorSpaceRelease(colorSpace);
193   CGContextTranslateCTM(bmContext,
194                         +(width/2),
195                         +(height/2));
196   CGContextRotateCTM(bmContext, angleInRadians);
197   CGContextTranslateCTM(bmContext,
198                         -(width/2),
199                         -(height/2));
200   CGContextDrawImage(bmContext, CGRectMake(0, 0, width, height), imgRef);
201   
202   CGImageRef rotatedImage = CGBitmapContextCreateImage(bmContext);
203   CFRelease(bmContext);
204   [(id)rotatedImage autorelease];
205   
206   return rotatedImage;
207 }
208
209 - (void)takePicture:(NSTimer*)theTimer {
210   CGImageRef capture = UIGetScreenImage();
211   CGRect cropRect = [overlayView cropRect];
212   if (oneDMode) {
213     // let's just give the decoder a vertical band right above the red line
214     cropRect.origin.x = cropRect.origin.x + (cropRect.size.width / 2) - (ONE_D_BAND_HEIGHT + 1);
215     cropRect.size.width = ONE_D_BAND_HEIGHT;
216     // do a rotate
217     CGImageRef croppedImg = CGImageCreateWithImageInRect(capture, cropRect);
218     capture = [self CGImageRotated90:croppedImg];
219     capture = [self CGImageRotated180:capture];
220     //              UIImageWriteToSavedPhotosAlbum([UIImage imageWithCGImage:capture], nil, nil, nil);
221     CGImageRelease(croppedImg);
222     cropRect.origin.x = 0.0;
223     cropRect.origin.y = 0.0;
224     cropRect.size.width = CGImageGetWidth(capture);
225     cropRect.size.height = CGImageGetHeight(capture);
226   }
227   CGImageRef newImage = CGImageCreateWithImageInRect(capture, cropRect);
228   CGImageRelease(capture);
229   //UIImage *scrn = [UIImage imageWithCGImage:newImage];
230   UIImage *scrn = [[UIImage alloc] initWithCGImage:newImage];
231   CGImageRelease(newImage);
232   Decoder *d = [[Decoder alloc] init];
233   d.delegate = self;
234   cropRect.origin.x = 0.0;
235   cropRect.origin.y = 0.0;
236   [d decodeImage:scrn cropRect:cropRect];
237   [scrn release];
238 }
239
240 // DecoderDelegate methods
241
242 - (void)decoder:(Decoder *)decoder willDecodeImage:(UIImage *)image usingSubset:(UIImage *)subset{
243 #ifdef DEBUG
244   NSLog(@"DecoderViewController MessageWhileDecodingWithDimensions: Decoding image (%.0fx%.0f) ...", image.size.width, image.size.height);
245 #endif
246 }
247
248 - (void)decoder:(Decoder *)decoder
249   decodingImage:(UIImage *)image
250     usingSubset:(UIImage *)subset
251        progress:(NSString *)message {
252 }
253
254 - (void)presentResultForString:(NSString *)resultString {
255   self.result = [ResultParser parsedResultForString:resultString];
256   
257   if (beepSound != -1) {
258     AudioServicesPlaySystemSound(beepSound);
259   }
260 #ifdef DEBUG
261   NSLog(@"result string = %@", resultString);
262   NSLog(@"result has %d actions", actions ? 0 : actions.count);
263 #endif
264   //      [self updateToolbar];
265 }
266
267 - (void)presentResultPoints:(NSArray *)resultPoints
268                    forImage:(UIImage *)image
269                 usingSubset:(UIImage *)subset {
270   // simply add the points to the image view
271   [overlayView setPoints:resultPoints];
272 }
273
274 - (void)decoder:(Decoder *)decoder didDecodeImage:(UIImage *)image usingSubset:(UIImage *)subset withResult:(TwoDDecoderResult *)twoDResult {
275   [self presentResultForString:[twoDResult text]];
276   [self presentResultPoints:[twoDResult points] forImage:image usingSubset:subset];
277   // now, in a selector, call the delegate to give this overlay time to show the points
278   [self performSelector:@selector(alertDelegate:) withObject:[[twoDResult text] copy] afterDelay:1.0];
279   decoder.delegate = nil;
280   [decoder release];
281 }
282
283 - (void)alertDelegate:(id)text {        
284   if (delegate != nil) {
285     [delegate scanResult:text];
286   }
287 }
288
289 - (void)decoder:(Decoder *)decoder failedToDecodeImage:(UIImage *)image usingSubset:(UIImage *)subset reason:(NSString *)reason {
290   decoder.delegate = nil;
291   [decoder release];
292   [overlayView setPoints:nil];
293   if (!wasCancelled) {
294     [self takePicture:nil];
295   }
296   //[self updateToolbar];
297 }
298
299 @end