improved image cropping, also work around iPhone OS 2.1 bug when picking from photo...
[zxing.git] / iphone / Classes / Decoder.m
1 //
2 //  Decoder.m
3 //  ZXing
4 //
5 //  Created by Christian Brunschen on 31/03/2008.
6 //
7 /*
8  * Copyright 2008 ZXing authors
9  *
10  * Licensed under the Apache License, Version 2.0 (the "License");
11  * you may not use this file except in compliance with the License.
12  * You may obtain a copy of the License at
13  *
14  *      http://www.apache.org/licenses/LICENSE-2.0
15  *
16  * Unless required by applicable law or agreed to in writing, software
17  * distributed under the License is distributed on an "AS IS" BASIS,
18  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19  * See the License for the specific language governing permissions and
20  * limitations under the License.
21  */
22
23 #import "Decoder.h"
24 #import "TwoDDecoderResult.h"
25
26 #include "QRCodeReader.h"
27 #include "ReaderException.h"
28 #include "IllegalArgumentException.h"
29 #include "GrayBytesMonochromeBitmapSource.h"
30
31 using namespace qrcode;
32
33 @implementation Decoder
34
35 @synthesize image;
36 @synthesize cropRect;
37 @synthesize subsetImage;
38 @synthesize subsetData;
39 @synthesize subsetWidth;
40 @synthesize subsetHeight;
41 @synthesize subsetBytesPerRow;
42 @synthesize delegate;
43
44 - (void)willDecodeImage {
45   [self.delegate decoder:self willDecodeImage:self.image usingSubset:self.subsetImage];
46 }
47
48 - (void)progressDecodingImage:(NSString *)progress {
49   [self.delegate decoder:self 
50           decodingImage:self.image 
51             usingSubset:self.subsetImage
52                progress:progress];
53 }
54
55 - (void)didDecodeImage:(TwoDDecoderResult *)result {
56   [self.delegate decoder:self didDecodeImage:self.image usingSubset:self.subsetImage withResult:result];
57 }
58
59 - (void)failedToDecodeImage:(NSString *)reason {
60   [self.delegate decoder:self failedToDecodeImage:self.image usingSubset:self.subsetImage reason:reason];
61 }
62
63 #define SUBSET_SIZE 320.0
64 - (void) prepareSubset {
65   CGSize size = [image size];
66 #ifdef DEBUG
67   NSLog(@"decoding: image is (%.1f x %.1f), cropRect is (%.1f,%.1f)x(%.1f,%.1f)", size.width, size.height,
68                                 cropRect.origin.x, cropRect.origin.y, cropRect.size.width, cropRect.size.height);
69 #endif
70   float scale = fminf(1.0f, fmaxf(SUBSET_SIZE / cropRect.size.width, SUBSET_SIZE / cropRect.size.height));
71         CGPoint offset = CGPointMake(-cropRect.origin.x, -cropRect.origin.y);
72 #ifdef DEBUG
73         NSLog(@"  offset = (%.1f, %.1f), scale = %.3f", offset.x, offset.y, scale);
74 #endif
75         
76   subsetWidth = cropRect.size.width * scale;
77   subsetHeight = cropRect.size.height * scale;
78   
79   subsetBytesPerRow = ((subsetWidth + 0xf) >> 4) << 4;
80 #ifdef DEBUG
81   NSLog(@"decoding: image to decode is (%d x %d) (%d bytes/row)", subsetWidth, subsetHeight, subsetBytesPerRow);
82 #endif
83   
84   subsetData = (unsigned char *)malloc(subsetBytesPerRow * subsetHeight);
85 #ifdef DEBUG
86   NSLog(@"allocated %d bytes of memory", subsetBytesPerRow * subsetHeight);
87 #endif
88   
89   CGColorSpaceRef grayColorSpace = CGColorSpaceCreateDeviceGray();
90   
91   CGContextRef ctx = 
92   CGBitmapContextCreate(subsetData, subsetWidth, subsetHeight, 
93                         8, subsetBytesPerRow, grayColorSpace, 
94                         kCGImageAlphaNone);
95   CGColorSpaceRelease(grayColorSpace);
96   CGContextSetInterpolationQuality(ctx, kCGInterpolationNone);
97   CGContextSetAllowsAntialiasing(ctx, false);
98         // adjust the coordinate system
99         CGContextTranslateCTM(ctx, 0.0, subsetHeight);
100         CGContextScaleCTM(ctx, 1.0, -1.0);      
101   
102 #ifdef DEBUG
103   NSLog(@"created %dx%d bitmap context", subsetWidth, subsetHeight);
104 #endif
105         
106         UIGraphicsPushContext(ctx);
107         CGRect rect = CGRectMake(offset.x * scale, offset.y * scale, scale * size.width, scale * size.height);
108 #ifdef DEBUG
109         NSLog(@"rect for image = (%.1f,%.1f)x(%.1f,%.1f)", rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
110 #endif
111         [image drawInRect:rect];
112         UIGraphicsPopContext();
113   
114 #ifdef DEBUG
115   NSLog(@"drew image into %d(%d)x%d  bitmap context", subsetWidth, subsetBytesPerRow, subsetHeight);
116 #endif
117   CGContextFlush(ctx);
118 #ifdef DEBUG
119   NSLog(@"flushed context");
120 #endif
121     
122   CGImageRef subsetImageRef = CGBitmapContextCreateImage(ctx);
123 #ifdef DEBUG
124   NSLog(@"created CGImage from context");
125 #endif
126         
127   self.subsetImage = [UIImage imageWithCGImage:subsetImageRef];
128   CGImageRelease(subsetImageRef);
129   
130   CGContextRelease(ctx);
131 #ifdef DEBUG
132   NSLog(@"released context");  
133 #endif
134 }  
135
136 - (void)decode:(id)arg {
137   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
138   { 
139     QRCodeReader reader;
140 #ifdef DEBUG
141     NSLog(@"created QRCoreReader");
142 #endif
143     
144     Ref<MonochromeBitmapSource> grayImage
145     (new GrayBytesMonochromeBitmapSource(subsetData, subsetWidth, subsetHeight, subsetBytesPerRow));
146 #ifdef DEBUG
147     NSLog(@"created GrayBytesMonochromeBitmapSource", subsetWidth, subsetHeight);
148     NSLog(@"grayImage count = %d", grayImage->count());
149 #endif
150     
151     TwoDDecoderResult *decoderResult = nil;
152     
153 #ifdef TRY_ROTATIONS
154     for (int i = 0; !decoderResult && i < 4; i++) {
155 #endif
156
157     try {
158 #ifdef DEBUG
159       NSLog(@"decoding gray image");
160 #endif
161       Ref<Result> result(reader.decode(grayImage));
162 #ifdef DEBUG
163       NSLog(@"gray image decoded");
164 #endif
165       
166       Ref<String> resultText(result->getText());
167       const char *cString = resultText->getText().c_str();
168       ArrayRef<Ref<ResultPoint> > resultPoints = result->getResultPoints();
169       NSMutableArray *points = 
170         [NSMutableArray arrayWithCapacity:resultPoints->size()];
171       
172       for (size_t i = 0; i < resultPoints->size(); i++) {
173         Ref<ResultPoint> rp(resultPoints[i]);
174         CGPoint p = CGPointMake(rp->getX(), rp->getY());
175         [points addObject:[NSValue valueWithCGPoint:p]];
176       }
177       
178       NSString *resultString = [NSString stringWithCString:cString
179                                         encoding:NSUTF8StringEncoding];
180       
181       decoderResult = [TwoDDecoderResult resultWithText:resultString
182                                              points:points];
183     } catch (ReaderException *rex) {
184       NSLog(@"failed to decode, caught ReaderException '%s'",
185             rex->what());
186       delete rex;
187     } catch (IllegalArgumentException *iex) {
188       NSLog(@"failed to decode, caught IllegalArgumentException '%s'", 
189             iex->what());
190       delete iex;
191     } catch (...) {
192       NSLog(@"Caught unknown exception!");
193     }
194
195 #ifdef TRY_ROTATIONS
196       if (!decoderResult) {
197 #ifdef DEBUG
198         NSLog(@"rotating gray image");
199 #endif
200         grayImage = grayImage->rotateCounterClockwise();
201 #ifdef DEBUG
202         NSLog(@"gray image rotated");
203 #endif
204       }
205     }
206 #endif
207     
208     if (decoderResult) {
209       [self performSelectorOnMainThread:@selector(didDecodeImage:)
210                              withObject:decoderResult
211                           waitUntilDone:NO];
212     } else {
213       [self performSelectorOnMainThread:@selector(failedToDecodeImage:)
214                              withObject:NSLocalizedString(@"Decoder BarcodeDetectionFailure", @"No barcode detected.")
215                           waitUntilDone:NO];
216     }
217
218     free(subsetData);
219     self.subsetData = NULL;
220   }
221   [pool release];
222 #ifdef DEBUG
223   NSLog(@"finished decoding.");
224 #endif
225   
226   // if this is not the main thread, then we end it
227   if (![NSThread isMainThread]) {
228     [NSThread exit];
229   }
230 }
231
232 - (void) decodeImage:(UIImage *)i {
233   [self decodeImage:i cropRect:CGRectMake(0.0f, 0.0f, image.size.width, image.size.height)];
234 }
235
236 - (void) decodeImage:(UIImage *)i cropRect:(CGRect)cr {
237         self.image = i;
238         self.cropRect = cr;
239   
240   [self prepareSubset];
241         [self.delegate decoder:self willDecodeImage:i usingSubset:self.subsetImage];
242
243   
244   [self performSelectorOnMainThread:@selector(progressDecodingImage:)
245                          withObject:NSLocalizedString(@"Decoder MessageWhileDecoding", @"Decoding ...")
246                       waitUntilDone:NO];  
247   
248         [NSThread detachNewThreadSelector:@selector(decode:) 
249                            toTarget:self 
250                          withObject:nil];
251 }
252
253 - (void) dealloc {
254         [image release];
255   [subsetImage release];
256   if (subsetData) free(subsetData);
257         [super dealloc];
258 }
259
260 @end