You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

155 lines
6.8 KiB

// Copyright 2022 KURZ Digital Solutions GmbH
//
// SPDX-License-Identifier: Apache-2.0
#import "ZXIBarcodeReader.h"
#import "ReadBarcode.h"
#import "ImageView.h"
#import "Barcode.h"
#import "GTIN.h"
#import "ZXIFormatHelper.h"
#import "ZXIPosition+Helper.h"
#import "ZXIErrors.h"
using namespace ZXing;
NSString *stringToNSString(const std::string &text) {
return [[NSString alloc]initWithBytes:text.data() length:text.size() encoding:NSUTF8StringEncoding];
}
ZXIGTIN *getGTIN(const Barcode &barcode) {
try {
auto country = GTIN::LookupCountryIdentifier(barcode.text(TextMode::Plain), barcode.format());
auto addOn = GTIN::EanAddOn(barcode);
return country.empty()
? nullptr
: [[ZXIGTIN alloc]initWithCountry:stringToNSString(country)
addOn:stringToNSString(addOn)
price:stringToNSString(GTIN::Price(addOn))
issueNumber:stringToNSString(GTIN::IssueNr(addOn))];
} catch (std::exception e) {
// Because invalid GTIN data can lead to exceptions, in which case
// we don't want to discard the whole result.
return nullptr;
}
}
@interface ZXIReaderOptions()
@property(nonatomic) ZXing::ReaderOptions cppOpts;
@end
@interface ZXIBarcodeReader()
@property (nonatomic, strong) CIContext* ciContext;
@end
@implementation ZXIBarcodeReader
- (instancetype)init {
return [self initWithOptions: [[ZXIReaderOptions alloc] init]];
}
- (instancetype)initWithOptions:(ZXIReaderOptions*)options{
self = [super init];
self.ciContext = [[CIContext alloc] initWithOptions:@{kCIContextWorkingColorSpace: [NSNull new]}];
self.options = options;
return self;
}
- (NSArray<ZXIResult *> *)readCVPixelBuffer:(nonnull CVPixelBufferRef)pixelBuffer
error:(NSError *__autoreleasing _Nullable *)error {
OSType pixelFormat = CVPixelBufferGetPixelFormatType(pixelBuffer);
// We tried to work with all luminance based formats listed in kCVPixelFormatType
// but only the following ones seem to be supported on iOS.
switch (pixelFormat) {
case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange:
case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange:
NSInteger cols = CVPixelBufferGetWidth(pixelBuffer);
NSInteger rows = CVPixelBufferGetHeight(pixelBuffer);
NSInteger bytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
const uint8_t * bytes = static_cast<const uint8_t *>(CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0));
ImageView imageView = ImageView(
static_cast<const uint8_t *>(bytes),
static_cast<int>(cols),
static_cast<int>(rows),
ImageFormat::Lum,
static_cast<int>(bytesPerRow),
0);
NSArray* results = [self readImageView:imageView error:error];
CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
return results;
}
// If given pixel format is not a supported type with a luminance channel we just use the
// default method
return [self readCIImage:[[CIImage alloc] initWithCVImageBuffer:pixelBuffer] error:error];
}
- (NSArray<ZXIResult *> *)readCIImage:(nonnull CIImage *)image
error:(NSError *__autoreleasing _Nullable *)error {
CGImageRef cgImage = [self.ciContext createCGImage:image fromRect:image.extent];
auto results = [self readCGImage:cgImage error:error];
CGImageRelease(cgImage);
return results;
}
- (NSArray<ZXIResult *> *)readCGImage:(nonnull CGImageRef)image
error:(NSError *__autoreleasing _Nullable *)error {
CGFloat cols = CGImageGetWidth(image);
CGFloat rows = CGImageGetHeight(image);
NSMutableData *data = [NSMutableData dataWithLength: cols * rows];
CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericGray);
CGContextRef contextRef = CGBitmapContextCreate(data.mutableBytes,// Pointer to backing data
cols, // Width of bitmap
rows, // Height of bitmap
8, // Bits per component
cols, // Bytes per row
colorSpace, // Colorspace
kCGBitmapByteOrderDefault); // Bitmap info flags
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(contextRef, CGRectMake(0, 0, cols, rows), image);
CGContextRelease(contextRef);
ImageView imageView = ImageView(
static_cast<const uint8_t *>(data.bytes),
static_cast<int>(cols),
static_cast<int>(rows),
ImageFormat::Lum);
return [self readImageView:imageView error:error];
}
- (NSArray<ZXIResult*> *)readImageView:(ImageView)imageView
error:(NSError *__autoreleasing _Nullable *)error {
try {
Barcodes results = ReadBarcodes(imageView, self.options.cppOpts);
NSMutableArray* zxiResults = [NSMutableArray array];
for (auto result: results) {
[zxiResults addObject:
[[ZXIResult alloc] init:stringToNSString(result.text())
format:ZXIFormatFromBarcodeFormat(result.format())
bytes:[[NSData alloc] initWithBytes:result.bytes().data() length:result.bytes().size()]
position:[[ZXIPosition alloc]initWithPosition: result.position()]
orientation:result.orientation()
ecLevel:stringToNSString(result.ecLevel())
symbologyIdentifier:stringToNSString(result.symbologyIdentifier())
sequenceSize:result.sequenceSize()
sequenceIndex:result.sequenceIndex()
sequenceId:stringToNSString(result.sequenceId())
readerInit:result.readerInit()
lineCount:result.lineCount()
gtin:getGTIN(result)]
];
}
return zxiResults;
} catch(std::exception &e) {
SetNSError(error, ZXIReaderError, e.what());
return nil;
} catch (...) {
SetNSError(error, ZXIReaderError, "An unknown error occurred");
return nil;
}
}
@end