/*
|
* Copyright 2013 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 "ZXAztecCode.h"
|
#import "ZXAztecEncoder.h"
|
#import "ZXAztecHighLevelEncoder.h"
|
#import "ZXBitArray.h"
|
#import "ZXBitMatrix.h"
|
#import "ZXByteArray.h"
|
#import "ZXGenericGF.h"
|
#import "ZXIntArray.h"
|
#import "ZXReedSolomonEncoder.h"
|
|
const int ZX_AZTEC_DEFAULT_EC_PERCENT = 33; // default minimal percentage of error check words
|
const int ZX_AZTEC_DEFAULT_LAYERS = 0;
|
const int ZX_AZTEC_MAX_NB_BITS = 32;
|
const int ZX_AZTEC_MAX_NB_BITS_COMPACT = 4;
|
|
const int ZX_AZTEC_WORD_SIZE[] = {
|
4, 6, 6, 8, 8, 8, 8, 8, 8, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
|
12, 12, 12, 12, 12, 12, 12, 12, 12, 12
|
};
|
|
@implementation ZXAztecEncoder
|
|
+ (ZXAztecCode *)encode:(ZXByteArray *)data {
|
return [self encode:data minECCPercent:ZX_AZTEC_DEFAULT_EC_PERCENT userSpecifiedLayers:ZX_AZTEC_DEFAULT_LAYERS];
|
}
|
|
+ (ZXAztecCode *)encode:(ZXByteArray *)data minECCPercent:(int)minECCPercent userSpecifiedLayers:(int)userSpecifiedLayers {
|
// High-level encode
|
ZXBitArray *bits = [[[ZXAztecHighLevelEncoder alloc] initWithText:data] encode];
|
|
// stuff bits and choose symbol size
|
int eccBits = bits.size * minECCPercent / 100 + 11;
|
int totalSizeBits = bits.size + eccBits;
|
BOOL compact;
|
int layers;
|
int totalBitsInLayer;
|
int wordSize = ZX_AZTEC_WORD_SIZE[0];
|
ZXBitArray *stuffedBits;
|
if (userSpecifiedLayers != ZX_AZTEC_DEFAULT_LAYERS) {
|
compact = userSpecifiedLayers < 0;
|
layers = abs(userSpecifiedLayers);
|
if (layers > (compact ? ZX_AZTEC_MAX_NB_BITS_COMPACT : ZX_AZTEC_MAX_NB_BITS)) {
|
@throw [NSException exceptionWithName:@"IllegalArgumentException"
|
reason:[NSString stringWithFormat:@"Illegal value %d for layers", userSpecifiedLayers]
|
userInfo:nil];
|
}
|
totalBitsInLayer = [self totalBitsInLayer:layers compact:compact];
|
wordSize = ZX_AZTEC_WORD_SIZE[layers];
|
int usableBitsInLayers = totalBitsInLayer - (totalBitsInLayer % wordSize);
|
stuffedBits = [self stuffBits:bits wordSize:wordSize];
|
if (stuffedBits.size + eccBits > usableBitsInLayers) {
|
@throw [NSException exceptionWithName:@"IllegalArgumentException"
|
reason:@"Data too large for user specified layer"
|
userInfo:nil];
|
}
|
if (compact && stuffedBits.size > wordSize * 64) {
|
// Compact format only allows 64 data words, though C4 can hold more words than that
|
@throw [NSException exceptionWithName:@"IllegalArgumentException"
|
reason:@"Data too large for user specified layer"
|
userInfo:nil];
|
}
|
} else {
|
// We look at the possible table sizes in the order Compact1, Compact2, Compact3,
|
// Compact4, Normal4,... Normal(i) for i < 4 isn't typically used since Compact(i+1)
|
// is the same size, but has more data.
|
for (int i = 0; ; i++) {
|
if (i > ZX_AZTEC_MAX_NB_BITS) {
|
@throw [NSException exceptionWithName:@"IllegalArgumentException"
|
reason:@"Data too large for an Aztec code"
|
userInfo:nil];
|
}
|
compact = i <= 3;
|
layers = compact ? i + 1 : i;
|
totalBitsInLayer = [self totalBitsInLayer:layers compact:compact];
|
if (totalSizeBits > totalBitsInLayer) {
|
continue;
|
}
|
// [Re]stuff the bits if this is the first opportunity, or if the
|
// wordSize has changed
|
if (wordSize != ZX_AZTEC_WORD_SIZE[layers]) {
|
wordSize = ZX_AZTEC_WORD_SIZE[layers];
|
stuffedBits = [self stuffBits:bits wordSize:wordSize];
|
}
|
int usableBitsInLayers = totalBitsInLayer - (totalBitsInLayer % wordSize);
|
if (compact && stuffedBits.size > wordSize * 64) {
|
// Compact format only allows 64 data words, though C4 can hold more words than that
|
continue;
|
}
|
if (stuffedBits.size + eccBits <= usableBitsInLayers) {
|
break;
|
}
|
}
|
}
|
ZXBitArray *messageBits = [self generateCheckWords:stuffedBits totalBits:totalBitsInLayer wordSize:wordSize];
|
|
// generate check words
|
int messageSizeInWords = stuffedBits.size / wordSize;
|
ZXBitArray *modeMessage = [self generateModeMessageCompact:compact layers:layers messageSizeInWords:messageSizeInWords];
|
|
// allocate symbol
|
int baseMatrixSize = compact ? 11 + layers * 4 : 14 + layers * 4; // not including alignment lines
|
int alignmentMap[baseMatrixSize];
|
memset(alignmentMap, 0, baseMatrixSize * sizeof(int));
|
int matrixSize;
|
if (compact) {
|
// no alignment marks in compact mode, alignmentMap is a no-op
|
matrixSize = baseMatrixSize;
|
for (int i = 0; i < baseMatrixSize; i++) {
|
alignmentMap[i] = i;
|
}
|
} else {
|
matrixSize = baseMatrixSize + 1 + 2 * ((baseMatrixSize / 2 - 1) / 15);
|
int origCenter = baseMatrixSize / 2;
|
int center = matrixSize / 2;
|
for (int i = 0; i < origCenter; i++) {
|
int newOffset = i + i / 15;
|
alignmentMap[origCenter - i - 1] = center - newOffset - 1;
|
alignmentMap[origCenter + i] = center + newOffset + 1;
|
}
|
}
|
ZXBitMatrix *matrix = [[ZXBitMatrix alloc] initWithDimension:matrixSize];
|
|
// draw data bits
|
for (int i = 0, rowOffset = 0; i < layers; i++) {
|
int rowSize = compact ? (layers - i) * 4 + 9 : (layers - i) * 4 + 12;
|
for (int j = 0; j < rowSize; j++) {
|
int columnOffset = j * 2;
|
for (int k = 0; k < 2; k++) {
|
if ([messageBits get:rowOffset + columnOffset + k]) {
|
[matrix setX:alignmentMap[i * 2 + k] y:alignmentMap[i * 2 + j]];
|
}
|
if ([messageBits get:rowOffset + rowSize * 2 + columnOffset + k]) {
|
[matrix setX:alignmentMap[i * 2 + j] y:alignmentMap[baseMatrixSize - 1 - i * 2 - k]];
|
}
|
if ([messageBits get:rowOffset + rowSize * 4 + columnOffset + k]) {
|
[matrix setX:alignmentMap[baseMatrixSize - 1 - i * 2 - k] y:alignmentMap[baseMatrixSize - 1 - i * 2 - j]];
|
}
|
if ([messageBits get:rowOffset + rowSize * 6 + columnOffset + k]) {
|
[matrix setX:alignmentMap[baseMatrixSize - 1 - i * 2 - j] y:alignmentMap[i * 2 + k]];
|
}
|
}
|
}
|
rowOffset += rowSize * 8;
|
}
|
|
// draw mode message
|
[self drawModeMessage:matrix compact:compact matrixSize:matrixSize modeMessage:modeMessage];
|
|
// draw alignment marks
|
if (compact) {
|
[self drawBullsEye:matrix center:matrixSize / 2 size:5];
|
} else {
|
[self drawBullsEye:matrix center:matrixSize / 2 size:7];
|
for (int i = 0, j = 0; i < baseMatrixSize / 2 - 1; i += 15, j += 16) {
|
for (int k = (matrixSize / 2) & 1; k < matrixSize; k += 2) {
|
[matrix setX:matrixSize / 2 - j y:k];
|
[matrix setX:matrixSize / 2 + j y:k];
|
[matrix setX:k y:matrixSize / 2 - j];
|
[matrix setX:k y:matrixSize / 2 + j];
|
}
|
}
|
}
|
|
ZXAztecCode *aztec = [[ZXAztecCode alloc] init];
|
aztec.compact = compact;
|
aztec.size = matrixSize;
|
aztec.layers = layers;
|
aztec.codeWords = messageSizeInWords;
|
aztec.matrix = matrix;
|
return aztec;
|
}
|
|
+ (void)drawBullsEye:(ZXBitMatrix *)matrix center:(int)center size:(int)size {
|
for (int i = 0; i < size; i += 2) {
|
for (int j = center - i; j <= center + i; j++) {
|
[matrix setX:j y:center - i];
|
[matrix setX:j y:center + i];
|
[matrix setX:center - i y:j];
|
[matrix setX:center + i y:j];
|
}
|
}
|
[matrix setX:center - size y:center - size];
|
[matrix setX:center - size + 1 y:center - size];
|
[matrix setX:center - size y:center - size + 1];
|
[matrix setX:center + size y:center - size];
|
[matrix setX:center + size y:center - size + 1];
|
[matrix setX:center + size y:center + size - 1];
|
}
|
|
+ (ZXBitArray *)generateModeMessageCompact:(BOOL)compact layers:(int)layers messageSizeInWords:(int)messageSizeInWords {
|
ZXBitArray *modeMessage = [[ZXBitArray alloc] init];
|
if (compact) {
|
[modeMessage appendBits:layers - 1 numBits:2];
|
[modeMessage appendBits:messageSizeInWords - 1 numBits:6];
|
modeMessage = [self generateCheckWords:modeMessage totalBits:28 wordSize:4];
|
} else {
|
[modeMessage appendBits:layers - 1 numBits:5];
|
[modeMessage appendBits:messageSizeInWords - 1 numBits:11];
|
modeMessage = [self generateCheckWords:modeMessage totalBits:40 wordSize:4];
|
}
|
return modeMessage;
|
}
|
|
+ (void)drawModeMessage:(ZXBitMatrix *)matrix compact:(BOOL)compact matrixSize:(int)matrixSize modeMessage:(ZXBitArray *)modeMessage {
|
int center = matrixSize / 2;
|
if (compact) {
|
for (int i = 0; i < 7; i++) {
|
int offset = center - 3 + i;
|
if ([modeMessage get:i]) {
|
[matrix setX:offset y:center - 5];
|
}
|
if ([modeMessage get:i + 7]) {
|
[matrix setX:center + 5 y:offset];
|
}
|
if ([modeMessage get:20 - i]) {
|
[matrix setX:offset y:center + 5];
|
}
|
if ([modeMessage get:27 - i]) {
|
[matrix setX:center - 5 y:offset];
|
}
|
}
|
} else {
|
for (int i = 0; i < 10; i++) {
|
int offset = center - 5 + i + i / 5;
|
if ([modeMessage get:i]) {
|
[matrix setX:offset y:center - 7];
|
}
|
if ([modeMessage get:i + 10]) {
|
[matrix setX:center + 7 y:offset];
|
}
|
if ([modeMessage get:29 - i]) {
|
[matrix setX:offset y:center + 7];
|
}
|
if ([modeMessage get:39 - i]) {
|
[matrix setX:center - 7 y:offset];
|
}
|
}
|
}
|
}
|
|
+ (ZXBitArray *)generateCheckWords:(ZXBitArray *)bitArray totalBits:(int)totalBits wordSize:(int)wordSize {
|
// bitArray is guaranteed to be a multiple of the wordSize, so no padding needed
|
int messageSizeInWords = bitArray.size / wordSize;
|
ZXReedSolomonEncoder *rs = [[ZXReedSolomonEncoder alloc] initWithField:[self getGF:wordSize]];
|
int totalWords = totalBits / wordSize;
|
|
ZXIntArray *messageWords = [self bitsToWords:bitArray wordSize:wordSize totalWords:totalWords];
|
[rs encode:messageWords ecBytes:totalWords - messageSizeInWords];
|
int startPad = totalBits % wordSize;
|
ZXBitArray *messageBits = [[ZXBitArray alloc] init];
|
[messageBits appendBits:0 numBits:startPad];
|
for (int i = 0; i < totalWords; i++) {
|
[messageBits appendBits:messageWords.array[i] numBits:wordSize];
|
}
|
return messageBits;
|
}
|
|
+ (ZXIntArray *)bitsToWords:(ZXBitArray *)stuffedBits wordSize:(int)wordSize totalWords:(int)totalWords {
|
ZXIntArray *message = [[ZXIntArray alloc] initWithLength:totalWords];
|
int i;
|
int n;
|
for (i = 0, n = stuffedBits.size / wordSize; i < n; i++) {
|
int32_t value = 0;
|
for (int j = 0; j < wordSize; j++) {
|
value |= [stuffedBits get:i * wordSize + j] ? (1 << (wordSize - j - 1)) : 0;
|
}
|
message.array[i] = value;
|
}
|
return message;
|
}
|
|
+ (ZXGenericGF *)getGF:(int)wordSize {
|
switch (wordSize) {
|
case 4:
|
return [ZXGenericGF AztecParam];
|
case 6:
|
return [ZXGenericGF AztecData6];
|
case 8:
|
return [ZXGenericGF AztecData8];
|
case 10:
|
return [ZXGenericGF AztecData10];
|
case 12:
|
return [ZXGenericGF AztecData12];
|
default:
|
return nil;
|
}
|
}
|
|
+ (ZXBitArray *)stuffBits:(ZXBitArray *)bits wordSize:(int)wordSize {
|
ZXBitArray *arrayOut = [[ZXBitArray alloc] init];
|
|
int n = bits.size;
|
int mask = (1 << wordSize) - 2;
|
for (int i = 0; i < n; i += wordSize) {
|
int word = 0;
|
for (int j = 0; j < wordSize; j++) {
|
if (i + j >= n || [bits get:i + j]) {
|
word |= 1 << (wordSize - 1 - j);
|
}
|
}
|
if ((word & mask) == mask) {
|
[arrayOut appendBits:word & mask numBits:wordSize];
|
i--;
|
} else if ((word & mask) == 0) {
|
[arrayOut appendBits:word | 1 numBits:wordSize];
|
i--;
|
} else {
|
[arrayOut appendBits:word numBits:wordSize];
|
}
|
}
|
|
return arrayOut;
|
}
|
|
+ (int)totalBitsInLayer:(int)layers compact:(BOOL)compact {
|
return ((compact ? 88 : 112) + 16 * layers) * layers;
|
}
|
|
@end
|