Skip to content

Commit e2b37c3

Browse files
committed
serratus#195: Added Code93 barcode reader;
1 parent 2e4b14b commit e2b37c3

17 files changed

+326
-6
lines changed

example/file_input.html

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ <h3>Working with file-input</h3>
5353
<option value="codabar">Codabar</option>
5454
<option value="i2of5">Interleaved 2 of 5</option>
5555
<option value="2of5">Standard 2 of 5</option>
56+
<option value="code_93">Code 93</option>
5657
</select>
5758
</label>
5859
<label>

example/live_w_locator.html

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ <h3>The user's camera</h3>
4545
<option value="codabar">Codabar</option>
4646
<option value="i2of5">Interleaved 2 of 5</option>
4747
<option value="2of5">Standard 2 of 5</option>
48+
<option value="code_93">Code 93</option>
4849
</select>
4950
</label>
5051
<label>

example/live_w_locator.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,17 @@ $(function() {
33
capture: true,
44
capacity: 20,
55
blacklist: [{
6-
code: "9577149002", format: "2of5"
6+
code: "WIWV8ETQZ1", format: "code_93"
77
}, {
8-
code: "5776158811", format: "2of5"
8+
code: "EH3C-%GU23RK3", format: "code_93"
99
}, {
10-
code: "0463381455", format: "2of5"
10+
code: "O308SIHQOXN5SA/PJ", format: "code_93"
1111
}, {
12-
code: "3261594101", format: "2of5"
12+
code: "DG7Q$TV8JQ/EN", format: "code_93"
1313
}, {
14-
code: "6730705801", format: "2of5"
14+
code: "VOFD1DB5A.1F6QU", format: "code_93"
1515
}, {
16-
code: "8568166929", format: "2of5"
16+
code: "4SO64P4X8 U4YUU1T-", format: "code_93"
1717
}],
1818
filter: function(codeResult) {
1919
// only store results which match this constraint

example/static_images.html

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ <h3>Working with static images</h3>
4747
<option value="i2of5">I2of5</option>
4848
<option value="i2of5">Interleaved 2 of 5</option>
4949
<option value="2of5">Standard 2 of 5</option>
50+
<option value="code_93">Code 93</option>
5051
</select>
5152
</fieldset>
5253
</div>

src/decoder/barcode_decoder.js

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import EAN5Reader from '../reader/ean_5_reader';
1212
import UPCEReader from '../reader/upc_e_reader';
1313
import I2of5Reader from '../reader/i2of5_reader';
1414
import TwoOfFiveReader from '../reader/2of5_reader';
15+
import Code93Reader from '../reader/code_93_reader';
1516

1617
const READERS = {
1718
code_128_reader: Code128Reader,
@@ -26,6 +27,7 @@ const READERS = {
2627
upc_e_reader: UPCEReader,
2728
i2of5_reader: I2of5Reader,
2829
'2of5_reader': TwoOfFiveReader,
30+
code_93_reader: Code93Reader
2931
};
3032
export default {
3133
create: function(config, inputImageWrapper) {

src/reader/code_93_reader.js

+278
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
import BarcodeReader from './barcode_reader';
2+
import ArrayHelper from '../common/array_helper';
3+
4+
function Code93Reader() {
5+
BarcodeReader.call(this);
6+
}
7+
8+
const ALPHABETH_STRING = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%abcd*";
9+
10+
var properties = {
11+
ALPHABETH_STRING: {value: ALPHABETH_STRING},
12+
ALPHABET: {value: ALPHABETH_STRING.split('').map(char => char.charCodeAt(0))},
13+
CHARACTER_ENCODINGS: {value: [
14+
0x114, 0x148, 0x144, 0x142, 0x128, 0x124, 0x122, 0x150, 0x112, 0x10A,
15+
0x1A8, 0x1A4, 0x1A2, 0x194, 0x192, 0x18A, 0x168, 0x164, 0x162, 0x134,
16+
0x11A, 0x158, 0x14C, 0x146, 0x12C, 0x116, 0x1B4, 0x1B2, 0x1AC, 0x1A6,
17+
0x196, 0x19A, 0x16C, 0x166, 0x136, 0x13A, 0x12E, 0x1D4, 0x1D2, 0x1CA,
18+
0x16E, 0x176, 0x1AE, 0x126, 0x1DA, 0x1D6, 0x132, 0x15E
19+
]},
20+
ASTERISK: {value: 0x15E},
21+
FORMAT: {value: "code_93", writeable: false}
22+
};
23+
24+
Code93Reader.prototype = Object.create(BarcodeReader.prototype, properties);
25+
Code93Reader.prototype.constructor = Code93Reader;
26+
27+
Code93Reader.prototype._toCounters = function(start, counter) {
28+
var self = this,
29+
numCounters = counter.length,
30+
end = self._row.length,
31+
isWhite = !self._row[start],
32+
i,
33+
counterPos = 0;
34+
35+
ArrayHelper.init(counter, 0);
36+
37+
for ( i = start; i < end; i++) {
38+
if (self._row[i] ^ isWhite) {
39+
counter[counterPos]++;
40+
} else {
41+
counterPos++;
42+
if (counterPos === numCounters) {
43+
break;
44+
} else {
45+
counter[counterPos] = 1;
46+
isWhite = !isWhite;
47+
}
48+
}
49+
}
50+
51+
return counter;
52+
};
53+
54+
Code93Reader.prototype._decode = function() {
55+
var self = this,
56+
counters = [0, 0, 0, 0, 0, 0],
57+
result = [],
58+
start = self._findStart(),
59+
decodedChar,
60+
lastStart,
61+
pattern,
62+
nextStart;
63+
64+
if (!start) {
65+
return null;
66+
}
67+
nextStart = self._nextSet(self._row, start.end);
68+
69+
do {
70+
counters = self._toCounters(nextStart, counters);
71+
pattern = self._toPattern(counters);
72+
if (pattern < 0) {
73+
return null;
74+
}
75+
decodedChar = self._patternToChar(pattern);
76+
if (decodedChar < 0){
77+
return null;
78+
}
79+
result.push(decodedChar);
80+
lastStart = nextStart;
81+
nextStart += ArrayHelper.sum(counters);
82+
nextStart = self._nextSet(self._row, nextStart);
83+
} while (decodedChar !== '*');
84+
result.pop();
85+
86+
if (!result.length) {
87+
return null;
88+
}
89+
90+
if (!self._verifyEnd(lastStart, nextStart, counters)) {
91+
return null;
92+
}
93+
94+
if (!self._verifyChecksums(result)) {
95+
return null;
96+
}
97+
98+
result = result.slice(0, result.length - 2);
99+
if ((result = self._decodeExtended(result)) === null) {
100+
return null;
101+
};
102+
103+
return {
104+
code: result.join(""),
105+
start: start.start,
106+
end: nextStart,
107+
startInfo: start,
108+
decodedCodes: result
109+
};
110+
};
111+
112+
Code93Reader.prototype._verifyEnd = function(lastStart, nextStart) {
113+
if (lastStart === nextStart || !this._row[nextStart]) {
114+
return false;
115+
}
116+
return true;
117+
};
118+
119+
Code93Reader.prototype._patternToChar = function(pattern) {
120+
var i,
121+
self = this;
122+
123+
for (i = 0; i < self.CHARACTER_ENCODINGS.length; i++) {
124+
if (self.CHARACTER_ENCODINGS[i] === pattern) {
125+
return String.fromCharCode(self.ALPHABET[i]);
126+
}
127+
}
128+
return -1;
129+
};
130+
131+
Code93Reader.prototype._toPattern = function(counters) {
132+
const numCounters = counters.length;
133+
let pattern = 0;
134+
let sum = 0;
135+
for (let i = 0; i < numCounters; i++) {
136+
sum += counters[i];
137+
}
138+
139+
for (let i = 0; i < numCounters; i++) {
140+
let normalized = Math.round(counters[i] * 9 / sum);
141+
if (normalized < 1 || normalized > 4) {
142+
return -1;
143+
}
144+
if ((i & 1) === 0) {
145+
for (let j = 0; j < normalized; j++) {
146+
pattern = (pattern << 1) | 1;
147+
}
148+
} else {
149+
pattern <<= normalized;
150+
}
151+
}
152+
153+
return pattern;
154+
};
155+
156+
Code93Reader.prototype._findStart = function() {
157+
var self = this,
158+
offset = self._nextSet(self._row),
159+
patternStart = offset,
160+
counter = [0, 0, 0, 0, 0, 0],
161+
counterPos = 0,
162+
isWhite = false,
163+
i,
164+
j,
165+
whiteSpaceMustStart;
166+
167+
for ( i = offset; i < self._row.length; i++) {
168+
if (self._row[i] ^ isWhite) {
169+
counter[counterPos]++;
170+
} else {
171+
if (counterPos === counter.length - 1) {
172+
// find start pattern
173+
if (self._toPattern(counter) === self.ASTERISK) {
174+
whiteSpaceMustStart = Math.floor(Math.max(0, patternStart - ((i - patternStart) / 4)));
175+
if (self._matchRange(whiteSpaceMustStart, patternStart, 0)) {
176+
return {
177+
start: patternStart,
178+
end: i
179+
};
180+
}
181+
}
182+
183+
patternStart += counter[0] + counter[1];
184+
for ( j = 0; j < 4; j++) {
185+
counter[j] = counter[j + 2];
186+
}
187+
counter[4] = 0;
188+
counter[5] = 0;
189+
counterPos--;
190+
} else {
191+
counterPos++;
192+
}
193+
counter[counterPos] = 1;
194+
isWhite = !isWhite;
195+
}
196+
}
197+
return null;
198+
};
199+
200+
Code93Reader.prototype._decodeExtended = function(charArray) {
201+
const length = charArray.length;
202+
const result = [];
203+
for (let i = 0; i < length; i++) {
204+
const char = charArray[i];
205+
if (char >= 'a' && char <= 'd') {
206+
if (i > (length - 2)) {
207+
return null;
208+
}
209+
const nextChar = charArray[++i];
210+
const nextCharCode = nextChar.charCodeAt(0);
211+
let decodedChar;
212+
switch (char) {
213+
case 'a':
214+
if (nextChar >= 'A' && nextChar <= 'Z') {
215+
decodedChar = String.fromCharCode(nextCharCode - 64);
216+
} else {
217+
return null;
218+
}
219+
break;
220+
case 'b':
221+
if (nextChar >= 'A' && nextChar <= 'E') {
222+
decodedChar = String.fromCharCode(nextCharCode - 38);
223+
} else if (nextChar >= 'F' && nextChar <= 'J') {
224+
decodedChar = String.fromCharCode(nextCharCode - 11);
225+
} else if (nextChar >= 'K' && nextChar <= 'O') {
226+
decodedChar = String.fromCharCode(nextCharCode + 16);
227+
} else if (nextChar >= 'P' && nextChar <= 'S') {
228+
decodedChar = String.fromCharCode(nextCharCode + 43);
229+
} else if (nextChar >= 'T' && nextChar <= 'Z') {
230+
decodedChar = String.fromCharCode(127);
231+
} else {
232+
return null;
233+
}
234+
break;
235+
case 'c':
236+
if (nextChar >= 'A' && nextChar <= 'O') {
237+
decodedChar = String.fromCharCode(nextCharCode - 32);
238+
} else if (nextChar == 'Z') {
239+
decodedChar = ':';
240+
} else {
241+
return null;
242+
}
243+
break;
244+
case 'd':
245+
if (nextChar >= 'A' && nextChar <= 'Z') {
246+
decodedChar = String.fromCharCode(nextCharCode + 32);
247+
} else {
248+
return null;
249+
}
250+
break;
251+
}
252+
result.push(decodedChar);
253+
} else {
254+
result.push(char);
255+
}
256+
}
257+
return result;
258+
};
259+
260+
Code93Reader.prototype._verifyChecksums = function(charArray) {
261+
return this._matchCheckChar(charArray, charArray.length - 2, 20)
262+
&& this._matchCheckChar(charArray, charArray.length - 1, 15);
263+
};
264+
265+
Code93Reader.prototype._matchCheckChar = function(charArray, index, maxWeight) {
266+
const arrayToCheck = charArray.slice(0, index);
267+
const length = arrayToCheck.length;
268+
const weightedSums = arrayToCheck.reduce((sum, char, i) => {
269+
const weight = (((i * -1) + (length - 1)) % maxWeight) + 1;
270+
const value = this.ALPHABET.indexOf(char.charCodeAt(0));
271+
return sum + (weight * value);
272+
}, 0);
273+
274+
const checkChar = this.ALPHABET[(weightedSums % 47)];
275+
return checkChar === charArray[index].charCodeAt(0);
276+
};
277+
278+
export default Code93Reader;

test/fixtures/code_93/image-001.jpg

143 KB
Loading

test/fixtures/code_93/image-002.jpg

160 KB
Loading

test/fixtures/code_93/image-003.jpg

151 KB
Loading

test/fixtures/code_93/image-004.jpg

128 KB
Loading

test/fixtures/code_93/image-005.jpg

154 KB
Loading

test/fixtures/code_93/image-006.jpg

111 KB
Loading

test/fixtures/code_93/image-007.jpg

117 KB
Loading

test/fixtures/code_93/image-008.jpg

121 KB
Loading

test/fixtures/code_93/image-009.jpg

144 KB
Loading

test/fixtures/code_93/image-010.jpg

140 KB
Loading

test/integration/integration.spec.js

+37
Original file line numberDiff line numberDiff line change
@@ -341,4 +341,41 @@ describe('decodeSingle', function () {
341341

342342
_runTestSet(testSet, config);
343343
});
344+
345+
describe("code_93", function() {
346+
var config = config = {
347+
inputStream: {
348+
size: 800,
349+
singleChannel: false
350+
},
351+
locator: {
352+
patchSize: "large",
353+
halfSample: true
354+
},
355+
numOfWorkers: 0,
356+
decoder: {
357+
readers: ["code_93_reader"]
358+
},
359+
locate: true,
360+
src: null
361+
},
362+
testSet = [
363+
{"name": "image-001.jpg", "result": "WIWV8ETQZ1"},
364+
{"name": "image-002.jpg", "result": "EH3C-%GU23RK3"},
365+
{"name": "image-003.jpg", "result": "O308SIHQOXN5SA/PJ"},
366+
{"name": "image-004.jpg", "result": "DG7Q$TV8JQ/EN"},
367+
{"name": "image-005.jpg", "result": "DG7Q$TV8JQ/EN"},
368+
{"name": "image-006.jpg", "result": "O308SIHQOXN5SA/PJ"},
369+
{"name": "image-007.jpg", "result": "VOFD1DB5A.1F6QU"},
370+
{"name": "image-008.jpg", "result": "WIWV8ETQZ1"},
371+
{"name": "image-009.jpg", "result": "4SO64P4X8 U4YUU1T-"},
372+
{"name": "image-010.jpg", "result": "4SO64P4X8 U4YUU1T-"}
373+
];
374+
375+
testSet.forEach(function(sample) {
376+
sample.format = "code_93";
377+
});
378+
379+
_runTestSet(testSet, config);
380+
});
344381
});

0 commit comments

Comments
 (0)