[iphone] Memory leak fixed. Widget does not crash anymore after a few seconds. No...
[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();
31
32 @implementation ZXingWidgetController
33 @synthesize result, actions, showCancel, delegate, soundToPlay, oneDMode;
34
35 - (id)initWithDelegate:(id<ZXingDelegate>)scanDelegate {
36   if (self = [super init]) {
37     [self setDelegate:scanDelegate];
38     beepSound = -1;
39     self.wantsFullScreenLayout = YES;
40     if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])
41       self.sourceType = UIImagePickerControllerSourceTypeCamera;
42     float zoomFactor = CAMERA_SCALAR;
43     if ([self fixedFocus]) {
44       zoomFactor *= 2.0;
45     }
46     if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])
47       self.cameraViewTransform = CGAffineTransformScale(
48                                                       self.cameraViewTransform, zoomFactor, zoomFactor);
49     overlayView = [OverlayView alloc];
50     [overlayView setOneDMode:oneDMode];
51     overlayView = [overlayView initWithCancelEnabled:showCancel frame:[UIScreen mainScreen].bounds];
52     [overlayView setDelegate:self];
53     if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])
54     {
55       self.showsCameraControls = NO;
56       self.cameraOverlayView = overlayView;
57       self.allowsEditing = NO; // [[NSUserDefaults standardUserDefaults] boolForKey:@"allowEditing"];
58     }
59   }
60   
61   return self;
62 }
63
64 - (void)dealloc {
65   if (beepSound != -1) {
66     AudioServicesDisposeSystemSoundID(beepSound);
67   }
68   [overlayView dealloc];
69   [super dealloc];
70 }
71
72 - (void)cancelled {
73   NSLog(@"cancelled called in ZXingWidgetController");
74   wasCancelled = true;
75   if (delegate != nil) {
76     [delegate cancelled];
77   }
78 }
79
80 - (NSString *)getPlatform {
81   size_t size;
82   sysctlbyname("hw.machine", NULL, &size, NULL, 0);
83   char *machine = malloc(size);
84   sysctlbyname("hw.machine", machine, &size, NULL, 0);
85   NSString *platform = [NSString stringWithCString:machine encoding:NSASCIIStringEncoding];
86   free(machine);
87   return platform;
88 }
89
90 - (BOOL)fixedFocus {
91   NSString *platform = [self getPlatform];
92   if ([platform isEqualToString:@"iPhone1,1"] ||
93       [platform isEqualToString:@"iPhone1,2"]) return true;
94   return false;
95 }
96
97 - (void)viewWillAppear:(BOOL)animated {
98   [super viewWillAppear:animated];
99   if ([self soundToPlay] != nil) {
100     OSStatus error = AudioServicesCreateSystemSoundID((CFURLRef)[self soundToPlay], &beepSound);
101     if (error != kAudioServicesNoError) {
102       NSLog(@"Problem loading nearSound.caf");
103     }
104   }
105 }
106
107 - (void)viewDidAppear:(BOOL)animated {
108   [super viewDidAppear:animated];
109   [overlayView setPoints:nil];
110   wasCancelled = false;
111   [NSTimer scheduledTimerWithTimeInterval: FIRST_TAKE_DELAY
112                                    target: self
113                                  selector: @selector(takePicture:)
114                                  userInfo: nil
115                                   repeats: NO];
116 }
117
118 - (CGImageRef)CGImageRotated90:(CGImageRef)imgRef
119 {
120   CGFloat angleInRadians = -90 * (M_PI / 180);
121   CGFloat width = CGImageGetWidth(imgRef);
122   CGFloat height = CGImageGetHeight(imgRef);
123   
124   CGRect imgRect = CGRectMake(0, 0, width, height);
125   CGAffineTransform transform = CGAffineTransformMakeRotation(angleInRadians);
126   CGRect rotatedRect = CGRectApplyAffineTransform(imgRect, transform);
127   
128   CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
129   CGContextRef bmContext = CGBitmapContextCreate(NULL,
130                                                  rotatedRect.size.width,
131                                                  rotatedRect.size.height,
132                                                  8,
133                                                  0,
134                                                  colorSpace,
135                                                  kCGImageAlphaPremultipliedFirst);
136   CGContextSetAllowsAntialiasing(bmContext, FALSE);
137   CGContextSetInterpolationQuality(bmContext, kCGInterpolationNone);
138   CGColorSpaceRelease(colorSpace);
139   //      CGContextTranslateCTM(bmContext,
140   //                                                +(rotatedRect.size.width/2),
141   //                                                +(rotatedRect.size.height/2));
142   CGContextScaleCTM(bmContext, rotatedRect.size.width/rotatedRect.size.height, 1.0);
143   CGContextTranslateCTM(bmContext, 0.0, rotatedRect.size.height);
144   CGContextRotateCTM(bmContext, angleInRadians);
145   //      CGContextTranslateCTM(bmContext,
146   //                                                -(rotatedRect.size.width/2),
147   //                                                -(rotatedRect.size.height/2));
148   CGContextDrawImage(bmContext, CGRectMake(0, 0,
149                                            rotatedRect.size.width,
150                                            rotatedRect.size.height),
151                      imgRef);
152   
153   CGImageRef rotatedImage = CGBitmapContextCreateImage(bmContext);
154   CFRelease(bmContext);
155   [(id)rotatedImage autorelease];
156   
157   return rotatedImage;
158 }
159
160 - (CGImageRef)CGImageRotated180:(CGImageRef)imgRef
161 {
162   CGFloat angleInRadians = M_PI;
163   CGFloat width = CGImageGetWidth(imgRef);
164   CGFloat height = CGImageGetHeight(imgRef);
165   
166   CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
167   CGContextRef bmContext = CGBitmapContextCreate(NULL,
168                                                  width,
169                                                  height,
170                                                  8,
171                                                  0,
172                                                  colorSpace,
173                                                  kCGImageAlphaPremultipliedFirst);
174   CGContextSetAllowsAntialiasing(bmContext, FALSE);
175   CGContextSetInterpolationQuality(bmContext, kCGInterpolationNone);
176   CGColorSpaceRelease(colorSpace);
177   CGContextTranslateCTM(bmContext,
178                         +(width/2),
179                         +(height/2));
180   CGContextRotateCTM(bmContext, angleInRadians);
181   CGContextTranslateCTM(bmContext,
182                         -(width/2),
183                         -(height/2));
184   CGContextDrawImage(bmContext, CGRectMake(0, 0, width, height), imgRef);
185   
186   CGImageRef rotatedImage = CGBitmapContextCreateImage(bmContext);
187   CFRelease(bmContext);
188   [(id)rotatedImage autorelease];
189   
190   return rotatedImage;
191 }
192
193 - (void)takePicture:(NSTimer*)theTimer {
194   CGImageRef capture = UIGetScreenImage();
195   CGRect cropRect = [overlayView cropRect];
196   if (oneDMode) {
197     // let's just give the decoder a vertical band right above the red line
198     cropRect.origin.x = cropRect.origin.x + (cropRect.size.width / 2) - (ONE_D_BAND_HEIGHT + 1);
199     cropRect.size.width = ONE_D_BAND_HEIGHT;
200     // do a rotate
201     CGImageRef croppedImg = CGImageCreateWithImageInRect(capture, cropRect);
202     capture = [self CGImageRotated90:croppedImg];
203     capture = [self CGImageRotated180:capture];
204     //              UIImageWriteToSavedPhotosAlbum([UIImage imageWithCGImage:capture], nil, nil, nil);
205     CGImageRelease(croppedImg);
206     cropRect.origin.x = 0.0;
207     cropRect.origin.y = 0.0;
208     cropRect.size.width = CGImageGetWidth(capture);
209     cropRect.size.height = CGImageGetHeight(capture);
210   }
211   CGImageRef newImage = CGImageCreateWithImageInRect(capture, cropRect);
212   CGImageRelease(capture);
213   //UIImage *scrn = [UIImage imageWithCGImage:newImage];
214   UIImage *scrn = [[UIImage alloc] initWithCGImage:newImage];
215   CGImageRelease(newImage);
216   Decoder *d = [[Decoder alloc] init];
217   d.delegate = self;
218   cropRect.origin.x = 0.0;
219   cropRect.origin.y = 0.0;
220   [d decodeImage:scrn cropRect:cropRect];
221   [scrn release];
222 }
223
224 // DecoderDelegate methods
225
226 - (void)decoder:(Decoder *)decoder willDecodeImage:(UIImage *)image usingSubset:(UIImage *)subset{
227 #ifdef DEBUG
228   NSLog(@"DecoderViewController MessageWhileDecodingWithDimensions: Decoding image (%.0fx%.0f) ...", image.size.width, image.size.height);
229 #endif
230 }
231
232 - (void)decoder:(Decoder *)decoder
233   decodingImage:(UIImage *)image
234     usingSubset:(UIImage *)subset
235        progress:(NSString *)message {
236 }
237
238 - (void)presentResultForString:(NSString *)resultString {
239   self.result = [ResultParser parsedResultForString:resultString];
240   
241   if (beepSound != -1) {
242     AudioServicesPlaySystemSound(beepSound);
243   }
244 #ifdef DEBUG
245   NSLog(@"result string = %@", resultString);
246   NSLog(@"result has %d actions", actions ? 0 : actions.count);
247 #endif
248   //      [self updateToolbar];
249 }
250
251 - (void)presentResultPoints:(NSArray *)resultPoints
252                    forImage:(UIImage *)image
253                 usingSubset:(UIImage *)subset {
254   // simply add the points to the image view
255   [overlayView setPoints:resultPoints];
256 }
257
258 - (void)decoder:(Decoder *)decoder didDecodeImage:(UIImage *)image usingSubset:(UIImage *)subset withResult:(TwoDDecoderResult *)twoDResult {
259   [self presentResultForString:[twoDResult text]];
260   [self presentResultPoints:[twoDResult points] forImage:image usingSubset:subset];
261   // now, in a selector, call the delegate to give this overlay time to show the points
262   [self performSelector:@selector(alertDelegate:) withObject:[[twoDResult text] copy] afterDelay:1.0];
263   decoder.delegate = nil;
264   [decoder release];
265 }
266
267 - (void)alertDelegate:(id)text {        
268   if (delegate != nil) {
269     [delegate scanResult:text];
270   }
271 }
272
273 - (void)decoder:(Decoder *)decoder failedToDecodeImage:(UIImage *)image usingSubset:(UIImage *)subset reason:(NSString *)reason {
274   decoder.delegate = nil;
275   [decoder release];
276   [overlayView setPoints:nil];
277   if (!wasCancelled) {
278     [self takePicture:nil];
279   }
280   //[self updateToolbar];
281 }
282
283 @end