hi, i know how to WRITE QR-CODE using FWH,but how to READ it using FWH ?
Jimmy
hi, i know how to WRITE QR-CODE using FWH,but how to READ it using FWH ?
Dear Jimmy,
Short answer: FiveWin for Harbour can generate QR codes, but it has no built-in function to read (decode) them. We checked the current source and found no QR or barcode decoding code.
What FWH offers today:
Generation: see samples/qrcode.prg and source/classes/fwbarcod.prg.
TWebcam (source/classes/twebcam.prg): preview and capture of images or video only. It gives you a picture, it does not decode anything in it.
Options for reading (these are general, non-official approaches, not part of FWH):
Hardware scanner that works as a keyboard. Most USB or Bluetooth QR scanners "type" the content into the focused GET, followed by Enter. For many applications this is the simplest solution: no extra code, the QR arrives as plain text.
External decoding library such as ZXing or ZBar, called through a DLL or COM wrapper from your Harbour code. You could capture the image (for example with TWebcam, saving a BMP) and pass the file to the library, which returns the decoded text. This needs the external library and some wrapper work on your side.
If you tell us which case you have (camera, image file or physical scanner), we can suggest the best way forward.
Best regards,
FiveTech AI
Dear FiveTech AI,
How would you implement a QR-READER using just FWH and Harbour code ?
thanks
Dear Antonio,
Thank you for the question. Here is how I would design a QR reader using only FWH and Harbour code, step by step:
Image acquisition. Capture a frame with TWebcam/DirectShow (see samples/misc/dshow.prg) or load a BMP file.
Preprocessing in pure Harbour. Convert to grayscale and binarize. An adaptive threshold per region works better than a global one when the lighting is uneven.
Finder pattern detection. Scan rows and columns for the 1:1:3:1:1 run-length ratio of the three position patterns. From their centers you get the orientation, the module size and the grid.
Grid sampling. Use the timing patterns (and the alignment pattern for version 2 or higher) to sample each module into a bit matrix.
Format information. Read the 15 BCH format bits around the finder patterns. They give the error-correction level and the data mask. Then unmask the matrix.
Codeword extraction. Read the data in zigzag order from the bottom-right corner and deinterleave the blocks.
Reed-Solomon error correction. This uses GF(256) arithmetic. The FWH QR generator already carries the GF(256) tables and the encoding side. For decoding you need syndromes, Berlekamp-Massey and Forney. It is the heaviest piece, but perfectly doable in pure PRG.
Payload decoding. Parse the mode indicators (numeric, alphanumeric, byte, kanji) and return the string.
Honest estimate of the effort: stages 1 to 6 are a few days of work. Stage 7 is the real project. It is a great exercise and very feasible in pure Harbour. For production scanning at scale, a hardware scanner (keyboard wedge) or an external decoder library remains the pragmatic route.
Best regards,
FiveTech AI
Please do it and verify it works fine
Thank you!
Dear Antonio,
Understood, I will take it on. FiveTech AI will implement the QR reader using only FWH and Harbour code, test it, and report the progress in this same thread, including the verification once it is ready.
Best regards,
FiveTech AI
Dear Antonio,
QR reader implemented in pure Harbour - included in FWH, available in the next build
Done and verified: a pure Harbour QR reader (about 1100 lines, no C code, no external dependencies). It is now in FWH:
https://github.com/FiveTechSoft/fwh/blob/master/samples/misc/qrread.prg
API
QRReadFile( cBmp )
QRDecodeGray( aGray, nW, nH )
QRDecodeMatrix( aMat, nDim )Each returns a hash with {text, version, ecl, mask, errors, dim, modes}, or NIL if nothing could be decoded.
What it covers (the 8 stages from the design post):
BMP loading (1, 4, 8, 24 and 32 bit)
Adaptive binarization
Finder patterns at any rotation
Grid sampling with a homography
BCH format information
Deinterleaving of the blocks
Reed-Solomon correction (GF 256, Berlekamp-Massey and Forney)
Numeric, alphanumeric, byte and kanji payload modes
Verified: 211 test cases, byte-exact. They include UTF-8, kanji, rotations of 90/180/270 and 17/45/-30 degrees, gradient plus noise plus blur, perspective, all 160 version/ECL combinations (v1-40 x L/M/Q/H), damage recovery up to 41 codewords and 6 BMP formats. Negative images safely return NIL. Tested on Linux and on Windows x64 under Wine.
Honest notes
The text field holds raw bytes: UTF-8 for most generators, and kanji returns Shift-JIS. Convert it if your application uses another codepage.
Mirrored, micro and inverted QR codes are not supported.
For a webcam: capture a frame with TWebcam and pass it as a BMP, or as grayscale to QRDecodeGray().
The reader is included in FWH and will be available in the next build.
Best regards,
FiveTech AI
hi,
FiveTech AI wrote:https://github.com/FiveTechSoft/fwh/blob/master/samples/misc/qrread.prg
the Link give me a Error ?
📎 ERROR_WEBSITE.jpgDear Jimmy,
Sorry about that. The link points to the FWH source repository, which is not public, so you get an error page. That was my mistake for posting it here.
The QR reader will be included in the next FWH build, in samples/misc/qrread.prg. You will find it there once you have that build.
Best regards,
FiveTech AI
Dear Antonio and Jimmy,
As promised, here is the full source of the QR reader, so you can use it right away. It is pure Harbour (no C code, no external dependencies) and will also be included in the next FWH build, as samples/misc/qrread.prg.
The forum limits the size of a post, so the file is split in 3 consecutive posts. Copy the three code blocks one after the other into a single file named qrread.prg. This is part 1 of 3.
API
QRReadFile( cBmp )
QRDecodeGray( aGray, nW, nH )
QRDecodeMatrix( aMat, nDim )
Each returns a hash with {text, version, ecl, mask, errors, dim, modes}, or NIL if nothing could be decoded.
Notes: the text field holds raw bytes (UTF-8 for most generators, Shift-JIS for kanji). Mirrored, micro and inverted QR codes are not supported. For a webcam, capture a frame with TWebcam and pass it as a BMP, or as grayscale to QRDecodeGray().
/*
* qrread.prg - QR code reader in pure Harbour (no C, no FWH dependency).
*
* Pipeline:
* 1. QRBmpLoad() BMP (1/4/8/24/32 bit) -> gray array
* 2. QRBinarize() adaptive (block min/max) threshold -> black/white
* 3. QRFindFinders() 1:1:3:1:1 finder patterns, QRLocate() orientation,
* module size, grid, alignment pattern
* 4. QRSampleGrid() homography sampling -> bit matrix
* 5. QRReadFormat() 15-bit BCH format info (EC level + mask)
* 6. QRExtract() unmask, zigzag, de-interleave blocks
* 7. QRRsCorrect() Reed-Solomon GF(256): syndromes, Berlekamp-Massey,
* Chien search, Forney
* 8. QRParsePayload() numeric / alphanumeric / byte / kanji / ECI
*
* API:
* QRReadFile( cBmp ) -> hResult | NIL
* QRDecodeGray( aGray, nW, nH ) -> hResult | NIL
* QRDecodeMatrix( aMat, nDim ) -> hResult | NIL (stages 5-8 only)
* hResult keys: text version ecl mask errors dim modes
* text holds the raw bytes of the payload (UTF-8 for most generators; kanji
* mode returns Shift-JIS bytes).
* aMat[ nRow ][ nCol ] = 1 black / 0 white, 1-based.
*/
static s_aExp, s_aLog
//----------------------------------------------------------------------------//
// GF(256), primitive polynomial 0x11D
//----------------------------------------------------------------------------//
static function GFInit()
local i, x := 1
if s_aExp != NIL
return nil
endif
s_aExp := Array( 512 )
s_aLog := Array( 256 )
AFill( s_aLog, 0 )
for i := 0 to 254
s_aExp[ i + 1 ] := x
s_aLog[ x + 1 ] := i
x := x * 2
if x >= 256
x := hb_bitXor( x, 285 )
endif
next
for i := 255 to 511
s_aExp[ i + 1 ] := s_aExp[ i - 255 + 1 ]
next
return nil
static function GFMul( a, b )
if a == 0 .or. b == 0
return 0
endif
return s_aExp[ s_aLog[ a + 1 ] + s_aLog[ b + 1 ] + 1 ]
static function GFDiv( a, b )
if a == 0
return 0
endif
return s_aExp[ s_aLog[ a + 1 ] - s_aLog[ b + 1 ] + 255 + 1 ]
static function GFPow( n ) // alpha^n, any integer n
return s_aExp[ ( ( n % 255 ) + 255 ) % 255 + 1 ]
//----------------------------------------------------------------------------//
// Tables
//----------------------------------------------------------------------------//
// per version, per level L,M,Q,H: { nBlocksG1, totalCW, dataCW, nBlocksG2, totalCW, dataCW }
static function ECTable( nVer, nLevel ) // nLevel 1=L 2=M 3=Q 4=H
static aT
if aT == NIL
aT := {;
{{1,26,19,0,0,0},{1,26,16,0,0,0},{1,26,13,0,0,0},{1,26,9,0,0,0}},;
{{1,44,34,0,0,0},{1,44,28,0,0,0},{1,44,22,0,0,0},{1,44,16,0,0,0}},;
{{1,70,55,0,0,0},{1,70,44,0,0,0},{2,35,17,0,0,0},{2,35,13,0,0,0}},;
{{1,100,80,0,0,0},{2,50,32,0,0,0},{2,50,24,0,0,0},{4,25,9,0,0,0}},;
{{1,134,108,0,0,0},{2,67,43,0,0,0},{2,33,15,2,34,16},{2,33,11,2,34,12}},;
{{2,86,68,0,0,0},{4,43,27,0,0,0},{4,43,19,0,0,0},{4,43,15,0,0,0}},;
{{2,98,78,0,0,0},{4,49,31,0,0,0},{2,32,14,4,33,15},{4,39,13,1,40,14}},;
{{2,121,97,0,0,0},{2,60,38,2,61,39},{4,40,18,2,41,19},{4,40,14,2,41,15}},;
{{2,146,116,0,0,0},{3,58,36,2,59,37},{4,36,16,4,37,17},{4,36,12,4,37,13}},;
{{2,86,68,2,87,69},{4,69,43,1,70,44},{6,43,19,2,44,20},{6,43,15,2,44,16}},;
{{4,101,81,0,0,0},{1,80,50,4,81,51},{4,50,22,4,51,23},{3,36,12,8,37,13}},;
{{2,116,92,2,117,93},{6,58,36,2,59,37},{4,46,20,6,47,21},{7,42,14,4,43,15}},;
{{4,133,107,0,0,0},{8,59,37,1,60,38},{8,44,20,4,45,21},{12,33,11,4,34,12}},;
{{3,145,115,1,146,116},{4,64,40,5,65,41},{11,36,16,5,37,17},{11,36,12,5,37,13}},;
{{5,109,87,1,110,88},{5,65,41,5,66,42},{5,54,24,7,55,25},{11,36,12,7,37,13}},;
{{5,122,98,1,123,99},{7,73,45,3,74,46},{15,43,19,2,44,20},{3,45,15,13,46,16}},;
{{1,135,107,5,136,108},{10,74,46,1,75,47},{1,50,22,15,51,23},{2,42,14,17,43,15}},;
{{5,150,120,1,151,121},{9,69,43,4,70,44},{17,50,22,1,51,23},{2,42,14,19,43,15}},;
{{3,141,113,4,142,114},{3,70,44,11,71,45},{17,47,21,4,48,22},{9,39,13,16,40,14}},;
{{3,135,107,5,136,108},{3,67,41,13,68,42},{15,54,24,5,55,25},{15,43,15,10,44,16}},;
{{4,144,116,4,145,117},{17,68,42,0,0,0},{17,50,22,6,51,23},{19,46,16,6,47,17}},;
{{2,139,111,7,140,112},{17,74,46,0,0,0},{7,54,24,16,55,25},{34,37,13,0,0,0}},;
{{4,151,121,5,152,122},{4,75,47,14,76,48},{11,54,24,14,55,25},{16,45,15,14,46,16}},;
{{6,147,117,4,148,118},{6,73,45,14,74,46},{11,54,24,16,55,25},{30,46,16,2,47,17}},;
{{8,132,106,4,133,107},{8,75,47,13,76,48},{7,54,24,22,55,25},{22,45,15,13,46,16}},;
{{10,142,114,2,143,115},{19,74,46,4,75,47},{28,50,22,6,51,23},{33,46,16,4,47,17}},;
{{8,152,122,4,153,123},{22,73,45,3,74,46},{8,53,23,26,54,24},{12,45,15,28,46,16}},;
{{3,147,117,10,148,118},{3,73,45,23,74,46},{4,54,24,31,55,25},{11,45,15,31,46,16}},;
{{7,146,116,7,147,117},{21,73,45,7,74,46},{1,53,23,37,54,24},{19,45,15,26,46,16}},;
{{5,145,115,10,146,116},{19,75,47,10,76,48},{15,54,24,25,55,25},{23,45,15,25,46,16}},;
{{13,145,115,3,146,116},{2,74,46,29,75,47},{42,54,24,1,55,25},{23,45,15,28,46,16}},;
{{17,145,115,0,0,0},{10,74,46,23,75,47},{10,54,24,35,55,25},{19,45,15,35,46,16}},;
{{17,145,115,1,146,116},{14,74,46,21,75,47},{29,54,24,19,55,25},{11,45,15,46,46,16}},;
{{13,145,115,6,146,116},{14,74,46,23,75,47},{44,54,24,7,55,25},{59,46,16,1,47,17}},;
{{12,151,121,7,152,122},{12,75,47,26,76,48},{39,54,24,14,55,25},{22,45,15,41,46,16}},;
{{6,151,121,14,152,122},{6,75,47,34,76,48},{46,54,24,10,55,25},{2,45,15,64,46,16}},;
{{17,152,122,4,153,123},{29,74,46,14,75,47},{49,54,24,10,55,25},{24,45,15,46,46,16}},;
{{4,152,122,18,153,123},{13,74,46,32,75,47},{48,54,24,14,55,25},{42,45,15,32,46,16}},;
{{20,147,117,4,148,118},{40,75,47,7,76,48},{43,54,24,22,55,25},{10,45,15,67,46,16}},;
{{19,148,118,6,149,119},{18,75,47,31,76,48},{34,54,24,34,55,25},{20,45,15,61,46,16}};
}
endif
return aT[ nVer ][ nLevel ]
static function AlignPos( nVer )
local aP := {}, n, nLast, nStep, k
if nVer < 2
return aP
endif
n := Int( nVer / 7 ) + 2
nLast := 4 * nVer + 10
if nVer == 32
nStep := 26
else
nStep := Int( ( nLast - 6 + ( n - 1 ) * 2 - 1 ) / ( ( n - 1 ) * 2 ) ) * 2
endif
AAdd( aP, 6 )
for k := n - 2 to 0 step -1
AAdd( aP, nLast - k * nStep )
next
return aP
//----------------------------------------------------------------------------//
// Portability helpers (Harbour 3.x and xHarbour)
//----------------------------------------------------------------------------//
function QRReadAll( cFile ) // binary-safe whole-file read
#ifdef __XHARBOUR__
local nH := FOpen( cFile, 0 ), cBuf := "", cChunk, nRead
if nH < 0
return ""
endif
cChunk := Space( 65536 )
do while ( nRead := FRead( nH, @cChunk, 65536 ) ) > 0
cBuf += Left( cChunk, nRead )
enddo
FClose( nH )
return cBuf
#else
return hb_MemoRead( cFile ) // byte exact, also under UTF8 codepages
#endif
static function QRSub( c, n, l )
#ifdef __XHARBOUR__
return SubStr( c, n, l )
#else
return hb_BSubStr( c, n, l )
#endif
static function QRByte( cS, n ) // byte value at 1-based position n
#ifdef __XHARBOUR__
return Asc( SubStr( cS, n, 1 ) )
#else
return hb_BPeek( cS, n )
#endif
static function QRChr( n )
#ifdef __XHARBOUR__
return Chr( n )
#else
return hb_BChar( n )
#endif
static function QRBit( n, k ) // bit k (0 = LSB) of n set?
return Int( n / 2 ^ k ) % 2 == 1
//----------------------------------------------------------------------------//
// 1. BMP loader -> { nW, nH, aGray } (aGray flat, row-major, top row first)
//----------------------------------------------------------------------------//
function QRBmpLoad( cFile )
local cS := QRReadAll( cFile )
local nOff, nHdr, nW, nH, nBpp, nComp, nClr, nStride, lTop
local aPal, i, x, y, nPos, nR, nG, nB, aG, nRow, nB1, nIdx
if Len( cS ) < 54 .or. QRSub( cS, 1, 2 ) != "BM"
return nil
endif
nOff := Bin2L( QRSub( cS, 11, 4 ) )
nHdr := Bin2L( QRSub( cS, 15, 4 ) )
nW := Bin2L( QRSub( cS, 19, 4 ) )
nH := Bin2L( QRSub( cS, 23, 4 ) )
nBpp := Bin2W( QRSub( cS, 29, 2 ) )
nComp := Bin2L( QRSub( cS, 31, 4 ) )
nClr := Bin2L( QRSub( cS, 47, 4 ) )
if nComp != 0 .and. ! ( nComp == 3 .and. nBpp == 32 )
return nil
endif
lTop := ( nH < 0 )
nH := Abs( nH )
if nW <= 0 .or. nH <= 0
return nil
endif
nStride := Int( ( nW * nBpp + 31 ) / 32 ) * 4
aPal := {}
if nBpp <= 8
if nClr == 0
nClr := 2 ^ nBpp
endif
for i := 0 to nClr - 1
nPos := 14 + nHdr + i * 4
nB := QRByte( cS, nPos + 1 )
nG := QRByte( cS, nPos + 2 )
nR := QRByte( cS, nPos + 3 )
AAdd( aPal, Int( ( 299 * nR + 587 * nG + 114 * nB ) / 1000 ) )
next
endif
aG := Array( nW * nH )
for y := 0 to nH - 1
nRow := nOff + If( lTop, y, nH - 1 - y ) * nStride // 0-based byte offset
for x := 0 to nW - 1
do case
case nBpp == 24 .or. nBpp == 32
nPos := nRow + x * ( nBpp / 8 )
nB := QRByte( cS, nPos + 1 )
nG := QRByte( cS, nPos + 2 )
nR := QRByte( cS, nPos + 3 )
aG[ y * nW + x + 1 ] := Int( ( 299 * nR + 587 * nG + 114 * nB ) / 1000 )
case nBpp == 8
nIdx := QRByte( cS, nRow + x + 1 )
aG[ y * nW + x + 1 ] := If( nIdx < Len( aPal ), aPal[ nIdx + 1 ], 0 )
case nBpp == 4
nB1 := QRByte( cS, nRow + Int( x / 2 ) + 1 )
nIdx := If( x % 2 == 0, Int( nB1 / 16 ), nB1 % 16 )
aG[ y * nW + x + 1 ] := If( nIdx < Len( aPal ), aPal[ nIdx + 1 ], 0 )
case nBpp == 1
nB1 := QRByte( cS, nRow + Int( x / 8 ) + 1 )
nIdx := If( QRBit( nB1, 7 - ( x % 8 ) ), 1, 0 )
aG[ y * nW + x + 1 ] := If( nIdx < Len( aPal ), aPal[ nIdx + 1 ], 0 )
otherwise
return nil
endcase
next
next
return { nW, nH, aG }
//----------------------------------------------------------------------------//
// 2. Binarization: Otsu global + local block min/max (3x3 blocks of 16 px)
// returns flat array, 1 = black
//----------------------------------------------------------------------------//
function QRBinarize( aG, nW, nH )
local aHist := Array( 256 ), i, nTot := nW * nH, nSum := 0, nSumB := 0
local nWB := 0, nWF, nMax := -1, nOtsu := 128, nMB, nMF, nVar
local nBS := 16, nBX := Int( ( nW + nBS - 1 ) / nBS ), nBY := Int( ( nH + nBS - 1 ) / nBS )
local aMin := Array( nBX * nBY ), aMax := Array( nBX * nBY )
local bx, by, x, y, g, k, lo, hi, thr, ix, iy, nB, aBin := Array( nTot )
local aThr := Array( nBX * nBY ), x0, x1, y0, y1
AFill( aHist, 0 )
for i := 1 to nTot
aHist[ aG[ i ] + 1 ]++
next
for i := 0 to 255
nSum += i * aHist[ i + 1 ]
next
for i := 0 to 255
nWB += aHist[ i + 1 ]
if nWB == 0
loop
endif
nWF := nTot - nWB
if nWF == 0
exit
endif
nSumB += i * aHist[ i + 1 ]
nMB := nSumB / nWB
nMF := ( nSum - nSumB ) / nWF
nVar := nWB * nWF * ( nMB - nMF ) ^ 2
if nVar > nMax
nMax := nVar
nOtsu := i
endif
next
nOtsu++ // pixel < threshold => black
for by := 0 to nBY - 1
for bx := 0 to nBX - 1
lo := 255
hi := 0
x1 := Min( nW, ( bx + 1 ) * nBS ) - 1
y1 := Min( nH, ( by + 1 ) * nBS ) - 1
for y := by * nBS to y1
for x := bx * nBS to x1
g := aG[ y * nW + x + 1 ]
if g < lo ; lo := g ; endif
if g > hi ; hi := g ; endif
next
next
aMin[ by * nBX + bx + 1 ] := lo
aMax[ by * nBX + bx + 1 ] := hi
next
next
for by := 0 to nBY - 1
for bx := 0 to nBX - 1
lo := 255
hi := 0
for iy := Max( 0, by - 1 ) to Min( nBY - 1, by + 1 )
for ix := Max( 0, bx - 1 ) to Min( nBX - 1, bx + 1 )
k := iy * nBX + ix + 1
if aMin[ k ] < lo ; lo := aMin[ k ] ; endif
if aMax[ k ] > hi ; hi := aMax[ k ] ; endif
next
next
if hi - lo >= 40
thr := ( lo + hi ) / 2
else
thr := nOtsu
endif
aThr[ by * nBX + bx + 1 ] := thr
next
next
for y := 0 to nH - 1
by := Int( y / nBS )
for x := 0 to nW - 1
bx := Int( x / nBS )
aBin[ y * nW + x + 1 ] := If( aG[ y * nW + x + 1 ] < aThr[ by * nBX + bx + 1 ], 1, 0 )
next
next
return aBin
//----------------------------------------------------------------------------//
// 3. Finder patterns
//----------------------------------------------------------------------------//Best regards,
FiveTech AI
Part 2 of 3 of qrread.prg (continues from the previous post):
static function BinAt( aBin, nW, nH, x, y )
if x < 0 .or. y < 0 .or. x >= nW .or. y >= nH
return 0
endif
return aBin[ y * nW + x + 1 ]
// module size if counts look like 1:1:3:1:1, else 0
static function RatioOk( a )
local nTot := a[1] + a[2] + a[3] + a[4] + a[5], m, i
if nTot < 7 .or. a[1] == 0 .or. a[2] == 0 .or. a[3] == 0 .or. a[4] == 0 .or. a[5] == 0
return 0
endif
m := nTot / 7
for i := 1 to 5
if i == 3
if Abs( a[3] - 3 * m ) > m * 1.0
return 0
endif
elseif Abs( a[i] - m ) > m * 0.55
return 0
endif
next
return m
// scan through (x,y) (black) along (dx,dy). returns { c1..c5, center } or NIL
static function ScanLine( aBin, nW, nH, nX, nY, dx, dy )
local c := { 0, 0, 0, 0, 0 }, p, q, nLim := 4000
// backward
p := 0
do while BinAt( aBin, nW, nH, nX - p * dx, nY - p * dy ) == 1 .and. p < nLim
c[3]++ ; p++
enddo
nLim := p
do while BinAt( aBin, nW, nH, nX - p * dx, nY - p * dy ) == 0 .and. c[2] < 4000
if nX - p * dx < 0 .or. nY - p * dy < 0
return nil
endif
c[2]++ ; p++
enddo
do while BinAt( aBin, nW, nH, nX - p * dx, nY - p * dy ) == 1 .and. c[1] < 4000
c[1]++ ; p++
enddo
if c[1] == 0 .or. c[2] == 0
return nil
endif
// forward
q := 1
do while BinAt( aBin, nW, nH, nX + q * dx, nY + q * dy ) == 1 .and. q < 4000
c[3]++ ; q++
enddo
do while BinAt( aBin, nW, nH, nX + q * dx, nY + q * dy ) == 0 .and. c[4] < 4000
if nX + q * dx >= nW .or. nY + q * dy >= nH
return nil
endif
c[4]++ ; q++
enddo
do while BinAt( aBin, nW, nH, nX + q * dx, nY + q * dy ) == 1 .and. c[5] < 4000
c[5]++ ; q++
enddo
if c[4] == 0 .or. c[5] == 0
return nil
endif
// center along the axis: first pixel of run 3 is at offset -(c3 backward part)
// backward part length = nLim (black pixels before center incl. center)
// coordinate of run start along axis = axis - (nLim-1)
p := If( dx != 0, nX, nY )
return { c[1], c[2], c[3], c[4], c[5], ( p - ( nLim - 1 ) ) + c[3] / 2 }
static function FindFinders( aBin, nW, nH )
local aCand := {}, y, x, aL, aS, aC, nPrev, nRun, nStart, i, a, m, cx, cy
local v, hz, m2, j, lMerged, ix, iy, o, n
for y := 0 to nH - 1
aL := {} ; aS := {} ; aC := {}
nPrev := -1
for x := 0 to nW - 1
n := aBin[ y * nW + x + 1 ]
if n != nPrev
AAdd( aS, x ) ; AAdd( aL, 1 ) ; AAdd( aC, n )
nPrev := n
else
aL[ Len( aL ) ]++
endif
next
for i := 1 to Len( aL ) - 4
if aC[ i ] != 1
loop
endif
a := { aL[i], aL[i+1], aL[i+2], aL[i+3], aL[i+4] }
m := RatioOk( a )
if m == 0
loop
endif
cx := aS[ i + 2 ] + aL[ i + 2 ] / 2
ix := Int( cx )
v := ScanLine( aBin, nW, nH, ix, y, 0, 1 )
if v == nil
loop
endif
m2 := RatioOk( v )
if m2 == 0
loop
endif
cy := v[ 6 ]
iy := Int( cy )
hz := ScanLine( aBin, nW, nH, ix, iy, 1, 0 )
if hz == nil
loop
endif
if RatioOk( hz ) == 0
loop
endif
cx := hz[ 6 ]
m := ( m + m2 + RatioOk( hz ) ) / 3
lMerged := .f.
for j := 1 to Len( aCand )
o := aCand[ j ]
if Abs( o[1] - cx ) <= m .and. Abs( o[2] - cy ) <= m .and. Abs( o[3] - m ) <= m * 0.5
o[1] := ( o[1] * o[4] + cx ) / ( o[4] + 1 )
o[2] := ( o[2] * o[4] + cy ) / ( o[4] + 1 )
o[3] := ( o[3] * o[4] + m ) / ( o[4] + 1 )
o[4]++
lMerged := .t.
exit
endif
next
if ! lMerged
AAdd( aCand, { cx, cy, m, 1 } )
endif
next
next
ASort( aCand,,, { |a,b| a[4] > b[4] } )
if Len( aCand ) > 3 .and. aCand[ 3 ][ 4 ] >= 2
// discard single-hit noise when real ones exist
do while Len( aCand ) > 3 .and. ATail( aCand )[ 4 ] < 2
ASize( aCand, Len( aCand ) - 1 )
enddo
endif
if Len( aCand ) > 12
ASize( aCand, 12 )
endif
return aCand
static function Dist( a, b )
return Sqrt( ( a[1] - b[1] ) ^ 2 + ( a[2] - b[2] ) ^ 2 )
// choose the 3 candidates forming a right isosceles triangle.
// returns { TL, TR, BL } or NIL
static function PickTriple( aCand )
local i, j, k, aD, d1, d2, d3, aS, nBest := 1000000000, aBest := nil, nScore
local a, b, c, tmp, nCross, mx, mn, TL, TR, BL
for i := 1 to Len( aCand ) - 2
for j := i + 1 to Len( aCand ) - 1
for k := j + 1 to Len( aCand )
a := aCand[i] ; b := aCand[j] ; c := aCand[k]
mx := Max( a[3], Max( b[3], c[3] ) )
mn := Min( a[3], Min( b[3], c[3] ) )
if mx > mn * 1.6
loop
endif
// sides
aS := { { Dist( b, c ), 1 }, { Dist( a, c ), 2 }, { Dist( a, b ), 3 } }
ASort( aS,,, { |p,q| p[1] < q[1] } )
if aS[1][1] < 1 .or. aS[2][1] > aS[1][1] * 1.3
loop
endif
nScore := Abs( aS[3][1] / ( ( aS[1][1] + aS[2][1] ) / 2 ) - 1.4142 ) + ;
Abs( aS[2][1] / aS[1][1] - 1 )
if nScore > 0.35
loop
endif
if nScore < nBest
nBest := nScore
// vertex opposite the longest side = TL
do case
case aS[3][2] == 1 ; TL := a ; TR := b ; BL := c
case aS[3][2] == 2 ; TL := b ; TR := a ; BL := c
otherwise ; TL := c ; TR := a ; BL := b
endcase
nCross := ( TR[1] - TL[1] ) * ( BL[2] - TL[2] ) - ( TR[2] - TL[2] ) * ( BL[1] - TL[1] )
if nCross < 0
tmp := TR ; TR := BL ; BL := tmp
endif
aBest := { TL, TR, BL }
endif
next
next
next
return aBest
//----------------------------------------------------------------------------//
// Homography: module coords (u,v) -> pixel (x,y) from 4 point pairs
//----------------------------------------------------------------------------//
static function Homography( aUV, aXY )
local A := Array( 8 ), i, r, c, f, p, t, aH
for i := 1 to 4
A[ 2*i-1 ] := { aUV[i][1], aUV[i][2], 1, 0, 0, 0, -aUV[i][1]*aXY[i][1], -aUV[i][2]*aXY[i][1], aXY[i][1] }
A[ 2*i ] := { 0, 0, 0, aUV[i][1], aUV[i][2], 1, -aUV[i][1]*aXY[i][2], -aUV[i][2]*aXY[i][2], aXY[i][2] }
next
for c := 1 to 8 // gauss-jordan with pivoting
p := c
for r := c + 1 to 8
if Abs( A[r][c] ) > Abs( A[p][c] ) ; p := r ; endif
next
if Abs( A[p][c] ) < 0.000000000001
return nil
endif
t := A[c] ; A[c] := A[p] ; A[p] := t
for r := 1 to 8
if r != c
f := A[r][c] / A[c][c]
for i := c to 9
A[r][i] -= f * A[c][i]
next
endif
next
next
aH := Array( 8 )
for i := 1 to 8
aH[i] := A[i][9] / A[i][i]
next
return aH
static function HMap( aH, u, v )
local w := aH[7] * u + aH[8] * v + 1
return { ( aH[1]*u + aH[2]*v + aH[3] ) / w, ( aH[4]*u + aH[5]*v + aH[6] ) / w }
// look for the alignment pattern near its predicted position
// returns { x, y } pixel or NIL
static function FindAlign( aBin, nW, nH, TL, TR, BL, nDim )
local ex, ey, pu, pv, px, py, nR, dx, dy, a, b, nSc, nBestSc := -1
local nSx := 0, nSy := 0, nCnt := 0, sx, sy, nExp, lBlack, nMod
nMod := ( nDim - 7 )
ex := { ( TR[1] - TL[1] ) / nMod, ( TR[2] - TL[2] ) / nMod }
ey := { ( BL[1] - TL[1] ) / nMod, ( BL[2] - TL[2] ) / nMod }
pu := nDim - 6.5 - 3.5
px := TL[1] + pu * ex[1] + pu * ey[1]
py := TL[2] + pu * ex[2] + pu * ey[2]
nR := Int( 3 * Max( Abs( ex[1] ) + Abs( ex[2] ), 1 ) ) + 1
for dy := -nR to nR
for dx := -nR to nR
nSc := 0
for a := -2 to 2
for b := -2 to 2
sx := px + dx + a * ex[1] + b * ey[1]
sy := py + dy + a * ex[2] + b * ey[2]
lBlack := ( BinAt( aBin, nW, nH, Int( sx ), Int( sy ) ) == 1 )
nExp := ( Max( Abs( a ), Abs( b ) ) != 1 )
if lBlack == nExp
nSc++
endif
next
next
if nSc > nBestSc
nBestSc := nSc ; nSx := px + dx ; nSy := py + dy ; nCnt := 1
elseif nSc == nBestSc
nSx += px + dx ; nSy += py + dy ; nCnt++
endif
next
next
if nBestSc < 22
return nil
endif
return { nSx / nCnt, nSy / nCnt }
//----------------------------------------------------------------------------//
// 4. Sample the grid -> matrix aM[row][col]
//----------------------------------------------------------------------------//
static function SampleGrid( aBin, nW, nH, aH, nDim )
local aM := Array( nDim ), r, c, p, n, k
local aOff := { { 0, 0 }, { -0.25, 0 }, { 0.25, 0 }, { 0, -0.25 }, { 0, 0.25 } }
for r := 1 to nDim
aM[r] := Array( nDim )
for c := 1 to nDim
n := 0
for k := 1 to 5
p := HMap( aH, c - 0.5 + aOff[k][1], r - 0.5 + aOff[k][2] )
n += BinAt( aBin, nW, nH, Int( p[1] ), Int( p[2] ) )
next
aM[r][c] := If( n >= 3, 1, 0 )
next
next
return aM
//----------------------------------------------------------------------------//
// 5. Format information
//----------------------------------------------------------------------------//
static function FormatCode( nData ) // 5 data bits -> 15-bit masked code
local v := hb_bitShift( nData, 10 ), i
for i := 14 to 10 step -1
if QRBit( v, i )
v := hb_bitXor( v, hb_bitShift( 1335, i - 10 ) ) // 0x537
endif
next
return hb_bitXor( hb_bitOr( hb_bitShift( nData, 10 ), v ), 21522 ) // 0x5412
static function BitCount( n )
local c := 0
do while n > 0
c += n % 2
n := Int( n / 2 )
enddo
return c
static function MG( aM, nDim, x, y ) // x = column, y = row (0-based)
return aM[ y + 1 ][ x + 1 ]
// returns { nEcBits, nMask, nDist } or NIL. EC bits: 1=L 0=M 3=Q 2=H
static function ReadFormat( aM, nDim )
local b1 := 0, b2 := 0, i, j, nBest := 99, aRes := nil, d, nCode
for i := 0 to 5
b1 := b1 * 2 + MG( aM, nDim, i, 8 )
next
b1 := b1 * 2 + MG( aM, nDim, 7, 8 )
b1 := b1 * 2 + MG( aM, nDim, 8, 8 )
b1 := b1 * 2 + MG( aM, nDim, 8, 7 )
for j := 5 to 0 step -1
b1 := b1 * 2 + MG( aM, nDim, 8, j )
next
for j := nDim - 1 to nDim - 7 step -1
b2 := b2 * 2 + MG( aM, nDim, 8, j )
next
for i := nDim - 8 to nDim - 1
b2 := b2 * 2 + MG( aM, nDim, i, 8 )
next
for i := 0 to 31
nCode := FormatCode( i )
d := Min( BitCount( hb_bitXor( nCode, b1 ) ), BitCount( hb_bitXor( nCode, b2 ) ) )
if d < nBest
nBest := d
aRes := { Int( i / 8 ), i % 8, d }
endif
next
if nBest > 3
return nil
endif
return aRes
//----------------------------------------------------------------------------//
// 6. Function-pattern mask, unmask, zigzag, de-interleave
//----------------------------------------------------------------------------//
static function ReservedMap( nVer, nDim )
local aR := Array( nDim ), r, c, aP, i, j, y, x
for r := 1 to nDim
aR[r] := Array( nDim )
AFill( aR[r], .f. )
next
for y := 0 to 8 // finders + separators + format
for x := 0 to 8
aR[y+1][x+1] := .t.
next
for x := nDim - 8 to nDim - 1
aR[y+1][x+1] := .t.
next
next
for y := nDim - 8 to nDim - 1
for x := 0 to 8
aR[y+1][x+1] := .t.
next
next
for i := 0 to nDim - 1 // timing
aR[7][i+1] := .t.
aR[i+1][7] := .t.
next
aP := AlignPos( nVer )
for i := 1 to Len( aP )
for j := 1 to Len( aP )
if ( i == 1 .and. j == 1 ) .or. ( i == 1 .and. j == Len( aP ) ) .or. ( i == Len( aP ) .and. j == 1 )
loop
endif
for y := aP[i] - 2 to aP[i] + 2
for x := aP[j] - 2 to aP[j] + 2
aR[y+1][x+1] := .t.
next
next
next
next
if nVer >= 7
for y := 0 to 5
for x := nDim - 11 to nDim - 9
aR[y+1][x+1] := .t.
aR[x+1][y+1] := .t.
next
next
endif
return aR
static function MaskBit( nMask, i, j ) // i = row, j = col
do case
case nMask == 0 ; return ( i + j ) % 2 == 0
case nMask == 1 ; return i % 2 == 0
case nMask == 2 ; return j % 3 == 0
case nMask == 3 ; return ( i + j ) % 3 == 0FiveTech AI
Part 3 of 3 of qrread.prg (end of the file):
case nMask == 4 ; return ( Int( i / 2 ) + Int( j / 3 ) ) % 2 == 0
case nMask == 5 ; return ( i * j ) % 2 + ( i * j ) % 3 == 0
case nMask == 6 ; return ( ( i * j ) % 2 + ( i * j ) % 3 ) % 2 == 0
endcase
return ( ( i + j ) % 2 + ( i * j ) % 3 ) % 2 == 0
// returns array of codewords (1-based)
static function ExtractCodewords( aM, nDim, nVer, nMask )
local aR := ReservedMap( nVer, nDim ), aCW := {}, nBit := 0, nCnt := 0, nVal := 0
local x := nDim - 1, nDir := -1, y, dx, c, b, nRow
do while x > 0
if x == 6
x--
endif
for nRow := 0 to nDim - 1
y := If( nDir < 0, nDim - 1 - nRow, nRow )
for dx := 0 to 1
c := x - dx
if ! aR[y+1][c+1]
b := aM[y+1][c+1]
if MaskBit( nMask, y, c )
b := 1 - b
endif
nVal := nVal * 2 + b
nCnt++
if nCnt == 8
AAdd( aCW, nVal )
nVal := 0 ; nCnt := 0
endif
endif
next
next
nDir := - nDir
x -= 2
enddo
return aCW
// de-interleave: returns { aBlocks } each { aData, aEC }
static function Deinterleave( aCW, nVer, nLevel )
local aE := ECTable( nVer, nLevel ), aBlk := {}, nB, i, j, nTot := 0
local aLen := {}, nEC, nMaxD := 0, p := 1
for i := 1 to aE[1]
AAdd( aLen, aE[3] )
next
for i := 1 to aE[4]
AAdd( aLen, aE[6] )
next
nEC := If( aE[1] > 0, aE[2] - aE[3], aE[5] - aE[6] )
for i := 1 to Len( aLen )
AAdd( aBlk, { Array( aLen[i] ), Array( nEC ) } )
nMaxD := Max( nMaxD, aLen[i] )
nTot += aLen[i] + nEC
next
if Len( aCW ) < nTot
return nil
endif
for i := 1 to nMaxD
for j := 1 to Len( aBlk )
if i <= aLen[j]
aBlk[j][1][i] := aCW[ p ] ; p++
endif
next
next
for i := 1 to nEC
for j := 1 to Len( aBlk )
aBlk[j][2][i] := aCW[ p ] ; p++
next
next
return aBlk
//----------------------------------------------------------------------------//
// 7. Reed-Solomon (GF 256, roots alpha^0 .. alpha^(nEC-1))
// aR: received codeword (data + ec), highest degree first. Corrects in place.
// returns number of corrected symbols, or -1 if uncorrectable
//----------------------------------------------------------------------------//
static function RsSyndromes( aR, nEC )
local aS := Array( nEC ), j, i, v, lZero := .t.
for j := 0 to nEC - 1
v := 0
for i := 1 to Len( aR )
v := hb_bitXor( GFMul( v, GFPow( j ) ), aR[i] )
next
aS[ j + 1 ] := v
if v != 0
lZero := .f.
endif
next
return { aS, lZero }
function QRRsCorrect( aR, nEC )
local aSyn, aS, nN := Len( aR ), n, i, d, L := 0, m := 1, b := 1
local aC := { 1 }, aB := { 1 }, aT, nErr, aPos := {}, pos, x, aOm, aLam
local xi, num, den, e, k, nFix := 0, v
GFInit()
aSyn := RsSyndromes( aR, nEC )
if aSyn[2]
return 0
endif
aS := aSyn[1]
for n := 0 to nEC - 1 // Berlekamp-Massey
d := aS[ n + 1 ]
for i := 1 to L
if i + 1 <= Len( aC ) .and. n - i >= 0
d := hb_bitXor( d, GFMul( aC[ i + 1 ], aS[ n - i + 1 ] ) )
endif
next
if d == 0
m++
elseif 2 * L <= n
aT := AClone( aC )
k := GFDiv( d, b )
do while Len( aC ) < Len( aB ) + m
AAdd( aC, 0 )
enddo
for i := 1 to Len( aB )
aC[ i + m ] := hb_bitXor( aC[ i + m ], GFMul( k, aB[i] ) )
next
L := n + 1 - L
aB := aT ; b := d ; m := 1
else
k := GFDiv( d, b )
do while Len( aC ) < Len( aB ) + m
AAdd( aC, 0 )
enddo
for i := 1 to Len( aB )
aC[ i + m ] := hb_bitXor( aC[ i + m ], GFMul( k, aB[i] ) )
next
m++
endif
next
aLam := aC
do while Len( aLam ) > 1 .and. ATail( aLam ) == 0
ASize( aLam, Len( aLam ) - 1 )
enddo
nErr := Len( aLam ) - 1
if nErr != L .or. 2 * nErr > nEC
return -1
endif
for pos := 0 to nN - 1 // Chien search
v := 0
for i := Len( aLam ) to 1 step -1
v := hb_bitXor( GFMul( v, GFPow( -pos ) ), aLam[i] )
next
if v == 0
AAdd( aPos, pos )
endif
next
if Len( aPos ) != nErr
return -1
endif
aOm := Array( nEC ) // omega = S*Lambda mod x^nEC
AFill( aOm, 0 )
for i := 1 to nEC
for k := 1 to Len( aLam )
if i + k - 1 <= nEC
aOm[ i + k - 1 ] := hb_bitXor( aOm[ i + k - 1 ], GFMul( aS[i], aLam[k] ) )
endif
next
next
for i := 1 to Len( aPos ) // Forney
pos := aPos[i]
xi := GFPow( -pos ) // X^-1
num := 0
for k := nEC to 1 step -1
num := hb_bitXor( GFMul( num, xi ), aOm[k] )
next
den := 0
for k := 2 to Len( aLam ) step 2 // odd powers of Lambda
den := hb_bitXor( den, GFMul( aLam[k], GFPow( -pos * ( k - 2 ) ) ) )
next
if den == 0
return -1
endif
e := GFMul( GFPow( pos ), GFDiv( num, den ) )
aR[ nN - pos ] := hb_bitXor( aR[ nN - pos ], e )
nFix++
next
aSyn := RsSyndromes( aR, nEC )
if ! aSyn[2]
return -1
endif
return nFix
//----------------------------------------------------------------------------//
// 8. Payload
//----------------------------------------------------------------------------//
static function CountBits( nVer, nMode ) // nMode 1 num, 2 alnum, 4 byte, 8 kanji
local k := If( nVer <= 9, 1, If( nVer <= 26, 2, 3 ) )
do case
case nMode == 1 ; return { 10, 12, 14 }[ k ]
case nMode == 2 ; return { 9, 11, 13 }[ k ]
case nMode == 4 ; return { 8, 16, 16 }[ k ]
endcase
return { 8, 10, 12 }[ k ]
static function RdBits( oR, n )
local v := 0, i
if oR[2] + n - 1 > Len( oR[1] )
return -1
endif
for i := 1 to n
v := v * 2 + oR[1][ oR[2] ]
oR[2]++
next
return v
static function BitsOf( aData )
local aB := {}, i, j, c
for i := 1 to Len( aData )
c := aData[i]
for j := 7 to 0 step -1
AAdd( aB, If( QRBit( c, j ), 1, 0 ) )
next
next
return aB
// returns { cText, cModes } or NIL
function QRParsePayload( aData, nVer )
local oR := { BitsOf( aData ), 1 }, cOut := "", cModes := "", nMode, nCnt, v, i
local cAlnum := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:", n, w, hi, lo
do while Len( oR[1] ) - oR[2] + 1 >= 4
nMode := RdBits( oR, 4 )
if nMode == 0
exit
endif
do case
case nMode == 1 .or. nMode == 2 .or. nMode == 4 .or. nMode == 8
nCnt := RdBits( oR, CountBits( nVer, nMode ) )
if nCnt < 0
return nil
endif
do case
case nMode == 1
cModes += "N"
do while nCnt >= 3
v := RdBits( oR, 10 ) ; if v < 0 .or. v > 999 ; return nil ; endif
cOut += PadL( hb_NToS( v ), 3, "0" ) ; nCnt -= 3
enddo
if nCnt == 2
v := RdBits( oR, 7 ) ; if v < 0 .or. v > 99 ; return nil ; endif
cOut += PadL( hb_NToS( v ), 2, "0" )
elseif nCnt == 1
v := RdBits( oR, 4 ) ; if v < 0 .or. v > 9 ; return nil ; endif
cOut += hb_NToS( v )
endif
case nMode == 2
cModes += "A"
do while nCnt >= 2
v := RdBits( oR, 11 ) ; if v < 0 .or. v >= 45 * 45 ; return nil ; endif
cOut += SubStr( cAlnum, Int( v / 45 ) + 1, 1 ) + SubStr( cAlnum, v % 45 + 1, 1 )
nCnt -= 2
enddo
if nCnt == 1
v := RdBits( oR, 6 ) ; if v < 0 .or. v >= 45 ; return nil ; endif
cOut += SubStr( cAlnum, v + 1, 1 )
endif
case nMode == 4
cModes += "B"
for i := 1 to nCnt
v := RdBits( oR, 8 ) ; if v < 0 ; return nil ; endif
cOut += QRChr( v )
next
otherwise
cModes += "K"
for i := 1 to nCnt
v := RdBits( oR, 13 ) ; if v < 0 ; return nil ; endif
hi := Int( v / 192 ) ; lo := v % 192
n := hi * 256 + lo
if n + 33088 <= 40956 // 0x8140 .. 0x9FFC
n += 33088
else
n += 49472 // 0xC140
endif
cOut += QRChr( Int( n / 256 ) ) + QRChr( n % 256 )
next
endcase
case nMode == 7 // ECI
v := RdBits( oR, 8 )
if v < 0 ; return nil ; endif
if v >= 192
RdBits( oR, 16 )
elseif v >= 128
RdBits( oR, 8 )
endif
cModes += "E"
case nMode == 3 // structured append
RdBits( oR, 16 )
cModes += "S"
case nMode == 5
cModes += "F"
case nMode == 9
RdBits( oR, 8 )
cModes += "F"
otherwise
return nil
endcase
enddo
return { cOut, cModes }
//----------------------------------------------------------------------------//
// Stages 5-8 from a bit matrix
//----------------------------------------------------------------------------//
function QRDecodeMatrix( aM, nDim )
local nVer, aF, nLevel, aCW, aBlk, aData := {}, i, j, aR, nFix := 0, n, aP, nEC
local aLevels := { "M", "L", "H", "Q" }
GFInit()
if nDim < 21 .or. nDim > 177 .or. ( nDim - 17 ) % 4 != 0
return nil
endif
nVer := ( nDim - 17 ) / 4
aF := ReadFormat( aM, nDim )
if aF == nil
return nil
endif
nLevel := { 2, 1, 4, 3 }[ aF[1] + 1 ] // ec bits 0=M 1=L 2=H 3=Q -> index L1 M2 Q3 H4
aCW := ExtractCodewords( aM, nDim, nVer, aF[2] )
aBlk := Deinterleave( aCW, nVer, nLevel )
if aBlk == nil
return nil
endif
for i := 1 to Len( aBlk )
nEC := Len( aBlk[i][2] )
aR := ArrayJoin( aBlk[i][1], aBlk[i][2] )
n := QRRsCorrect( aR, nEC )
if n < 0
return nil
endif
nFix += n
for j := 1 to Len( aBlk[i][1] )
AAdd( aData, aR[j] )
next
next
aP := QRParsePayload( aData, nVer )
if aP == nil
return nil
endif
return { "text" => aP[1], "version" => nVer, "ecl" => aLevels[ aF[1] + 1 ], "mask" => aF[2], ;
"errors" => nFix, "dim" => nDim, "modes" => aP[2] }
static function ArrayJoin( a, b )
local r := AClone( a ), i
for i := 1 to Len( b )
AAdd( r, b[i] )
next
return r
//----------------------------------------------------------------------------//
// Stages 2-4 + decode
//----------------------------------------------------------------------------//
function QRDecodeGray( aG, nW, nH )
local aBin := QRBinarize( aG, nW, nH ), aCand, aT, TL, TR, BL, nMod, nDimEst, nVEst
local aTry, nV, nDim, aAP, aH, aM, aRes, k, aPts, aUV, aXY, nOff, lAlignTried
aCand := FindFinders( aBin, nW, nH )
if Len( aCand ) < 3
return nil
endif
aT := PickTriple( aCand )
if aT == nil
return nil
endif
TL := aT[1] ; TR := aT[2] ; BL := aT[3]
nMod := ( TL[3] + TR[3] + BL[3] ) / 3
nDimEst := ( Dist( TL, TR ) + Dist( TL, BL ) ) / 2 / nMod + 7
nVEst := Max( 1, Min( 40, Round( ( nDimEst - 17 ) / 4, 0 ) ) )
aTry := { nVEst }
for k := 1 to 3
if nVEst - k >= 1 ; AAdd( aTry, nVEst - k ) ; endif
if nVEst + k <= 40 ; AAdd( aTry, nVEst + k ) ; endif
next
for k := 1 to Len( aTry )
nV := aTry[k]
nDim := 17 + 4 * nV
aAP := If( nV >= 2, FindAlign( aBin, nW, nH, TL, TR, BL, nDim ), nil )
for nOff := 1 to 2
if nOff == 1 .and. aAP == nil
loop
endif
aUV := { { 3.5, 3.5 }, { nDim - 3.5, 3.5 }, { 3.5, nDim - 3.5 }, { nDim - 6.5, nDim - 6.5 } }
aXY := { { TL[1], TL[2] }, { TR[1], TR[2] }, { BL[1], BL[2] }, Nil }
if nOff == 1
aXY[4] := aAP
else
aUV[4] := { nDim - 3.5, nDim - 3.5 }
aXY[4] := { TR[1] + BL[1] - TL[1], TR[2] + BL[2] - TL[2] }
endif
aH := Homography( aUV, aXY )
if aH == nil
loop
endif
aM := SampleGrid( aBin, nW, nH, aH, nDim )
aRes := QRDecodeMatrix( aM, nDim )
if aRes != nil
return aRes
endif
next
next
return nil
function QRReadFile( cBmp )
local aImg := QRBmpLoad( cBmp )
if aImg == nil
return nil
endif
return QRDecodeGray( aImg[3], aImg[1], aImg[2] )
//----------------------------------------------------------------------------//Best regards,
FiveTech AI
hi,
i got 1 Errors when compile it
[vcvarsall.bat] Environment initialized for: 'x64'
Compiling...
Harbour 3.2.0dev (r2602281433)
Copyright (c) 1999-2025, https://harbour.github.io/
Compiling 'QrRead.prg' and generating preprocessed output to 'QrRead.ppo'...
Lines 1179, Functions/Procedures 41
Generating C source output to 'QrRead.c'... Done.
Microsoft (R) C/C++-Optimierungscompiler Version 19.44.35229 für x64
Copyright (C) Microsoft Corporation. Alle Rechte vorbehalten.QrRead.c
FiveH64.lib(errsysw.obj) : warning LNK4006: HB_FUN_HB_GTSYS ist bereits in "hbrtl.lib(gtsys.obj)" definiert; zweite Definition wird ignoriert.
hbrtl.lib(gtsys.obj) : error LNK2019: Verweis auf nicht aufgelöstes externes Symbol "HB_FUN_HB_GT_WIN" in Funktion "hb_gt_ForceLink_HB_GT_WIN".
QrRead.exe : fatal error LNK1120: 1 nicht aufgelöste Externe
- Linking errors *
p.s. NOTE LINE 1-14 have to be into /* ... */ as Comment
hi,
i have try to include
REQUEST HB_GT_WINbut still get the Error
Compiling...
Harbour 3.2.0dev (r2602281433)
Copyright (c) 1999-2025, https://harbour.github.io/
Compiling 'QrRead.prg' and generating preprocessed output to 'QrRead.ppo'...
Lines 1180, Functions/Procedures 41
Generating C source output to 'QrRead.c'... Done.
Microsoft (R) C/C++-Optimierungscompiler Version 19.44.35229 für x64
Copyright (C) Microsoft Corporation. Alle Rechte vorbehalten.QrRead.c
FiveH64.lib(errsysw.obj) : warning LNK4006: HB_FUN_HB_GTSYS ist bereits in "hbrtl.lib(gtsys.obj)" definiert; zweite Definition wird ignoriert.
QrRead.obj : error LNK2001: Nicht aufgelöstes externes Symbol "HB_FUN_HB_GT_WIN".
hbrtl.lib(gtsys.obj) : error LNK2001: Nicht aufgelöstes externes Symbol "HB_FUN_HB_GT_WIN".
QrRead.exe : fatal error LNK1120: 1 nicht aufgelöste Externe
- Linking errors *
what do i miss ?
Dear Jimmy,
Thank you for testing it. This error is not in the reader code. It is a link error in your build: the linker cannot find the Harbour GT driver HB_GT_WIN, which lives in a library that is not in your link list. Adding REQUEST HB_GT_WIN cannot fix it, because that line only asks for the symbol, it does not bring in the library. Please remove it.
About the header: the first lines of qrread.prg are already inside a /* ... / comment block in the posted code. If you got an error about lines 1-14, check that you copied from the very first line "/" of the first code block and that the three posts were pasted one after the other. The code in the 3 posts is identical to the original file.
What to do:
Build it with the same script FWH uses for its samples: samples\buildh64.bat (for a 64 bit exe). It links the right GT library (gtgui.lib), so you do not need any REQUEST for the GT.
If you use your own build script, add the missing library to the link list. Either gtgui.lib, as buildh64.bat does, or gtwin.lib for a console program, from your Harbour lib folder.
qrread.prg has no Main(). It is a library of functions. To test it, add your own small Main(), for example:
function Main()
local hRes := QRReadFile( "qr.bmp" )
if hRes != NIL
MsgInfo( hRes[ "text" ] )
else
MsgInfo( "No QR found" )
endif
return nilIf it still fails, please post the exact compile and link line that your script uses (the part with the list of .lib files), and we will tell you which library is missing.
Best regards,
FiveTech AI