/*
|
* Copyright 2012 ZXing authors
|
*
|
* 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 "ZXBinaryBitmap.h"
|
#import "ZXBitArray.h"
|
#import "ZXDecodeHints.h"
|
#import "ZXErrors.h"
|
#import "ZXIntArray.h"
|
#import "ZXOneDReader.h"
|
#import "ZXResult.h"
|
#import "ZXResultPoint.h"
|
|
@implementation ZXOneDReader
|
|
- (ZXResult *)decode:(ZXBinaryBitmap *)image error:(NSError **)error {
|
return [self decode:image hints:nil error:error];
|
}
|
|
// Note that we don't try rotation without the try harder flag, even if rotation was supported.
|
- (ZXResult *)decode:(ZXBinaryBitmap *)image hints:(ZXDecodeHints *)hints error:(NSError **)error {
|
NSError *decodeError = nil;
|
ZXResult *result = [self doDecode:image hints:hints error:&decodeError];
|
if (result) {
|
return result;
|
} else if (decodeError.code == ZXNotFoundError) {
|
BOOL tryHarder = hints != nil && hints.tryHarder;
|
if (tryHarder && [image rotateSupported]) {
|
ZXBinaryBitmap *rotatedImage = [image rotateCounterClockwise];
|
ZXResult *result = [self doDecode:rotatedImage hints:hints error:error];
|
if (!result) {
|
return nil;
|
}
|
// Record that we found it rotated 90 degrees CCW / 270 degrees CW
|
NSMutableDictionary *metadata = [result resultMetadata];
|
int orientation = 270;
|
if (metadata != nil && metadata[@(kResultMetadataTypeOrientation)]) {
|
// But if we found it reversed in doDecode(), add in that result here:
|
orientation = (orientation + [((NSNumber *)metadata[@(kResultMetadataTypeOrientation)]) intValue]) % 360;
|
}
|
[result putMetadata:kResultMetadataTypeOrientation value:@(orientation)];
|
// Update result points
|
NSMutableArray *points = [result resultPoints];
|
if (points != nil) {
|
int height = [rotatedImage height];
|
for (int i = 0; i < [points count]; i++) {
|
points[i] = [[ZXResultPoint alloc] initWithX:height - [(ZXResultPoint *)points[i] y]
|
y:[(ZXResultPoint *)points[i] x]];
|
}
|
}
|
return result;
|
}
|
}
|
|
if (error) *error = decodeError;
|
return nil;
|
}
|
|
- (void)reset {
|
// do nothing
|
}
|
|
/**
|
* We're going to examine rows from the middle outward, searching alternately above and below the
|
* middle, and farther out each time. rowStep is the number of rows between each successive
|
* attempt above and below the middle. So we'd scan row middle, then middle - rowStep, then
|
* middle + rowStep, then middle - (2 * rowStep), etc.
|
* rowStep is bigger as the image is taller, but is always at least 1. We've somewhat arbitrarily
|
* decided that moving up and down by about 1/16 of the image is pretty good; we try more of the
|
* image if "trying harder".
|
*
|
* @param image The image to decode
|
* @param hints Any hints that were requested
|
* @return The contents of the decoded barcode or nil if an error occurs
|
*/
|
- (ZXResult *)doDecode:(ZXBinaryBitmap *)image hints:(ZXDecodeHints *)hints error:(NSError **)error {
|
int width = image.width;
|
int height = image.height;
|
ZXBitArray *row = [[ZXBitArray alloc] initWithSize:width];
|
int middle = height >> 1;
|
BOOL tryHarder = hints != nil && hints.tryHarder;
|
int rowStep = MAX(1, height >> (tryHarder ? 8 : 5));
|
int maxLines;
|
if (tryHarder) {
|
maxLines = height;
|
} else {
|
maxLines = 15;
|
}
|
|
for (int x = 0; x < maxLines; x++) {
|
int rowStepsAboveOrBelow = (x + 1) / 2;
|
BOOL isAbove = (x & 0x01) == 0;
|
int rowNumber = middle + rowStep * (isAbove ? rowStepsAboveOrBelow : -rowStepsAboveOrBelow);
|
if (rowNumber < 0 || rowNumber >= height) {
|
break;
|
}
|
|
NSError *rowError = nil;
|
row = [image blackRow:rowNumber row:row error:&rowError];
|
if (!row && rowError.code == ZXNotFoundError) {
|
continue;
|
} else if (!row) {
|
if (error) *error = rowError;
|
return nil;
|
}
|
|
for (int attempt = 0; attempt < 2; attempt++) {
|
if (attempt == 1) {
|
[row reverse];
|
if (hints != nil && hints.resultPointCallback) {
|
hints = [hints copy];
|
hints.resultPointCallback = nil;
|
}
|
}
|
|
ZXResult *result = [self decodeRow:rowNumber row:row hints:hints error:nil];
|
if (result) {
|
if (attempt == 1) {
|
[result putMetadata:kResultMetadataTypeOrientation value:@180];
|
NSMutableArray *points = [result resultPoints];
|
if (points != nil) {
|
points[0] = [[ZXResultPoint alloc] initWithX:width - [(ZXResultPoint *)points[0] x]
|
y:[(ZXResultPoint *)points[0] y]];
|
points[1] = [[ZXResultPoint alloc] initWithX:width - [(ZXResultPoint *)points[1] x]
|
y:[(ZXResultPoint *)points[1] y]];
|
}
|
}
|
return result;
|
}
|
}
|
}
|
|
if (error) *error = ZXNotFoundErrorInstance();
|
return nil;
|
}
|
|
/**
|
* Records the size of successive runs of white and black pixels in a row, starting at a given point.
|
* The values are recorded in the given array, and the number of runs recorded is equal to the size
|
* of the array. If the row starts on a white pixel at the given start point, then the first count
|
* recorded is the run of white pixels starting from that point; likewise it is the count of a run
|
* of black pixels if the row begin on a black pixels at that point.
|
*
|
* @param row row to count from
|
* @param start offset into row to start at
|
* @param counters array into which to record counts or nil if counters cannot be filled entirely
|
* from row before running out of pixels
|
*/
|
+ (BOOL)recordPattern:(ZXBitArray *)row start:(int)start counters:(ZXIntArray *)counters {
|
int numCounters = counters.length;
|
[counters clear];
|
int32_t *array = counters.array;
|
int end = row.size;
|
if (start >= end) {
|
return NO;
|
}
|
BOOL isWhite = ![row get:start];
|
int counterPosition = 0;
|
int i = start;
|
|
while (i < end) {
|
if ([row get:i] ^ isWhite) {
|
array[counterPosition]++;
|
} else {
|
counterPosition++;
|
if (counterPosition == numCounters) {
|
break;
|
} else {
|
array[counterPosition] = 1;
|
isWhite = !isWhite;
|
}
|
}
|
i++;
|
}
|
|
if (!(counterPosition == numCounters || (counterPosition == numCounters - 1 && i == end))) {
|
return NO;
|
}
|
return YES;
|
}
|
|
+ (BOOL)recordPatternInReverse:(ZXBitArray *)row start:(int)start counters:(ZXIntArray *)counters {
|
int numTransitionsLeft = counters.length;
|
BOOL last = [row get:start];
|
while (start > 0 && numTransitionsLeft >= 0) {
|
if ([row get:--start] != last) {
|
numTransitionsLeft--;
|
last = !last;
|
}
|
}
|
|
if (numTransitionsLeft >= 0 || ![self recordPattern:row start:start + 1 counters:counters]) {
|
return NO;
|
}
|
return YES;
|
}
|
|
/**
|
* Determines how closely a set of observed counts of runs of black/white values matches a given
|
* target pattern. This is reported as the ratio of the total variance from the expected pattern
|
* proportions across all pattern elements, to the length of the pattern.
|
*
|
* @param counters observed counters
|
* @param pattern expected pattern
|
* @param maxIndividualVariance The most any counter can differ before we give up
|
* @return ratio of total variance between counters and pattern compared to total pattern size
|
*/
|
+ (float)patternMatchVariance:(ZXIntArray *)counters pattern:(const int[])pattern maxIndividualVariance:(float)maxIndividualVariance {
|
int numCounters = counters.length;
|
int total = 0;
|
int patternLength = 0;
|
|
int32_t *array = counters.array;
|
for (int i = 0; i < numCounters; i++) {
|
total += array[i];
|
patternLength += pattern[i];
|
}
|
|
if (total < patternLength || patternLength == 0) {
|
return FLT_MAX;
|
}
|
float unitBarWidth = (float) total / patternLength;
|
maxIndividualVariance *= unitBarWidth;
|
|
float totalVariance = 0.0f;
|
for (int x = 0; x < numCounters; x++) {
|
int counter = array[x];
|
float scaledPattern = pattern[x] * unitBarWidth;
|
float variance = counter > scaledPattern ? counter - scaledPattern : scaledPattern - counter;
|
if (variance > maxIndividualVariance) {
|
return FLT_MAX;
|
}
|
totalVariance += variance;
|
}
|
|
return totalVariance / total;
|
}
|
|
/**
|
* Attempts to decode a one-dimensional barcode format given a single row of
|
* an image.
|
*
|
* @param rowNumber row number from top of the row
|
* @param row the black/white pixel data of the row
|
* @param hints decode hints
|
* @return ZXResult containing encoded string and start/end of barcode or nil
|
* if an error occurs or barcode cannot be found
|
*/
|
- (ZXResult *)decodeRow:(int)rowNumber row:(ZXBitArray *)row hints:(ZXDecodeHints *)hints error:(NSError **)error {
|
@throw [NSException exceptionWithName:NSInternalInconsistencyException
|
reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)]
|
userInfo:nil];
|
}
|
|
@end
|