1 /**
2 * libdmtx - Data Matrix Encoding/Decoding Library
3 * Copyright 2010 Mike Laughton. All rights reserved.
4 *
5 * See LICENSE file in the main project directory for full
6 * terms of use and distribution.
7 *
8 * Contact: Mike Laughton <mike@dragonflylogic.com>
9 *
10 * \file dmtxregion2.c
11 */
12
13 #include <string.h>
14 #include <math.h>
15 #include <assert.h>
16 #include "../../dmtx.h"
17 #include "multi_test.h"
18 #include "kiss_fftr.h"
19
20 #define RETURN_FAIL_IF(c) if(c) { return DmtxFail; }
21
22 /*
23 struct Deskew {
24 DmtxMatrix3 fit2raw;
25 DmtxMatrix3 raw2fit;
26 }
27
28 struct Timing {
29 double shift;
30 double period;
31 }
32
33 struct AlignmentGrid {
34 Deskew align;
35 Timing vTiming;
36 Timing hTiming;
37 }
38 */
39
40 /**
41 *
42 *
43 */
44 DmtxPassFail
dmtxRegion2FindNext(DmtxDecode2 * dec)45 dmtxRegion2FindNext(DmtxDecode2 *dec)
46 {
47 int i, j;
48 int phiDiff, phiDiffTmp;
49 DmtxBoolean regionFound;
50 DmtxPassFail passFail;
51 VanishPointSort vPoints;
52 DmtxOrient orient;
53
54 vPoints = dmtxFindVanishPoints(dec->hough->vanish);
55 dec->fn.vanishPointCallback(&vPoints, 0);
56
57 for(i = 0, regionFound = DmtxFalse; i < vPoints.count && regionFound == DmtxFalse; i++)
58 {
59 for(j = i + 1; j < vPoints.count; j++)
60 {
61 phiDiffTmp = abs(vPoints.bucket[i].phi - vPoints.bucket[j].phi);
62 phiDiff = (phiDiffTmp < 64) ? phiDiffTmp : 128 - phiDiffTmp;
63
64 /* Reject angle combinations that are too close */
65 if(phiDiff < 36)
66 continue;
67
68 /* Build oriented (but still untimed) grid from vanish points */
69 passFail = OrientRegion(&orient, vPoints.bucket[i], vPoints.bucket[j], dec);
70 if(passFail == DmtxFail)
71 continue;
72
73 /* Build timed grid from untimed grid and line hough */
74 /* align = CalibrateRegion(region, orient, lHough, &passFail);
75 if(passFail == DmtxFail)
76 continue;
77 */
78 /*
79 // timings = dmtxFindGridTiming(dec->hough->line, &vPoints);
80
81 err = dmtxBuildGridFromTimings(&grid, timings.timing[i], timings.timing[j]);
82 if(err == DmtxFail)
83 continue; // Keep trying
84
85 dec->fn.timingCallback(&timings.timing[i], &timings.timing[j], 1);
86
87 // Hack together raw2fitFull and fit2rawFull outside since we need app data
88 AddFullTransforms(&grid);
89
90 err = dmtxFindRegionWithinGrid(®ion, &grid, &houghCache, dec, fn);
91 regionFound = (err == DmtxPass) ? DmtxTrue : DmtxFalse;
92
93 if(regionFound == DmtxTrue) {
94 region.sizeIdx = dmtxGetSizeIdx(region.width, region.height);
95 if(region.sizeIdx >= DmtxSymbol10x10 && region.sizeIdx <= DmtxSymbol16x48)
96 dmtxDecodeSymbol(®ion, dec);
97 }
98
99 regionFound = DmtxTrue; // break out of outer loop
100 break; // break out of inner loop
101 */
102 }
103 }
104
105 return DmtxPass;
106 }
107
108 /**
109 *
110 *
111 */
112 DmtxPassFail
OrientRegion(DmtxOrient * orient,DmtxHoughBucket v0,DmtxHoughBucket v1,DmtxDecode2 * dec)113 OrientRegion(DmtxOrient *orient, DmtxHoughBucket v0, DmtxHoughBucket v1, DmtxDecode2 *dec)
114 {
115 DmtxRay2 v0a, v0b, v1a, v1b;
116
117 v0a = dec->corners[v0.d][v0.phi].lineA;
118 v0b = dec->corners[v0.d][v0.phi].lineB;
119 v1a = dec->corners[v1.d][v1.phi].lineA;
120 v1b = dec->corners[v1.d][v1.phi].lineB;
121 /*
122 RegionFromSides(v0a, v0b, v1a, v1b);
123 */
124 return DmtxPass;
125 }
126
127 /**
128 *
129 *
130 */
131 /*
132 DmtxPassFail
133 CalibrateRegion(DmtxOrient *orient)
134 {
135 input: orientation (transformation matrix)
136 hough line cache
137
138 output: timed alignment grid
139
140 description:
141 fourier transform receives evenly spaced samples to be taken in both directions (v and h)
142 -> tranforms into frequency space
143 -> major frequency emerges
144 determine shift as post-processing step (can't remember how I did it before at the moment)
145
146 need to interpolate values between true locations in line hough because
147 "evenly spaced" describes positions in the normalized fitted region whereas
148 the hough values are aligned along raw image coordinates
149
150 step size should be determined based on desired fft dimensions (maybe 64
151 steps?) ... check what we did for poc
152
153 steps:
154 for each step 0-63 upward along Y axis
155 xFit = 0
156 yFit = 1-63
157 xRaw,yRaw = VMult(fit2raw,xFit,yFit)
158 lineFit = (xRaw - 0, yRaw - 0) (duh)
159 phi = lineFit angle (something like atan2)
160 d = vector2Mag(lineFit)
161 interpolate hough[d][phi] and add to fft
162 that was easy
163
164 return DmtxPass;
165 }
166 */
167
168 /**
169 * Future structure of dmtxRegion2FindNext() (after getting orientation and timing working again)
170 *
171 * TODO:
172 * o Is there a way to name HoughGridPopulate() and HoughGridMerge() to show they are sister functions?
173 *
174 * hough lines, hough line maxima, and hough vanish can be calculated for entire image at once
175 * for each local:
176 * for each combination of vpoint pairs:
177 * step outward
178 * if step takes us into adjacent local
179 * add maxima from adjacent local into existing vpoint hough accumulator
180 * rescan for updated vpoints (using original centerline, adding only portion of region?)
181 * get new timing (?)
182 * continue stepping path where we left off?
183 *
184 * progress:
185 * level
186 * row
187 * col
188 * vanish point combo (?)
189 */
190 /*
191 dmtxRegion2FindNext(DmtxDecode2 *dec)
192 {
193 if(starting new level other than the first)
194 HoughGridMerge();
195
196 // for each new local
197 while(dec->localIdx < dec->localCount)
198 {
199 // each valid combination of vanish points
200 for(dec->vPointIdx < 28)
201 {
202 vPointI = xyz;
203 vPointJ = xyz;
204
205 while(perimeter condition not met)
206 {
207 stepOutward()
208
209 if(first step in new local, including first)
210 {
211 add new region to central vanish hough
212 update vanish points (shouldn't move too far)
213 record that new local was added to stats
214 redo timing
215 }
216
217 test perimeter for known strip patterns
218 if(perimeter says that barcode region is found) {
219 save current progress ("?")
220 return region;
221 }
222 }
223 }
224 }
225
226 return NULL;
227 }
228 */
229
230 /**
231 *
232 *
233 */
234 double
UncompactOffset(double compactedOffset,int phiIdx,int extent)235 UncompactOffset(double compactedOffset, int phiIdx, int extent)
236 {
237 double phiRad;
238 double scale;
239 double posMax, negMax;
240
241 phiRad = M_PI * phiIdx / 128.0;
242
243 if(phiIdx < 64) {
244 posMax = extent * (cos(phiRad) + sin(phiRad));
245 negMax = 0.0;
246 }
247 else {
248 posMax = extent * sin(phiRad);
249 negMax = extent * cos(phiRad);
250 }
251
252 assert(extent > 0);
253 scale = (posMax - negMax) / extent;
254
255 return (compactedOffset * scale) + negMax;
256 }
257
258 /**
259 *
260 *
261 */
262 void
AddToVanishPointSort(VanishPointSort * sort,DmtxHoughBucket bucket)263 AddToVanishPointSort(VanishPointSort *sort, DmtxHoughBucket bucket)
264 {
265 int i, startHere;
266 int phiDiff, phiDiffTmp;
267 DmtxBoolean isFull;
268 DmtxHoughBucket *lastBucket;
269
270 isFull = (sort->count == ANGLE_SORT_MAX_COUNT) ? DmtxTrue : DmtxFalse;
271 lastBucket = &(sort->bucket[ANGLE_SORT_MAX_COUNT - 1]);
272
273 /* Array is full and incoming bucket is already weakest */
274 if(isFull && bucket.val < lastBucket->val)
275 return;
276
277 startHere = DmtxUndefined;
278
279 /* If sort already has entry near this angle then either:
280 * a) Overwrite the old one without shifting if stronger
281 * b) Reject the new one completely if weaker
282 */
283 for(i = 0; i < sort->count; i++)
284 {
285 phiDiffTmp = abs(bucket.phi - sort->bucket[i].phi);
286 phiDiff = (phiDiffTmp < 64) ? phiDiffTmp : 128 - phiDiffTmp;
287
288 if(phiDiff < 10)
289 {
290 /* Similar angle is already represented with stronger magnitude */
291 if(bucket.val < sort->bucket[i].val)
292 {
293 return;
294 }
295 /* Found similar-but-weaker angle that will be overwritten */
296 else
297 {
298 sort->bucket[i] = bucket;
299 startHere = i;
300 break;
301 }
302 }
303 }
304
305 if(startHere == DmtxUndefined)
306 {
307 if(isFull)
308 *lastBucket = bucket;
309 else
310 sort->bucket[sort->count++] = bucket;
311
312 startHere = sort->count - 1;
313 }
314
315 /* Shift weak entries downward */
316 for(i = startHere; i > 0; i--)
317 {
318 if(bucket.val > sort->bucket[i-1].val)
319 {
320 sort->bucket[i] = sort->bucket[i-1];
321 sort->bucket[i-1] = bucket;
322 }
323 else
324 {
325 break;
326 }
327 }
328 }
329
330 /**
331 *
332 *
333 */
334 VanishPointSort
dmtxFindVanishPoints(DmtxHoughLocal * vHough)335 dmtxFindVanishPoints(DmtxHoughLocal *vHough)
336 {
337 DmtxHoughBucket bucket;
338 VanishPointSort sort;
339
340 memset(&sort, 0x00, sizeof(VanishPointSort));
341
342 for(bucket.phi = 0; bucket.phi < 128; bucket.phi++)
343 {
344 for(bucket.d = 0; bucket.d < 64; bucket.d++)
345 {
346 bucket.val = vHough->bucket[bucket.d][bucket.phi];
347 AddToVanishPointSort(&sort, bucket);
348 }
349 }
350
351 return sort;
352 }
353
354 /**
355 *
356 *
357 */
358 void
AddToMaximaSort(HoughMaximaSort * sort,int maximaMag)359 AddToMaximaSort(HoughMaximaSort *sort, int maximaMag)
360 {
361 int i;
362
363 /* If new entry would be weakest (or only) one in list, then append */
364 if(sort->count == 0 || maximaMag < sort->mag[sort->count - 1]) {
365 if(sort->count + 1 < MAXIMA_SORT_MAX_COUNT)
366 sort->mag[sort->count++] = maximaMag;
367 return;
368 }
369
370 /* Otherwise shift the weaker entries downward */
371 for(i = sort->count - 1; i >= 0; i--) {
372 if(maximaMag > sort->mag[i]) {
373 if(i + 1 < MAXIMA_SORT_MAX_COUNT)
374 sort->mag[i+1] = sort->mag[i];
375 sort->mag[i] = maximaMag;
376 }
377 }
378
379 if(sort->count < MAXIMA_SORT_MAX_COUNT)
380 sort->count++;
381 }
382
383 /**
384 * Return sum of top 8 maximum points (hmmm)
385 * btw, can we skip this step entirely?
386 */
387 DmtxHoughBucket
GetAngleSumAtPhi(DmtxHoughLocal * line,int phi)388 GetAngleSumAtPhi(DmtxHoughLocal *line, int phi)
389 {
390 int i, d;
391 int prev, here, next;
392 DmtxHoughBucket bucket;
393 HoughMaximaSort sort;
394
395 memset(&sort, 0x00, sizeof(HoughMaximaSort));
396
397 /* Handle last condition separately; one sided comparison */
398 prev = line->bucket[62][phi];
399 here = line->bucket[63][phi];
400 if(here > prev)
401 AddToMaximaSort(&sort, here);
402
403 /* Handle first condition separately; one sided comparison */
404 here = line->bucket[0][phi];
405 next = line->bucket[1][phi];
406 if(here > next)
407 AddToMaximaSort(&sort, here);
408
409 /* Handle remaining conditions as two sided comparisons */
410 for(d = 2; d < 64; d++)
411 {
412 prev = here;
413 here = next;
414 next = line->bucket[d][phi];
415
416 if(here > 0 && here >= prev && here >= next)
417 AddToMaximaSort(&sort, here);
418 }
419
420 bucket.d = 0;
421 bucket.phi = phi;
422 bucket.val = 0;
423 for(i = 0; i < 8; i++)
424 bucket.val += sort.mag[i];
425
426 return bucket;
427 }
428
429 /**
430 *
431 *
432 */
433 void
AddToTimingSort(DmtxTimingSort * sort,Timing timing)434 AddToTimingSort(DmtxTimingSort *sort, Timing timing)
435 {
436 int i;
437
438 if(timing.mag < 1.0) /* XXX or some minimum threshold */
439 return;
440
441 /* If new entry would be weakest (or only) one in list, then append */
442 if(sort->count == 0 || timing.mag < sort->timing[sort->count - 1].mag) {
443 if(sort->count + 1 < TIMING_SORT_MAX_COUNT)
444 sort->timing[sort->count++] = timing;
445 return;
446 }
447
448 /* Otherwise shift the weaker entries downward */
449 for(i = sort->count - 1; i >= 0; i--) {
450 if(timing.mag > sort->timing[i].mag) {
451 if(i + 1 < TIMING_SORT_MAX_COUNT)
452 sort->timing[i+1] = sort->timing[i];
453 sort->timing[i] = timing;
454 }
455 }
456
457 if(sort->count < TIMING_SORT_MAX_COUNT)
458 sort->count++;
459 }
460
461 /**
462 *
463 *
464 */
465 DmtxTimingSort
dmtxFindGridTiming(DmtxHoughLocal * line,VanishPointSort * vPoints)466 dmtxFindGridTiming(DmtxHoughLocal *line, VanishPointSort *vPoints)
467 {
468 int x, y, fitMag, fitMax, fitOff, attempts, iter;
469 int i, vSortIdx, phi;
470 kiss_fftr_cfg cfg = NULL;
471 kiss_fft_scalar rin[NFFT];
472 kiss_fft_cpx sout[NFFT/2+1];
473 kiss_fft_scalar mag[NFFT/2+1];
474 int maxIdx;
475 Timing timing;
476 DmtxTimingSort timings;
477
478 memset(&timings, 0x00, sizeof(DmtxTimingSort));
479
480 for(vSortIdx = 0; vSortIdx < vPoints->count; vSortIdx++) {
481
482 phi = vPoints->bucket[vSortIdx].phi;
483
484 /* Load FFT input array */
485 for(i = 0; i < NFFT; i++) {
486 rin[i] = (i < 64) ? line->bucket[i][phi] : 0;
487 }
488
489 /* Execute FFT */
490 memset(sout, 0x00, sizeof(kiss_fft_cpx) * (NFFT/2 + 1));
491 cfg = kiss_fftr_alloc(NFFT, 0, 0, 0);
492 kiss_fftr(cfg, rin, sout);
493 free(cfg);
494
495 /* Select best result */
496 maxIdx = NFFT/9-1;
497 for(i = 0; i < NFFT/9-1; i++)
498 mag[i] = 0.0;
499 for(i = NFFT/9-1; i < NFFT/2+1; i++) {
500 mag[i] = sout[i].r * sout[i].r + sout[i].i * sout[i].i;
501 if(mag[i] > mag[maxIdx])
502 maxIdx = i;
503 }
504
505 timing.phi = phi;
506 timing.period = NFFT / (double)maxIdx;
507 timing.mag = mag[maxIdx];
508
509 /* Find best offset */
510 fitOff = fitMax = 0;
511 attempts = (int)timing.period + 1;
512 for(x = 0; x < attempts; x++) {
513 fitMag = 0;
514 for(iter = 0; ; iter++) {
515 y = x + (int)(iter * timing.period);
516 if(y >= 64)
517 break;
518 fitMag += line->bucket[y][timing.phi];
519 }
520 if(x == 0 || fitMag > fitMax) {
521 fitMax = fitMag;
522 fitOff = x;
523 }
524 }
525 timing.shift = fitOff;
526
527 AddToTimingSort(&timings, timing);
528 }
529
530 return timings;
531 }
532
533 /**
534 *
535 *
536 */
537 DmtxRay2
HoughCompactToRay(int phi,double d)538 HoughCompactToRay(int phi, double d)
539 {
540 double phiRad;
541 double dScaled;
542 DmtxRay2 rStart, rLine;
543
544 memset(&rStart, 0x00, sizeof(DmtxRay2));
545 memset(&rLine, 0x00, sizeof(DmtxRay2));
546
547 rStart.p.X = rStart.p.Y = 0.0;
548
549 phiRad = phi * M_PI/128.0;
550
551 rStart.v.X = cos(phiRad);
552 rStart.v.Y = sin(phiRad);
553
554 rLine.v.X = -rStart.v.Y;
555 rLine.v.Y = rStart.v.X;
556
557 dScaled = UncompactOffset(d, phi, LOCAL_SIZE);
558
559 dmtxPointAlongRay2(&(rLine.p), &rStart, dScaled);
560
561 return rLine;
562 }
563
564 /**
565 *
566 *
567 *
568 */
569 DmtxPassFail
dmtxBuildGridFromTimings(AlignmentGrid * grid,Timing vp0,Timing vp1)570 dmtxBuildGridFromTimings(AlignmentGrid *grid, Timing vp0, Timing vp1)
571 {
572 RegionLines rl0, rl1, *flat, *steep;
573 DmtxVector2 p00, p10, p11, p01;
574 DmtxMatrix3 fit2raw, raw2fit, mScale;
575
576 /* (1) -- later compare all possible combinations for strongest pair */
577 rl0.timing = vp0;
578 rl0.gridCount = (int)((64.0 - vp0.shift)/vp0.period);
579 rl0.dA = vp0.shift;
580 rl0.dB = vp0.shift + vp0.period * rl0.gridCount; /* doesn't work but whatever */
581
582 rl1.timing = vp1;
583 rl1.gridCount = (int)((64.0 - vp1.shift)/vp1.period);
584 rl1.dA = vp1.shift;
585 rl1.dB = vp1.shift + vp1.period * rl1.gridCount; /* doesn't work but whatever */
586
587 /* flat[0] is the bottom flat line */
588 /* flat[1] is the top flat line */
589 /* steep[0] is the left steep line */
590 /* steep[1] is the right line */
591
592 /* Line with angle closest to horizontal is flatest */
593 if(abs(64 - rl0.timing.phi) < abs(64 - rl1.timing.phi)) {
594 flat = &rl0;
595 steep = &rl1;
596 }
597 else {
598 flat = &rl1;
599 steep = &rl0;
600 }
601
602 flat->line[0] = HoughCompactToRay(flat->timing.phi, flat->dA);
603 flat->line[1] = HoughCompactToRay(flat->timing.phi, flat->dB);
604
605 if(steep->timing.phi < 64) {
606 steep->line[0] = HoughCompactToRay(steep->timing.phi, steep->dA);
607 steep->line[1] = HoughCompactToRay(steep->timing.phi, steep->dB);
608 }
609 else {
610 steep->line[0] = HoughCompactToRay(steep->timing.phi, steep->dB);
611 steep->line[1] = HoughCompactToRay(steep->timing.phi, steep->dA);
612 }
613
614 RETURN_FAIL_IF(dmtxRay2Intersect(&p00, &(flat->line[0]), &(steep->line[0])) == DmtxFail);
615 RETURN_FAIL_IF(dmtxRay2Intersect(&p10, &(flat->line[0]), &(steep->line[1])) == DmtxFail);
616 RETURN_FAIL_IF(dmtxRay2Intersect(&p11, &(flat->line[1]), &(steep->line[1])) == DmtxFail);
617 RETURN_FAIL_IF(dmtxRay2Intersect(&p01, &(flat->line[1]), &(steep->line[0])) == DmtxFail);
618 RETURN_FAIL_IF(RegionUpdateCorners(fit2raw, raw2fit, p00, p10, p11, p01) == DmtxFail);
619
620 grid->rowCount = flat->gridCount;
621 grid->colCount = steep->gridCount;
622
623 /* raw2fit: Final transformation fits single origin module */
624 dmtxMatrix3Identity(mScale);
625 dmtxMatrix3Multiply(grid->raw2fitActive, raw2fit, mScale);
626
627 /* fit2raw: Abstract away display nuances of multi_test application */
628 dmtxMatrix3Identity(mScale);
629 dmtxMatrix3Multiply(grid->fit2rawActive, mScale, fit2raw);
630
631 return DmtxPass;
632 }
633
634 /**
635 *
636 *
637 */
638 StripStats
GenStripPatternStats(unsigned char * strip,int stripLength,int startState,int contrast)639 GenStripPatternStats(unsigned char *strip, int stripLength, int startState, int contrast)
640 {
641 int i, jumpAmount, jumpThreshold;
642 int finderLow, finderHigh, timingLow, timingHigh;
643 int surpriseCount, jumpCount, contrastSum;
644 int newState, currentState;
645 StripStats stats;
646
647 assert(startState == MODULE_HIGH || startState == MODULE_LOW);
648
649 jumpThreshold = (contrast*40)/100;
650 finderLow = finderHigh = timingLow = timingHigh = 0;
651 surpriseCount = jumpCount = contrastSum = 0;
652 currentState = startState;
653 memset(&stats, 0x00, sizeof(StripStats));
654
655 for(i = 0; i < stripLength; i++) {
656
657 if(i > 0) {
658 jumpAmount = strip[i] - strip[i-1];
659
660 /* Tally jump statistics if occurred */
661 if(abs(jumpAmount) > jumpThreshold) {
662 jumpCount++;
663 contrastSum += abs(jumpAmount);
664 newState = (jumpAmount > 0) ? MODULE_HIGH : MODULE_LOW;
665
666 /* Surprise! We jumped but landed in the same state */
667 if(newState == currentState)
668 surpriseCount++;
669 else
670 currentState = newState;
671 }
672 }
673
674 /* Increment appropriate finder pattern */
675 if(currentState == MODULE_HIGH)
676 finderHigh++;
677 else
678 finderLow++;
679
680 /* Increment appropriate timing pattern */
681 if(currentState ^ (i & 0x01))
682 timingHigh++;
683 else
684 timingLow++;
685 }
686
687 stats.jumps = jumpCount;
688 stats.surprises = surpriseCount;
689 stats.finderErrors = (finderHigh < finderLow) ? finderHigh : finderLow;
690 stats.timingErrors = (timingHigh < timingLow) ? timingHigh : timingLow;
691
692 if(jumpCount > 0) {
693 stats.contrast = (int)((double)contrastSum/jumpCount + 0.5);
694 stats.finderBest = (finderHigh > finderLow) ? MODULE_HIGH : MODULE_LOW;
695 stats.timingBest = (timingHigh > timingLow) ? MODULE_HIGH : MODULE_LOW;
696 }
697 else {
698 stats.contrast = 0;
699 stats.finderBest = MODULE_UNKNOWN;
700 stats.timingBest = MODULE_UNKNOWN;
701 }
702
703 return stats;
704 }
705
706 /**
707 *
708 *
709 */
710 DmtxPassFail
dmtxFindRegionWithinGrid(GridRegion * region,AlignmentGrid * grid,DmtxHoughLocal * line,DmtxDecode * dec,DmtxCallbacks * fn)711 dmtxFindRegionWithinGrid(GridRegion *region, AlignmentGrid *grid, DmtxHoughLocal *line, DmtxDecode *dec, DmtxCallbacks *fn)
712 {
713 int goodCount;
714 int finderSides;
715 DmtxDirection sideDir;
716 DmtxBarType innerType, outerType;
717 GridRegion regGrow;
718
719 memset(®Grow, 0x00, sizeof(GridRegion));
720
721 regGrow.grid = *grid; /* Capture local copy of grid for tweaking */
722 regGrow.x = regGrow.grid.colCount / 2;
723 regGrow.y = regGrow.grid.rowCount / 2;
724 regGrow.width = 2;
725 regGrow.height = 2;
726 regGrow.sizeIdx = DmtxUndefined;
727 regGrow.onColor = regGrow.offColor = 0;
728 regGrow.contrast = 20; /* low initial value */
729
730 /* Assume the starting region will be far enough away from any side that
731 * the expansion rules won't need to work at the very smallest sizes */
732
733 /* Grow region */
734 finderSides = DmtxDirNone;
735 for(goodCount = 0, sideDir = DmtxDirDown; goodCount < 4; sideDir = RotateCW(sideDir)) {
736 if(regGrow.width > 26 || regGrow.height > 26)
737 return DmtxFail;
738
739 innerType = TestSideForPattern(®Grow, dec->image, sideDir, 0);
740 outerType = TestSideForPattern(®Grow, dec->image, sideDir, 1);
741
742 /**
743 * Make smarter ... maybe:
744 * 1) Check that next-farther out strips are consistent (easy)
745 * 2) Check that the colors are all consistent (not as easy)
746 */
747
748 if(innerType == DmtxBarNone || outerType == DmtxBarNone) {
749 /* RegionExpand(®Grow, sideDir, line, fn); */
750 finderSides = DmtxDirNone;
751 goodCount = 0;
752 }
753 else {
754 if(innerType == DmtxBarFinder)
755 finderSides |= sideDir;
756
757 goodCount++;
758 }
759
760 /* fn->perimeterCallback(®Grow, sideDir, innerType); */
761 }
762
763 regGrow.finderSides = finderSides;
764 *region = regGrow;
765
766 return DmtxPass;
767 }
768
769 /**
770 *
771 *
772 */
dmtxReadModuleColor(DmtxImage * img,AlignmentGrid * grid,int symbolRow,int symbolCol,int colorPlane)773 int dmtxReadModuleColor(DmtxImage *img, AlignmentGrid *grid, int symbolRow,
774 int symbolCol, int colorPlane)
775 {
776 int err;
777 int i;
778 int color, colorTmp;
779 double sampleX[] = { 0.5, 0.4, 0.5, 0.6, 0.5 };
780 double sampleY[] = { 0.5, 0.5, 0.4, 0.5, 0.6 };
781 DmtxVector2 p;
782
783 color = 0;
784 for(i = 0; i < 5; i++) {
785
786 p.X = (1.0/grid->colCount) * (symbolCol + sampleX[i]);
787 p.Y = (1.0/grid->rowCount) * (symbolRow + sampleY[i]);
788
789 dmtxMatrix3VMultiplyBy(&p, grid->fit2rawFull);
790
791 /* Should use dmtxDecodeGetPixelValue() later to properly handle pixel skipping */
792 err = dmtxImageGetPixelValue(img, p.X, p.Y, colorPlane, &colorTmp);
793 if(err == DmtxFail)
794 return 0;
795
796 color += colorTmp;
797 }
798
799 return color/5;
800 }
801
802 /**
803 *
804 *
805 */
806 DmtxBarType
TestSideForPattern(GridRegion * region,DmtxImage * img,DmtxDirection side,int offset)807 TestSideForPattern(GridRegion *region, DmtxImage *img, DmtxDirection side, int offset)
808 {
809 int i;
810 int col, colBeg;
811 int row, rowBeg;
812 int extent;
813 unsigned char colorStrip[26] = { 0 };
814 StripStats *stats, statsHigh, statsLow;
815
816 assert(region->width <= 26 && region->height <= 26);
817 assert(side == DmtxDirUp || side == DmtxDirLeft ||
818 side == DmtxDirDown || side == DmtxDirRight);
819
820 /* Note opposite meaning (DmtxDirUp means "top", extent is horizontal) */
821 extent = (side & DmtxDirVertical) ? region->width : region->height;
822 if(extent < 3)
823 return DmtxBarNone;
824
825 switch(side) {
826 case DmtxDirUp:
827 colBeg = region->x;
828 rowBeg = (region->y + region->height - 1) + offset;
829 break;
830 case DmtxDirLeft:
831 colBeg = region->x - offset;
832 rowBeg = region->y;
833 break;
834 case DmtxDirDown:
835 colBeg = region->x;
836 rowBeg = region->y - offset;
837 break;
838 case DmtxDirRight:
839 colBeg = (region->x + region->width - 1) + offset;
840 rowBeg = region->y;
841 break;
842 default:
843 return DmtxUndefined;
844 }
845
846 /* Sample and hold colors at each module along both inner and outer edges */
847 col = colBeg;
848 row = rowBeg;
849
850 for(i = 0; i < extent; i++) {
851 colorStrip[i] = dmtxReadModuleColor(img, &(region->grid), row, col, 0);
852
853 if(side & DmtxDirVertical)
854 col++;
855 else
856 row++;
857 }
858
859 statsHigh = GenStripPatternStats(colorStrip, extent, MODULE_HIGH, region->contrast);
860 statsLow = GenStripPatternStats(colorStrip, extent, MODULE_LOW, region->contrast);
861 stats = (statsHigh.surprises > statsLow.surprises) ? &statsLow : &statsHigh;
862
863 if(stats->contrast > region->contrast) {
864 region->contrast = stats->contrast;
865 }
866
867 if(stats->finderErrors < stats->timingErrors) {
868 if(stats->finderErrors < 2) {
869 return DmtxBarFinder;
870 }
871 }
872 else if(stats->finderErrors > stats->timingErrors) {
873 if(stats->timingErrors < 2) {
874 return DmtxBarTiming;
875 }
876 }
877
878 return DmtxBarNone;
879 }
880
881 #define DmtxAlmostZero 0.000001
882
883 /**
884 *
885 *
886 */
887 DmtxPassFail
Vector2Norm(DmtxVector2 * v)888 Vector2Norm(DmtxVector2 *v)
889 {
890 double mag;
891
892 mag = dmtxVector2Mag(v);
893
894 if(mag <= DmtxAlmostZero)
895 return DmtxFail;
896
897 dmtxVector2ScaleBy(v, 1/mag);
898
899 return DmtxTrue;
900 }
901
902 /**
903 *
904 *
905 */
906 DmtxPassFail
RayFromPoints(DmtxRay2 * ray,DmtxVector2 p0,DmtxVector2 p1)907 RayFromPoints(DmtxRay2 *ray, DmtxVector2 p0, DmtxVector2 p1)
908 {
909 DmtxRay2 r;
910
911 r.tMin = 0.0;
912 r.tMax = 1.0;
913 r.p = p0;
914
915 dmtxVector2Sub(&r.v, &p1, &p0);
916 RETURN_FAIL_IF(Vector2Norm(&r.v) == DmtxFail);
917
918 *ray = r;
919
920 return DmtxPass;
921 }
922
923 /**
924 * Not a generic function. Notice that it uses fit2rawActive.
925 *
926 */
927 DmtxPassFail
dmtxRegionToSides(GridRegion * region,DmtxRegionSides * regionSides)928 dmtxRegionToSides(GridRegion *region, DmtxRegionSides *regionSides)
929 {
930 DmtxVector2 p00, p10, p11, p01;
931 DmtxRegionSides rs;
932
933 p00.X = p01.X = region->x * (1.0/region->grid.colCount);
934 p10.X = p11.X = (region->x + region->width) * (1.0/region->grid.colCount);
935 p00.Y = p10.Y = region->y * (1.0/region->grid.rowCount);
936 p01.Y = p11.Y = (region->y + region->height) * (1.0/region->grid.rowCount);
937
938 dmtxMatrix3VMultiplyBy(&p00, region->grid.fit2rawActive);
939 dmtxMatrix3VMultiplyBy(&p10, region->grid.fit2rawActive);
940 dmtxMatrix3VMultiplyBy(&p11, region->grid.fit2rawActive);
941 dmtxMatrix3VMultiplyBy(&p01, region->grid.fit2rawActive);
942
943 RETURN_FAIL_IF(RayFromPoints(&rs.bottom, p00, p10) == DmtxFail);
944 RETURN_FAIL_IF(RayFromPoints(&rs.right, p10, p11) == DmtxFail);
945 RETURN_FAIL_IF(RayFromPoints(&rs.top, p11, p01) == DmtxFail);
946 RETURN_FAIL_IF(RayFromPoints(&rs.left, p01, p00) == DmtxFail);
947
948 *regionSides = rs;
949
950 return DmtxPass;
951 }
952
953 /**
954 *
955 *
956 */
957 DmtxPassFail
dmtxRegionUpdateFromSides(GridRegion * region,DmtxRegionSides regionSides)958 dmtxRegionUpdateFromSides(GridRegion *region, DmtxRegionSides regionSides)
959 {
960 DmtxVector2 p00, p10, p11, p01;
961
962 RETURN_FAIL_IF(dmtxRay2Intersect(&p00, &(regionSides.left), &(regionSides.bottom)) == DmtxFail);
963 RETURN_FAIL_IF(dmtxRay2Intersect(&p10, &(regionSides.bottom), &(regionSides.right)) == DmtxFail);
964 RETURN_FAIL_IF(dmtxRay2Intersect(&p11, &(regionSides.right), &(regionSides.top)) == DmtxFail);
965 RETURN_FAIL_IF(dmtxRay2Intersect(&p01, &(regionSides.top), &(regionSides.left)) == DmtxFail);
966
967 RETURN_FAIL_IF(RegionUpdateCorners(region->grid.fit2rawActive, region->grid.raw2fitActive, p00, p10, p11, p01) == DmtxFail);
968 /* need to update fit2rawFull and raw2fitFull here too? */
969
970 return DmtxPass;
971 }
972
973 /**
974 *
975 *
976 */
977 DmtxPassFail
RegionExpand(GridRegion * region,DmtxDirection sideDir,DmtxHoughLocal * line,DmtxCallbacks * fn)978 RegionExpand(GridRegion *region, DmtxDirection sideDir, DmtxHoughLocal *line, DmtxCallbacks *fn)
979 {
980 DmtxRegionSides regionSides;
981 DmtxRay2 *sideRay;
982
983 switch(sideDir) {
984 case DmtxDirDown:
985 region->y--;
986 region->height++;
987 sideRay = &(regionSides.bottom);
988 break;
989 case DmtxDirUp:
990 region->height++;
991 sideRay = &(regionSides.top);
992 break;
993 case DmtxDirLeft:
994 region->x--;
995 region->width++;
996 sideRay = &(regionSides.left);
997 break;
998 case DmtxDirRight:
999 region->width++;
1000 sideRay = &(regionSides.right);
1001 break;
1002 default:
1003 return DmtxFail;
1004 }
1005
1006 return DmtxPass;
1007 }
1008
1009 /**
1010 *
1011 *
1012 */
1013 int
dmtxGetSizeIdx(int a,int b)1014 dmtxGetSizeIdx(int a, int b)
1015 {
1016 int i, rows, cols;
1017 const int totalSymbolSizes = 30; /* is there a better way to determine this? */
1018
1019 for(i = 0; i < totalSymbolSizes; i++) {
1020 rows = dmtxGetSymbolAttribute(DmtxSymAttribSymbolRows, i);
1021 cols = dmtxGetSymbolAttribute(DmtxSymAttribSymbolCols, i);
1022
1023 if((rows == a && cols == b) || (rows == b && cols == a))
1024 return i;
1025 }
1026
1027 return DmtxUndefined;
1028 }
1029
1030 /**
1031 *
1032 *
1033 */
1034 DmtxPassFail
RegionUpdateCorners(DmtxMatrix3 fit2raw,DmtxMatrix3 raw2fit,DmtxVector2 p00,DmtxVector2 p10,DmtxVector2 p11,DmtxVector2 p01)1035 RegionUpdateCorners(DmtxMatrix3 fit2raw, DmtxMatrix3 raw2fit, DmtxVector2 p00,
1036 DmtxVector2 p10, DmtxVector2 p11, DmtxVector2 p01)
1037 {
1038 /* double xMax, yMax; */
1039 double tx, ty, phi, shx, scx, scy, skx, sky;
1040 double dimOT, dimOR, dimTX, dimRX; /* , ratio; */
1041 DmtxVector2 vOT, vOR, vTX, vRX, vTmp;
1042 DmtxMatrix3 m, mtxy, mphi, mshx, mscx, mscy, mscxy, msky, mskx;
1043
1044 /* xMax = (double)(dmtxDecodeGetProp(dec, DmtxPropWidth) - 1);
1045 yMax = (double)(dmtxDecodeGetProp(dec, DmtxPropHeight) - 1);
1046
1047 if(p00.X < 0.0 || p00.Y < 0.0 || p00.X > xMax || p00.Y > yMax ||
1048 p01.X < 0.0 || p01.Y < 0.0 || p01.X > xMax || p01.Y > yMax ||
1049 p10.X < 0.0 || p10.Y < 0.0 || p10.X > xMax || p10.Y > yMax)
1050 return DmtxFail; */
1051 dimOT = dmtxVector2Mag(dmtxVector2Sub(&vOT, &p01, &p00)); /* XXX could use MagSquared() */
1052 dimOR = dmtxVector2Mag(dmtxVector2Sub(&vOR, &p10, &p00));
1053 dimTX = dmtxVector2Mag(dmtxVector2Sub(&vTX, &p11, &p01));
1054 dimRX = dmtxVector2Mag(dmtxVector2Sub(&vRX, &p11, &p10));
1055
1056 /* Verify that sides are reasonably long */
1057 /* if(dimOT <= 8.0 || dimOR <= 8.0 || dimTX <= 8.0 || dimRX <= 8.0)
1058 return DmtxFail; */
1059
1060 /* Verify that the 4 corners define a reasonably fat quadrilateral */
1061 /* ratio = dimOT / dimRX;
1062 if(ratio <= 0.5 || ratio >= 2.0)
1063 return DmtxFail; */
1064
1065 /* ratio = dimOR / dimTX;
1066 if(ratio <= 0.5 || ratio >= 2.0)
1067 return DmtxFail; */
1068
1069 /* Verify this is not a bowtie shape */
1070 /*
1071 if(dmtxVector2Cross(&vOR, &vRX) <= 0.0 ||
1072 dmtxVector2Cross(&vOT, &vTX) >= 0.0)
1073 return DmtxFail; */
1074
1075 /* if(RightAngleTrueness(p00, p10, p11, M_PI_2) <= dec->squareDevn)
1076 return DmtxFail;
1077 if(RightAngleTrueness(p10, p11, p01, M_PI_2) <= dec->squareDevn)
1078 return DmtxFail; */
1079
1080 /* Calculate values needed for transformations */
1081 tx = -1 * p00.X;
1082 ty = -1 * p00.Y;
1083 dmtxMatrix3Translate(mtxy, tx, ty);
1084
1085 phi = atan2(vOT.X, vOT.Y);
1086 dmtxMatrix3Rotate(mphi, phi);
1087 dmtxMatrix3Multiply(m, mtxy, mphi);
1088
1089 dmtxMatrix3VMultiply(&vTmp, &p10, m);
1090 shx = -vTmp.Y / vTmp.X;
1091 dmtxMatrix3Shear(mshx, 0.0, shx);
1092 dmtxMatrix3MultiplyBy(m, mshx);
1093
1094 scx = 1.0/vTmp.X;
1095 dmtxMatrix3Scale(mscx, scx, 1.0);
1096 dmtxMatrix3MultiplyBy(m, mscx);
1097
1098 dmtxMatrix3VMultiply(&vTmp, &p11, m);
1099 scy = 1.0/vTmp.Y;
1100 dmtxMatrix3Scale(mscy, 1.0, scy);
1101 dmtxMatrix3MultiplyBy(m, mscy);
1102
1103 dmtxMatrix3VMultiply(&vTmp, &p11, m);
1104 skx = vTmp.X;
1105 dmtxMatrix3LineSkewSide(mskx, 1.0, skx, 1.0);
1106 dmtxMatrix3MultiplyBy(m, mskx);
1107
1108 dmtxMatrix3VMultiply(&vTmp, &p01, m);
1109 sky = vTmp.Y;
1110 dmtxMatrix3LineSkewTop(msky, sky, 1.0, 1.0);
1111 dmtxMatrix3Multiply(raw2fit, m, msky);
1112
1113 /* Create inverse matrix by reverse (avoid straight matrix inversion) */
1114 dmtxMatrix3LineSkewTopInv(msky, sky, 1.0, 1.0);
1115 dmtxMatrix3LineSkewSideInv(mskx, 1.0, skx, 1.0);
1116 dmtxMatrix3Multiply(m, msky, mskx);
1117
1118 dmtxMatrix3Scale(mscxy, 1.0/scx, 1.0/scy);
1119 dmtxMatrix3MultiplyBy(m, mscxy);
1120
1121 dmtxMatrix3Shear(mshx, 0.0, -shx);
1122 dmtxMatrix3MultiplyBy(m, mshx);
1123
1124 dmtxMatrix3Rotate(mphi, -phi);
1125 dmtxMatrix3MultiplyBy(m, mphi);
1126
1127 dmtxMatrix3Translate(mtxy, -tx, -ty);
1128 dmtxMatrix3Multiply(fit2raw, m, mtxy);
1129
1130 return DmtxPass;
1131 }
1132
1133 /**
1134 *
1135 *
1136 */
1137 DmtxPassFail
dmtxDecodeSymbol(GridRegion * region,DmtxDecode * dec)1138 dmtxDecodeSymbol(GridRegion *region, DmtxDecode *dec)
1139 {
1140 static int prefix = 0;
1141 int onColor, offColor;
1142 DmtxVector2 p00, p10, p11, p01;
1143 DmtxRegion reg;
1144 DmtxMessage *msg;
1145
1146 /* Since we now hold 2 adjacent timing bars, find colors */
1147 RETURN_FAIL_IF(GetOnOffColors(region, dec, &onColor, &offColor) == DmtxFail);
1148
1149 p00.X = p01.X = region->x * (1.0/region->grid.colCount);
1150 p10.X = p11.X = (region->x + region->width) * (1.0/region->grid.colCount);
1151 p00.Y = p10.Y = region->y * (1.0/region->grid.rowCount);
1152 p01.Y = p11.Y = (region->y + region->height) * (1.0/region->grid.rowCount);
1153
1154 dmtxMatrix3VMultiplyBy(&p00, region->grid.fit2rawFull);
1155 dmtxMatrix3VMultiplyBy(&p10, region->grid.fit2rawFull);
1156 dmtxMatrix3VMultiplyBy(&p11, region->grid.fit2rawFull);
1157 dmtxMatrix3VMultiplyBy(&p01, region->grid.fit2rawFull);
1158
1159 /* Update DmtxRegion with detected corners */
1160 switch(region->finderSides) {
1161 case (DmtxDirLeft | DmtxDirDown):
1162 RETURN_FAIL_IF(dmtxRegionUpdateCorners(dec, ®, p00, p10, p11, p01) == DmtxFail);
1163 break;
1164 case (DmtxDirDown | DmtxDirRight):
1165 RETURN_FAIL_IF(dmtxRegionUpdateCorners(dec, ®, p10, p11, p01, p00) == DmtxFail);
1166 break;
1167 case (DmtxDirRight | DmtxDirUp):
1168 RETURN_FAIL_IF(dmtxRegionUpdateCorners(dec, ®, p11, p01, p00, p10) == DmtxFail);
1169 break;
1170 case (DmtxDirUp | DmtxDirLeft):
1171 RETURN_FAIL_IF(dmtxRegionUpdateCorners(dec, ®, p01, p00, p10, p11) == DmtxFail);
1172 break;
1173 default:
1174 return DmtxFail;
1175 }
1176
1177 /* Populate old-style region */
1178 reg.flowBegin.plane = 0; /* or 1, or 2 (0 = red, 1 = green, etc...) */
1179 reg.onColor = onColor;
1180 reg.offColor = offColor;
1181 reg.sizeIdx = region->sizeIdx;
1182 reg.symbolRows = dmtxGetSymbolAttribute(DmtxSymAttribSymbolRows, region->sizeIdx);
1183 reg.symbolCols = dmtxGetSymbolAttribute(DmtxSymAttribSymbolCols, region->sizeIdx);
1184 reg.mappingRows = dmtxGetSymbolAttribute(DmtxSymAttribMappingMatrixRows, region->sizeIdx);
1185 reg.mappingCols = dmtxGetSymbolAttribute(DmtxSymAttribMappingMatrixCols, region->sizeIdx);
1186
1187 msg = dmtxDecodeMatrixRegion(dec, ®, DmtxUndefined);
1188 if(msg == NULL)
1189 return DmtxFail;
1190
1191 fprintf(stdout, "%d: ", prefix);
1192 prefix = (prefix == 9) ? 0 : prefix + 1;
1193 fwrite(msg->output, sizeof(char), msg->outputIdx, stdout);
1194 fputc('\n', stdout);
1195 fflush(stdout);
1196
1197 return DmtxPass;
1198 }
1199
1200 /**
1201 *
1202 *
1203 */
1204 DmtxPassFail
GetOnOffColors(GridRegion * region,const DmtxDecode * dec,int * onColor,int * offColor)1205 GetOnOffColors(GridRegion *region, const DmtxDecode *dec, int *onColor, int *offColor)
1206 {
1207 int colBeg, rowBeg;
1208 DmtxDirection d0, d1;
1209 ColorTally t0, t1;
1210
1211 /* add assertion to guarantee 2 adjancent timing bars */
1212
1213 /* Start tally at intersection of timing bars */
1214 colBeg = (region->finderSides & DmtxDirLeft) ? region->x + region->width - 1 : region->x;
1215 rowBeg = (region->finderSides & DmtxDirDown) ? region->y + region->height - 1 : region->y;
1216
1217 switch(region->finderSides) {
1218 case (DmtxDirLeft | DmtxDirDown):
1219 d0 = DmtxDirLeft;
1220 d1 = DmtxDirDown;
1221 break;
1222 case (DmtxDirDown | DmtxDirRight):
1223 d0 = DmtxDirDown;
1224 d1 = DmtxDirRight;
1225 break;
1226 case (DmtxDirRight | DmtxDirUp):
1227 d0 = DmtxDirRight;
1228 d1 = DmtxDirUp;
1229 break;
1230 case (DmtxDirUp | DmtxDirLeft):
1231 d0 = DmtxDirUp;
1232 d1 = DmtxDirLeft;
1233 break;
1234 default:
1235 return DmtxFail;
1236 }
1237
1238 t0 = GetTimingColors(region, dec, colBeg, rowBeg, d0);
1239 t1 = GetTimingColors(region, dec, colBeg, rowBeg, d1);
1240
1241 if(t0.evnCount + t1.evnCount == 0 || t0.oddCount + t1.oddCount == 0)
1242 return DmtxFail;
1243
1244 *onColor = (t0.oddColor + t1.oddColor)/(t0.oddCount + t1.oddCount);
1245 *offColor = (t0.evnColor + t1.evnColor)/(t0.evnCount + t1.evnCount);
1246
1247 return DmtxPass;
1248 }
1249
1250 /**
1251 *
1252 *
1253 */
1254 ColorTally
GetTimingColors(GridRegion * region,const DmtxDecode * dec,int colBeg,int rowBeg,DmtxDirection dir)1255 GetTimingColors(GridRegion *region, const DmtxDecode *dec, int colBeg, int rowBeg,
1256 DmtxDirection dir)
1257 {
1258 int i, row, col, extent;
1259 int sample;
1260 ColorTally colors;
1261
1262 colors.evnColor = 0;
1263 colors.evnCount = 0;
1264
1265 colors.oddColor = 0;
1266 colors.oddCount = 0;
1267
1268 /* Note opposite meaning (DmtxDirUp means "top", extent is horizontal) */
1269 extent = (dir & DmtxDirVertical) ? region->width : region->height;
1270
1271 col = colBeg;
1272 row = rowBeg;
1273 for(i = 0; i < extent; i++) {
1274 sample = dmtxReadModuleColor(dec->image, &(region->grid), row, col, 0);
1275
1276 if(i & 0x01) {
1277 colors.oddColor += sample;
1278 colors.oddCount++;
1279 }
1280 else {
1281 colors.evnColor += sample;
1282 colors.evnCount++;
1283 }
1284
1285 switch(dir) {
1286 case DmtxDirUp:
1287 row++;
1288 break;
1289 case DmtxDirLeft:
1290 col--;
1291 break;
1292 case DmtxDirDown:
1293 row--;
1294 break;
1295 case DmtxDirRight:
1296 col++;
1297 break;
1298 default:
1299 return colors; /* XXX should be an error condition */
1300 }
1301 }
1302
1303 /* consider having PerimeterEdgeTest() call this function when ready? */
1304
1305 return colors;
1306 }
1307