diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,6 +15,7 @@ include(ECMAddQch) include(CMakePackageConfigHelpers) include(ECMSetupVersion) +include(ECMQtDeclareLoggingCategory) include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) @@ -42,6 +43,7 @@ add_subdirectory(src) if(BUILD_TESTING) find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Test Widgets) + add_subdirectory(autotests) add_subdirectory(tests) endif() diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/autotests/CMakeLists.txt @@ -0,0 +1,20 @@ +set(aztecbarcodetest_srcs + aztecbarcodetest.cpp + ../src/lib/aztecbarcode.cpp + ../src/lib/bitvector.cpp + ../src/lib/reedsolomon.cpp + ${CMAKE_CURRENT_BINARY_DIR}/../src/lib/prison_debug.cpp +) +qt5_add_resources(aztecbarcodetest_srcs aztec.qrc) + +add_executable(prison-aztecbarcodetest ${aztecbarcodetest_srcs}) +target_link_libraries(prison-aztecbarcodetest Qt5::Test KF5::Prison) +add_test(NAME prison-aztecbarcodetest COMMAND prison-aztecbarcodetest) + +add_executable(prison-reedsolomontest + reedsolomontest.cpp + ../src/lib/bitvector.cpp + ../src/lib/reedsolomon.cpp +) +target_link_libraries(prison-reedsolomontest Qt5::Test KF5::Prison) +add_test(NAME prison-reedsolomontest COMMAND prison-reedsolomontest) diff --git a/autotests/aztec-compact-data-0011.png b/autotests/aztec-compact-data-0011.png new file mode 100644 index 0000000000000000000000000000000000000000..9a8f68b2fabf25fffc231c45257b87cc87cca2da GIT binary patch literal 160 zc%17D@N?(olHy`uVBq!ia0vp^(jd&i1|)m0du=fx%oJ4GSo~4sFo&nHvC73wE|Y4tlq*fsOZ-0xP5Y6q za=7%K^i*5NRQDA+t+|mURjH?)Ynrz&k;)07bH<(Br`sB2pC($^~R{8A5e{tV(ZnWjzIG=EM<^B}2 zy1Czi4OU+aj{WSP>&qXJ8yPu2*D+ITNvn4=?{ zdhzB`hB-c};Y(#j19s{p#rhd)mbgE$D>=18>4tJqL(H*IrKvWzXB&O<+MsMzdOnTi zUDT(F^L_$5`TtjMa|{VLKA)6e=W^$JPT`s-AHP;lXF$RE3JDF*$MDOa@Cyz)BH(zoRH;jYpskFOt?qs0=H7-u%N0)5Qj>FVdQ&MBb@ E0E9ey+yDRo literal 0 Hc$@Jl=x=xpT0>g3bKI;3?4fjg(f*%lAR>3 zA1Ryvg6(t3jL$R9opQ*Zxk0pj>te+}hD`JSFpZT)|{ kn}NZGUlDs)6B4dQ{-3~V8e89F12l@k)78&qol`;+0C>bL8~^|S literal 0 Hc$@FVdQ&MBb@0B1`^mjD0& literal 0 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 YE-U-(DSpxQOY0VmJQm4~_~F9;0W+%=-T(jq literal 0 Hc$@=QxnI|O z&)$!Jeb;ZFb^pEfee1pV_Fr87AhpGR(g#l=JI_Pvo!V7g_?q6R5*>wf>H5p_*TEH?InxnsxR6dR%rtog6*+`23%-b=$J@ z*WvT%^*I>`PWpx1{`N#Ts^>zTfeiYxi)g`?vJ`nIX|1v8VwWzRUiw z;M14yGiy(~eAM-uUxQQK`qxobN!GPV<-hLTxvc(V_1mgAZEOmGy3fCp{;}=Wya%z% zgZ$S&x84516|1t_e%|-CUjL+99p(T3Z`kJNVqyZ=47B>UG!ij{P#Y!44Z9- wGZ{Sp;`usj`FqK^_wK)}ZuftWGg;LiGkV?@{8Do*Fi$afy85}Sb4q9e0DgtJo&W#< literal 0 Hc$@n(U@7lkQ{?`5f z=D+@6s(a)|=`{P|lDj9~q_6u?tGxZrdHdk-gGXKb`(B(Bw6$EM?*8RTO<8|;ScIXS z?aS|Pw^hsRtyali3KH*_Cv$PKa#fLw|GD(CH=C2rY)SH|IGuMvJz}GE@zMYDr^4kS zhRwfdf3L`(=IHmDUw(%t-u+(u{r%=k$O;yzKmS{`cUhX#?)j5<91Kqkv@0J>7t|Qw^BLnJCLoKX(wT~shX$iG{?tj&ac$p-iaxUUxVy#h?;+2 znD1%ZJe=_B-T5!a3V!gTSd&vf?dhA+-TZCyO9X1Kd0LzM|MJm>%E1h3JHKg9YWid0 zGW~PAQny%H_r+aYjuCiwt(E+D<+a|;zddj3qqzBBcrFW~o3rj_>d%(KXP)f$-Sc){ zFN-{6FNCbZc<h{_XErAc#OfH+)x33%B5trEy z`%BqY&HiDh%~!wv#Tn_t57|)^c$Y=Kt2=s*>*?J|>U-@nS~zhTQ(dx)`i{r$$HXz}v*+?vVrT6a9!6}kP+bkQ{Tx#%fG zr}FQ)F6O73>)$Wg9`Ro8`&+vewU_qFZA1?^i%m8!KYZ_czp47jwxlV$553#?sq4K_ z5_%r_Z9PNv?x(J~cm1T!&MjG=e{*XVx{_}bpT1*$s(t6vpA(ah?NQ#1>83xdR_Ukv U-riQ^2Nnbjp00i_>zopr0P7hehyVZp literal 0 Hc$@!zF8u5*DGH*tH6Ar3N{Pg!T5d3=dx32#8 z`Sl0w=l}bzy!(Fnm-p^z>+7$rdVTZn&-I&r|8oBq{Yk69a^Yl!5}_8qrVE}w*6g}% zTWVf=YURo+Csuvmo9ktqU1A#sl@+k!bW(q4d+YV4H@s&4(s{QZZ~R^R|JA(t6GQK> z7oE$3WKPe&0Bt)&prwwum%qRofQp>Mv z76tpoo`1LU;!0Z^<=x-EMO=h97p$lydHKt8yK<$Bv%jz0xjX*3ncwABeT0`1Z|>AP zY`){oyMI?IHMh_0{`4jD%$u*#C?>pJ{&L>0r`*|hzi}Hq-*rG7#rm6Xx2-%EUwT_^ z#@%396t{iY`PSr0@V{5{X5PJj`rY+-Q`^lbzMFsda@+F#|ALo!+O@}@D^>JG1kbd$ zr90ntUh<#+t>=!}--=WEJJ%p2Tl@}f+N-zv-KP0hk%^s#nRQIBY}h`tHm#`q%&GuiuX@^(IEV?(fy}x!?8s{~Y2FLo)LGD)FeT z>WNo>zx(!OulDA&{l{}_B=zAO|PuhqWJ7f-1Ok`*Z%j7KbP#@|NSmn+LCxzD!KD*Ue)*4bE=MA&fC2_ zbocVy&8T6saj)L)<-FT%H&%WBdZ*0lm9s{ab2gjvB|lb@IHTcc+5=&d%NXyExi36g3?kE{^q^TV<8~y=0M@Zso5yh3Z%j eVT5D{PH@B+ab2H_iw_{lE z9g}jz)om~n_t$K#`}5~*`Hpqp)8!xR7dW-T%-?T)OjV0oqfH)UsEr-4hU`nqH# zp3^|;aM4-e>xny`b-vyZ^Z7_=#Jb8yQYx#g&A%LvI_>((;B;w)&N<=h5BdMv6v|G^ zp0|VVw5fkg`_oxqoyDSsHN214>vQcow_|fmv(ELo(^*ee=u8q6mn@t(h>4#Jp3@S2 UyI$FD0wxRwPgg&ebxsLQ0BUdC4gdfE literal 0 Hc$@PGOdYg)Ik&hA#sTbF3Fg}u!|<;9+ZW-X*|N0F-!u>i%7~hGqa`~)H~fF_o-%P=FG^j zGYLg4`qt$ymhm5isonPXbN^Ja;n*Hgoze1L$;rRw`29$qW-Jdh7 z=|tjU2lmdEVO)BO?yVvet@PfPQP%wT(JD}EgQ{Tg_EOSeJ@k9I-J5KJ3+f6+HM8iv zTjx=`Gf+yM$!=0PRwDQf-}b~3Q??BW@=2YVRO%IsNgatG{nK^+lyR#?fbNJC8UM&( z`Z6YUb4W6&oYA_{z0)RqH-+xB9tu;|j}Lcg5Lxz<%h36$+96S381A{AofzwEwBnIG zsd%4y2fvWcK2`@ER!EyHHZ&Hl0Fq1P9kIFddlOPb0)&{R&71I*c911B|BI(CETl#!($ue z$cCTIbh7O>KD-7Hz%>s79~qQvofXGV z7*C{#G&SOj)217aXg~Xd&%p!m#?ZA(iHC*(Nu|@#pb&=uI{Px3Jx_eh*pwoM+N!%} zHg^D|Vqx*l)#Scl40}?swvAoR%TFX;Ect4;MPHUvy_bZx9%>+UugW5(4!@(K_xm;u zY7BJ4&D+;O)~pk4m6(d8UW0+A{7LzYTnq&UvIbpP1;!$-fFpp=RQ#sqaz`Ia03lx8 z#4D%1s`3?Jyi}(A{Z&zHB*u%I_uHAI07aHzxbF!-hTl;F-u|54CxE0o8Kq>wn3Hc1U1d8(`h@K0ue83$53fNEp4;|tR_bbNQD;8T+0+iZQqU-~O))(p21)+X69{ z-dF!h`QE;`YMc8DeE|r?QgN35A{BwYzpcByw+!DlvDW|znhA1*f^$FcNQr0gTzWr__huGg7K(vq z3v|e*45ma^cXR1~L5mVc~^3uv4-HSURrvs>YVOOQXIuho_2+@yp9F z@cdeJS}#3ip|2qSm^#S9+*`HEa`$~d$~gsp5^VE0{2(ySFu@ed{5E#2y43OSmDdR& z;8355Rx-sCf;UccGlooNll)_2j|-#;mAS(*a9pW2{hXr9BEy3$-?F!!`rF-bo5^U= zQGV@*K{V{*4X{{*FUhiM7#5_>sSUn6Lg?|kFN$%UQj}m+p48@iy{)7_O!|Uy?P;kZ zJxXmA9f)z3&zRPNz32q>DgJO*bS_jHWtNiOx|s>Tr_&$uT7r&`b|Dwzz^K1l=FpqgXKb=70m0ynI23oMb%qJA6J|93 zKCfdI$UZJPY+q(F9YrQ`0qiAKFgW?T#^zC^NO|o zUsnzObkLSsc0r_d%SyNvZ1=frAxJhjt{U4Q{M4^(a;cAfAj(WtpK|dVY5nqeNbnW( z;$whZ5vG$-oKaW)(V7gt`XIc~QA04$m}Ro9s~)ouzP9ex(i#__Ww>9kG9KAEEe_VA zh)tCz11HlVA3Ik~i}B6N)2@#leaPyTInG7&fF*<8IQ~rA!q}~-32)Ygg!r?jTbZ0l Q(-&nHc`hbQbT*CoA3*?a(EtDd literal 0 Hc$@ZSGLH8cPU+2i1gR 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 literal 0 Hc$@9QcwTz-omXB+KylB;V~*Kt4WHZmD)?M) zS8w@udP4h-6CK?>7{r;a*B-8~e;`@>Bky93NxtqRL2=2#iTH@`F)4S7XP$qy*45~d zlCmB?;_)bHmaKn&$}M8z21C$PU(~K`rn`HpQBqh=gys*RjdELivC&ucq&F1RO=bN+j%Da z^ED*`9*5dA{b$a-C>8u3R|c7RV#jl%`DdfM33&Vn&`eOMKHFTGJrOg4z{DK}Zc#VJ Ur!g&6z+}PT>FVdQ&MBb@0Q7AAlK=n! literal 0 Hc$@Rj>6yCHfZH*i{Wx1hGcUH_5Yc**4NqG%QdX8oBiAV6F%K3 uce*SdgdM5$(=+%?eyGfBqn@0gP(~Pgg&ebxsLQ E0HH<6OaK4? literal 0 Hc$@ + + aztec-full-grid.png + aztec-full-data-0011.png + aztec-full-data-0101.png + aztec-full-data-1001.png + aztec-full-data-1010.png + aztec-full-data-1111.png + aztec-full-mode-1111.png + aztec-full-mode-1234.png + aztec-full-mode-1234-rev.png + + aztec-compact-grid.png + aztec-compact-data-0011.png + aztec-compact-data-0101.png + aztec-compact-data-1001.png + aztec-compact-data-1010.png + aztec-compact-data-1111.png + aztec-compact-mode-1111.png + aztec-compact-mode-1234.png + aztec-compact-mode-1234-rev.png + + aztec-complete-compact1.png + aztec-complete-compact4.png + aztec-complete-full5.png + aztec-complete-big.png + + diff --git a/autotests/aztecbarcodetest.cpp b/autotests/aztecbarcodetest.cpp new file mode 100644 --- /dev/null +++ b/autotests/aztecbarcodetest.cpp @@ -0,0 +1,392 @@ +/* + Copyright (c) 2017 Volker Krause + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + +*/ + +#include "../src/lib/aztecbarcode.h" +#include "../src/lib/bitvector_p.h" + +#include +#include +#include + +Q_DECLARE_METATYPE(Prison::BitVector) + +using namespace Prison; + +class AztecBarcodeTest : public QObject +{ +Q_OBJECT +private Q_SLOTS: + void testAztecEncode_data() + { + QTest::addColumn("input"); + QTest::addColumn("output"); + + QTest::newRow("empty") << QByteArray() << BitVector(); + BitVector v; + v.appendMSB(12, 5); v.appendMSB(5, 5); v.appendMSB(6, 5); + QTest::newRow("all uppper") << QByteArray("KDE") << v; + v.clear(); + v.appendMSB(28, 5); v.appendMSB(12, 5); v.appendMSB(5, 5); v.appendMSB(6, 5); + QTest::newRow("all lower") << QByteArray("kde") << v; + v.clear(); + v.appendMSB(12, 5); v.appendMSB(28, 5); v.appendMSB(5, 5); v.appendMSB(6, 5); + QTest::newRow("upper -> lower latch") << QByteArray("Kde") << v; + v.clear(); + v.appendMSB(28, 5); v.appendMSB(2, 5); v.appendMSB(29, 5); v.appendMSB(30, 5); v.appendMSB(16, 5); v.appendMSB(16, 5); + QTest::newRow("lower -> punct latch") << QByteArray("a++") << v; + v.clear(); + v.appendMSB(30, 5); v.appendMSB(6, 4); v.appendMSB(4, 4); + QTest::newRow("digit") << QByteArray("42") << v; + v.clear(); + v.appendMSB(29, 5); v.appendMSB(30, 5); v.appendMSB(25, 5); v.appendMSB(24, 5); v.appendMSB(31, 5); v.appendMSB(30, 5); v.appendMSB(11, 4); v.appendMSB(2, 4); + QTest::newRow("punct -> digit latch") << QByteArray(">=90") << v; + v.clear(); + v.appendMSB(30, 5); v.appendMSB(10, 4); v.appendMSB(3, 4); v.appendMSB(14, 4); v.appendMSB(29, 5); v.appendMSB(30, 5); v.appendMSB(13, 5); v.appendMSB(14, 5); + QTest::newRow("digit -> punct latch") << QByteArray("81()") << v; + v.clear(); + v.appendMSB(29, 5); v.appendMSB(11, 5); v.appendMSB(8, 5); + QTest::newRow("mixed") << QByteArray("\n\a") << v; + v.clear(); + v.appendMSB(29, 5); v.appendMSB(30, 5); v.appendMSB(2, 5); v.appendMSB(2, 5); + QTest::newRow("CR LF") << QByteArray("\r\n\r\n") << v; + v.clear(); + v.appendMSB(31, 5); v.appendMSB(2, 5); v.appendMSB(128, 8); v.appendMSB(129, 8); + QTest::newRow("binary") << QByteArray("\x80\x81") << v; + v.clear(); + v.appendMSB(31, 5); v.appendMSB(2, 5); v.appendMSB(255, 8); v.appendMSB(254, 8); v.appendMSB(28, 5); v.appendMSB(3, 5); + QTest::newRow("binary/lower") << QByteArray("\xff\xfe" "b") << v; + v.clear(); + v.appendMSB(12, 5); v.appendMSB(0, 5); v.appendMSB(6, 5); + QTest::newRow("upper -> punct shift") << QByteArray("K!") << v; + v.clear(); + v.appendMSB(30, 5); v.appendMSB(9, 4); v.appendMSB(4, 4); v.appendMSB(15, 4); v.appendMSB(6, 5); v.appendMSB(5, 4); + QTest::newRow("digit -> upper shift") << QByteArray("72E3") << v; + v.clear(); + v.appendMSB(25, 5); v.appendMSB(1, 5); v.appendMSB(26, 5); + QTest::newRow("upper space") << QByteArray("X Y") << v; + v.clear(); + v.appendMSB(20, 5); v.appendMSB(0, 5); v.appendMSB(4, 5); v.appendMSB(23, 5); + QTest::newRow("upper punct double char shift") << QByteArray("S, V") << v; + v.clear(); + v.appendMSB(17, 5); v.appendMSB(0, 5); v.appendMSB(17, 5); v.appendMSB(18, 5); + QTest::newRow("upper ambigious punct shift") << QByteArray("P,Q") << v; + v.clear(); + v.appendMSB(30, 5); v.appendMSB(13, 4); v.appendMSB(7, 4); + QTest::newRow("digit ambigious punct latch") << QByteArray(".5") << v; + } + + void testAztecEncode() + { + QFETCH(QByteArray, input); + QFETCH(BitVector, output); + + AztecBarcode code; + const auto v = code.aztecEncode(input); + if (v != output) { + qDebug() << "Actual :" << v; + qDebug() << "Expected:" << output; + } + QCOMPARE(v, output); + } + + void testStuffAndPad_data() + { + QTest::addColumn("input"); + QTest::addColumn("output"); + QTest::addColumn("codeWordSize"); + + BitVector in, out; + QTest::newRow("emtpy") << in << out << 4; + in.appendMSB(0x3, 2); out.appendMSB(0xf, 4); + QTest::newRow("pad only") << in << out << 4; + in.clear(); out.clear(); + in.appendMSB(0xe0, 8); out.appendMSB(0xe13, 12); + QTest::newRow("stuff and pad") << in << out << 4; + in.clear(); out.clear(); + in.appendMSB(0, 6); out.appendMSB(0x11, 8); + QTest::newRow("stuff only") << in << out << 4; + } + + void testStuffAndPad() + { + QFETCH(BitVector, input); + QFETCH(BitVector, output); + QFETCH(int, codeWordSize); + AztecBarcode code; + const auto res= code.bitStuffAndPad(input, codeWordSize); + QCOMPARE(res.size(), output.size()); + if (res != output) { + qDebug() << "Actual :" << res; + qDebug() << "Expected:" << output; + } + QCOMPARE(res, output); + } + + void testFullGrid() + { + AztecBarcode code; + QImage img(151, 151, QImage::Format_ARGB32_Premultiplied); + img.fill(code.backgroundColor()); + code.paintFullGrid(&img); + + QImage ref(QStringLiteral(":/aztec-full-grid.png")); + ref = ref.convertToFormat(img.format()); + QCOMPARE(img, ref); + } + + void testComaptGrid() + { + AztecBarcode code; + QImage img(27, 27, QImage::Format_ARGB32_Premultiplied); + img.fill(code.backgroundColor()); + code.paintCompactGrid(&img); + img.save(QStringLiteral("aztec-compact-grid.png")); + + QImage ref(QStringLiteral(":/aztec-compact-grid.png")); + ref = ref.convertToFormat(img.format()); + QCOMPARE(img, ref); + } + + void testFullData_data() + { + QTest::addColumn("data"); + QTest::addColumn("refName"); + QTest::addColumn("layer"); + + BitVector v; + for (int i = 0; i < 1248; ++i) + v.appendLSB(0x9249, 16); + QTest::newRow("1001-31") << v << QStringLiteral("aztec-full-data-1001.png") << 31; + + v.clear(); + for (int i = 0; i < 1248 * 8; ++i) + v.appendLSB(0x2, 2); + QTest::newRow("0101-31") << v << QStringLiteral("aztec-full-data-0101.png") << 31; + + v.clear(); + for (int i = 0; i < 1248; ++i) + v.appendLSB(0xffff, 16); + QTest::newRow("1111-31") << v << QStringLiteral("aztec-full-data-1111.png") << 31; + + v.clear(); + for (int i = 0; i < 704 * 4; ++i) + v.appendLSB(0x1, 2); + QTest::newRow("1010-15") << v << QStringLiteral("aztec-full-data-1010.png") << 15; + + v.clear(); + for (int i = 0; i < 16; ++i) + v.appendLSB(0xCC, 8); + QTest::newRow("0011-0") << v << QStringLiteral("aztec-full-data-0011.png") << 0; + } + + void testFullData() + { + QFETCH(BitVector, data); + QFETCH(QString, refName); + QFETCH(int, layer); + + AztecBarcode code; + QImage img(151, 151, QImage::Format_ARGB32_Premultiplied); + img.fill(code.backgroundColor()); + code.paintFullData(&img, data, layer); + img.save(refName); + + QImage ref(QStringLiteral(":/") + refName); + ref = ref.convertToFormat(img.format()); + QCOMPARE(img, ref); + } + + void testFullModeMessage_data() + { + QTest::addColumn("data"); + QTest::addColumn("refName"); + + BitVector v; + for (int i = 0; i < 8; ++i) + v.appendMSB(i + 1, 5); + QTest::newRow("1234") << v << QStringLiteral("aztec-full-mode-1234.png"); + + v.clear(); + for (int i = 0; i < 8; ++i) + v.appendLSB(i + 1, 5); + QTest::newRow("1234-rev") << v << QStringLiteral("aztec-full-mode-1234-rev.png"); + + v.clear(); + for (int i = 0; i < 4; ++i) + v.appendMSB(0xffff, 10); + QTest::newRow("1111") << v << QStringLiteral("aztec-full-mode-1111.png"); + } + + void testFullModeMessage() + { + QFETCH(BitVector, data); + QFETCH(QString, refName); + + AztecBarcode code; + QImage img(151, 151, QImage::Format_ARGB32_Premultiplied); + img.fill(code.backgroundColor()); + code.paintFullModeMessage(&img, data); + img.save(refName); + + QImage ref(QStringLiteral(":/") + refName); + ref = ref.convertToFormat(img.format()); + QCOMPARE(img, ref); + } + + void testCompactData_data() + { + QTest::addColumn("data"); + QTest::addColumn("refName"); + QTest::addColumn("layer"); + + BitVector v; + for (int i = 0; i < 304; ++i) + v.appendLSB(0x9249, 16); + QTest::newRow("1001-3") << v << QStringLiteral("aztec-compact-data-1001.png") << 3; + + v.clear(); + for (int i = 0; i < 608 * 4; ++i) + v.appendLSB(0x2, 2); + QTest::newRow("0101-3") << v << QStringLiteral("aztec-compact-data-0101.png") << 3; + + v.clear(); + for (int i = 0; i < 304; ++i) + v.appendLSB(0xffff, 16); + QTest::newRow("1111-3") << v << QStringLiteral("aztec-compact-data-1111.png") << 3; + + v.clear(); + for (int i = 0; i < 102 * 4; ++i) + v.appendLSB(0x1, 2); + QTest::newRow("1010-2") << v << QStringLiteral("aztec-compact-data-1010.png") << 2; + + v.clear(); + for (int i = 0; i < 13; ++i) + v.appendLSB(0xCC, 8); + QTest::newRow("0011-0") << v << QStringLiteral("aztec-compact-data-0011.png") << 0; + } + + void testCompactData() + { + QFETCH(BitVector, data); + QFETCH(QString, refName); + QFETCH(int, layer); + + AztecBarcode code; + QImage img(27, 27, QImage::Format_ARGB32_Premultiplied); + img.fill(code.backgroundColor()); + code.paintCompactData(&img, data, layer); + img.save(refName); + + QImage ref(QStringLiteral(":/") + refName); + ref = ref.convertToFormat(img.format()); + QCOMPARE(img, ref); + } + + void testCompactModeMessage_data() + { + QTest::addColumn("data"); + QTest::addColumn("refName"); + + BitVector v; + for (int i = 0; i < 4; ++i) + v.appendMSB(i + 1, 7); + QTest::newRow("1234") << v << QStringLiteral("aztec-compact-mode-1234.png"); + + v.clear(); + for (int i = 0; i < 4; ++i) + v.appendLSB(i + 1, 7); + QTest::newRow("1234-rev") << v << QStringLiteral("aztec-compact-mode-1234-rev.png"); + + v.clear(); + for (int i = 0; i < 4; ++i) + v.appendMSB(0xffff, 7); + QTest::newRow("1111") << v << QStringLiteral("aztec-compact-mode-1111.png"); + } + + void testCompactModeMessage() + { + QFETCH(BitVector, data); + QFETCH(QString, refName); + + AztecBarcode code; + QImage img(151, 151, QImage::Format_ARGB32_Premultiplied); + img.fill(code.backgroundColor()); + code.paintCompactModeMessage(&img, data); + img.save(refName); + + QImage ref(QStringLiteral(":/") + refName); + ref = ref.convertToFormat(img.format()); + QCOMPARE(img, ref); + } + + void testCodeGen_data() + { + QTest::addColumn("input"); + QTest::addColumn("refName"); + + QTest::newRow("short compact") << QStringLiteral("KF5::Prison") << "aztec-complete-compact1.png"; + QTest::newRow("long compact") << QStringLiteral("KF5::Prison - the barcode generation library of KDE Frameworks 5!") << "aztec-complete-compact4.png"; + QTest::newRow("short full") << QStringLiteral("KF5::Prison - the MIT licensed free software barcode generation library of KDE Frameworks 5!") << "aztec-complete-full5.png"; + QTest::newRow("long full") << QString::fromLatin1( + "Permission is hereby granted, free of charge, to any person\n" + "obtaining a copy of this software and associated documentation\n" + "files (the \"Software\"), to deal in the Software without\n" + "restriction, including without limitation the rights to use,\n" + "copy, modify, merge, publish, distribute, sublicense, and/or sell\n" + "copies of the Software, and to permit persons to whom the\n" + "Software is furnished to do so, subject to the following\n" + "conditions:\n\n" + "The above copyright notice and this permission notice shall be\n" + "included in all copies or substantial portions of the Software.\n\n" + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n" + "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n" + "OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n" + "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n" + "HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n" + "WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n" + "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n" + "OTHER DEALINGS IN THE SOFTWARE.") << "aztec-complete-big.png"; + } + + void testCodeGen() + { + QFETCH(QString, input); + QFETCH(QString, refName); + + AztecBarcode code; + code.setData(input); + const auto img = code.paintImage({200, 200}); + img.save(refName); + + QImage ref(QStringLiteral(":/") + refName); + ref = ref.convertToFormat(img.format()); + QCOMPARE(img, ref); + } +}; + +QTEST_APPLESS_MAIN(AztecBarcodeTest) + +#include "aztecbarcodetest.moc" diff --git a/autotests/reedsolomontest.cpp b/autotests/reedsolomontest.cpp new file mode 100644 --- /dev/null +++ b/autotests/reedsolomontest.cpp @@ -0,0 +1,79 @@ +/* + Copyright (c) 2017 Volker Krause + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + +*/ + +#include "../src/lib/bitvector_p.h" +#include "../src/lib/reedsolomon_p.h" + +#include +#include +#include + +Q_DECLARE_METATYPE(Prison::BitVector) + +using namespace Prison; + +class ReedSolomonTest : public QObject +{ +Q_OBJECT +private Q_SLOTS: + void rsTest_data() + { + QTest::addColumn("poly"); + QTest::addColumn("symCount"); + QTest::addColumn("input"); + QTest::addColumn("output"); + + BitVector in, out; + out.appendMSB(0, 20); + QTest::newRow("empty") << (int)ReedSolomon::GF16 << 5 << in << out; + + in.clear(); out.clear(); + in.appendMSB(0x5c, 8); + out.appendMSB(7, 6); out.appendMSB(5, 7); out.appendMSB(0x4d, 7); + QTest::newRow("GF16") << (int)ReedSolomon::GF16 << 5 << in << out; + } + + void rsTest() + { + QFETCH(int, poly); + QFETCH(int, symCount); + QFETCH(BitVector, input); + QFETCH(BitVector, output); + + ReedSolomon rs(poly, symCount); + const auto res = rs.encode(input); + QCOMPARE(res.size(), output.size()); + if (res != output) { + qDebug() << "Actual :" << res; + qDebug() << "Expected:" << output; + } + QCOMPARE(res, output); + } +}; + +QTEST_APPLESS_MAIN(ReedSolomonTest) + +#include "reedsolomontest.moc" diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -2,13 +2,17 @@ set(prison_SRCS abstractbarcode.cpp + aztecbarcode.cpp + bitvector.cpp code39barcode.cpp code93barcode.cpp datamatrixbarcode.cpp qrcodebarcode.cpp prison.cpp + reedsolomon.cpp ) +ecm_qt_declare_logging_category(prison_SRCS HEADER prison_debug.h IDENTIFIER Prison::Log CATEGORY_NAME kf5.prison) add_library(KF5Prison ${prison_SRCS}) generate_export_header(KF5Prison BASE_NAME Prison) diff --git a/src/lib/aztecbarcode.h b/src/lib/aztecbarcode.h new file mode 100644 --- /dev/null +++ b/src/lib/aztecbarcode.h @@ -0,0 +1,67 @@ +/* + Copyright (c) 2017 Volker Krause + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + +*/ + +#ifndef PRISON_AZTECBARCODE_H +#define PRISON_AZTECBARCODE_H + +#include "abstractbarcode.h" + +class AztecBarcodeTest; + +namespace Prison { + +class BitVector; + +/** Aztec code generator. */ +class AztecBarcode : public AbstractBarcode +{ +public: + AztecBarcode(); + ~AztecBarcode(); + +protected: + QImage paintImage(const QSizeF& size) override; + +private: + friend class ::AztecBarcodeTest; + + BitVector aztecEncode(const QByteArray &data) const; + BitVector bitStuffAndPad(const BitVector &input, int codeWordSize) const; + + void paintFullGrid(QImage *img) const; + void paintFullData(QImage *img, const BitVector &data, int startLayer) const; + void paintFullModeMessage(QImage *img, const BitVector &modeData) const; + QImage cropAndScaleFull(QImage *img, int layerCount, int size); + + void paintCompactGrid(QImage *img) const; + void paintCompactData(QImage *img, const BitVector &data, int startLayer) const; + void paintCompactModeMessage(QImage *img, const BitVector &modeData) const; + QImage cropAndScaleCompact(QImage *img, int layerCount, int size); +}; + +} + +#endif // PRISON_AZTECCODE_H diff --git a/src/lib/aztecbarcode.cpp b/src/lib/aztecbarcode.cpp new file mode 100644 --- /dev/null +++ b/src/lib/aztecbarcode.cpp @@ -0,0 +1,775 @@ +/* + Copyright (c) 2017 Volker Krause + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + +*/ + +#include "aztecbarcode.h" +#include "bitvector_p.h" +#include "reedsolomon_p.h" +#include "prison_debug.h" + +#include +#include +#include + +#include +#include + +// see https://en.wikipedia.org/wiki/Aztec_Code for encoding tables, magic numbers, etc + +using namespace Prison; + +enum { + FullMaxSize = 151, + FullRadius = 74, + FullGridInterval = 16, + FullModeMessageSize = 40, + FullLayerCount = 32, + + CompactMaxSize = 27, + CompactRadius = 13, + CompactModeMessageSize = 28, + CompactLayerCount = 4 +}; + +AztecBarcode::AztecBarcode() = default; +AztecBarcode::~AztecBarcode() = default; + +// encoding properties depending on layer count +struct aztec_layer_property_t { + uint8_t layer; + uint8_t codeWordSize; + uint16_t gf; +}; + +static const aztec_layer_property_t aztec_layer_properties[] = { + { 2, 6, ReedSolomon::GF64 }, + { 8, 8, ReedSolomon::GF256 }, + { 22, 10, ReedSolomon::GF1024 }, + { 32, 12, ReedSolomon::GF4096 } +}; + +// amounts of bits in an Aztec code depending on layer count +static int aztecCompactDataBits(int layer) +{ + return (88 + 16 * (layer + 1)) * (layer + 1); +} + +static int aztecFullDataBits(int layer) +{ + return (112 + 16 * (layer + 1)) * (layer + 1); +} + +QImage AztecBarcode::paintImage(const QSizeF& size) +{ + const auto inputData = aztecEncode(data().toLatin1()); + + int layerCount = -1; + int codewordCount = 0; + int availableBits = 0; + int stuffSize = 0; // extra bits added during bit stuffing, which might make us overun the available size + bool compactMode = false; + BitVector encodedData; + + do { + layerCount = -1; + // find the smallest layout we can put the data in, while leaving 23% for error correction + for (auto i = 0; i < FullLayerCount; ++i) { + if (aztecFullDataBits(i) * 0.77 > (inputData.size() + stuffSize)) { + layerCount = i; + break; + } + } + for (auto i = 0; i < CompactLayerCount; ++i) { + if (aztecCompactDataBits(i) * 0.77 > (inputData.size() + stuffSize)) { + layerCount = i; + compactMode = true; + break; + } + } + if (layerCount == -1) { + qCWarning(Log) << "data too large for Aztec code" << inputData.size(); + return {}; + } + + // determine code word size + const auto propIt = std::lower_bound(aztec_layer_properties, aztec_layer_properties + 4, layerCount, [](const aztec_layer_property_t &lhs, int rhs) { + return lhs.layer < rhs; + }); + + // bit stuffing + auto stuffedData = bitStuffAndPad(inputData, (*propIt).codeWordSize); + stuffSize = stuffedData.size() - inputData.size(); + + availableBits = compactMode ? aztecCompactDataBits(layerCount) : aztecFullDataBits(layerCount); + codewordCount = stuffedData.size() / (*propIt).codeWordSize; + const auto rsWordCount = availableBits / (*propIt).codeWordSize - codewordCount; + + // compute error correction + ReedSolomon rs((*propIt).gf, rsWordCount); + const auto rsData = rs.encode(stuffedData); + + // pad with leading 0 bits to align to code word boundaries + encodedData.reserve(availableBits); + if (int diff = availableBits - stuffedData.size() - rsData.size()) + encodedData.appendMSB(0, diff); + encodedData.append(stuffedData); + encodedData.append(rsData); + + // try again in the rare case that we overrun the available bits due to bit stuffing and padding + } while (encodedData.size() > availableBits); + + // determine mode message + BitVector modeMsg; + if (compactMode) { + modeMsg.appendMSB(layerCount, 2); + modeMsg.appendMSB(codewordCount - 1, 6); + ReedSolomon rs(ReedSolomon::GF16, 5); + modeMsg.append(rs.encode(modeMsg)); + } else { + modeMsg.appendMSB(layerCount, 5); + modeMsg.appendMSB(codewordCount - 1, 11); + ReedSolomon rs(ReedSolomon::GF16, 6); + modeMsg.append(rs.encode(modeMsg)); + } + + // render the result + if (compactMode) { + QImage img(CompactMaxSize, CompactMaxSize, QImage::Format_RGB32); + img.fill(backgroundColor()); + paintCompactGrid(&img); + paintCompactData(&img, encodedData, layerCount); + paintCompactModeMessage(&img, modeMsg); + return cropAndScaleCompact(&img, layerCount, std::max(size.width(), size.height())); + } else { + QImage img(FullMaxSize, FullMaxSize, QImage::Format_RGB32); + img.fill(backgroundColor()); + paintFullGrid(&img); + paintFullData(&img, encodedData, layerCount); + paintFullModeMessage(&img, modeMsg); + return cropAndScaleFull(&img, layerCount, std::max(size.width(), size.height())); + } + + return {}; +} + +// code points and encoding modes for each of the first 127 ASCII characters, the rest is encoded in Binary mode +enum Mode { + NoMode, + Upper, + Lower, + Mixed, + Punct, + Digit, + Binary, + MODE_COUNT, + Special +}; + +enum SpecialChar { + Space, + CarriageReturn, + Comma, + Dot, + SPECIAL_CHAR_COUNT +}; + +struct aztec_code_t { + uint8_t code; + uint8_t mode; +}; + +static const aztec_code_t aztec_code_table[] = { + { 0, Binary }, // 0 + { 2, Mixed }, + { 3, Mixed }, + { 4, Mixed }, + { 5, Mixed }, + { 6, Mixed }, + { 7, Mixed }, + { 8, Mixed }, // 7 BEL \a + { 9, Mixed }, + { 10, Mixed }, + { 11, Mixed }, // 10 LF / ^J + { 12, Mixed }, + { 13, Mixed }, + { CarriageReturn, Special }, // 13 CR / ^M - but also 1 Punct + { 14, Binary }, + { 15, Binary }, + { 16, Binary }, + { 17, Binary }, + { 18, Binary }, + { 19, Binary }, + { 20, Binary }, // 20 ^T + { 21, Binary }, + { 22, Binary }, + { 23, Binary }, + { 24, Binary }, + { 25, Binary }, + { 26, Binary }, + { 15, Mixed }, // 27 ^[ + { 16, Mixed }, + { 17, Mixed }, + { 18, Mixed }, // 30 ^^ + { 19, Mixed }, + { Space, Special }, // 32 SP + { 6, Punct }, + { 7, Punct }, + { 8, Punct }, // 35 # + { 9, Punct }, + { 10, Punct }, + { 11, Punct }, + { 12, Punct }, + { 13, Punct }, // 40 ( + { 14, Punct }, + { 15, Punct }, + { 16, Punct }, // 43 + + { Comma, Special }, // 44 , + { 18, Punct }, // 45 - + { Dot, Special }, // 46 . + { 20, Punct }, // 47 / + { 2, Digit }, // 48 0 + { 3, Digit }, + { 4, Digit }, + { 5, Digit }, + { 6, Digit }, + { 7, Digit }, + { 8, Digit }, + { 9, Digit }, + { 10, Digit }, + { 11, Digit }, // 57 9 + { 21, Punct }, // 58 : + { 22, Punct }, // 59 ; + { 23, Punct }, // 60 < + { 24, Punct }, + { 25, Punct }, // 62 > + { 26, Punct }, // 63 ? + { 20, Mixed }, // 64 @ + { 2, Upper }, // 65 A + { 3, Upper }, + { 4, Upper }, + { 5, Upper }, + { 6, Upper }, + { 7, Upper }, + { 8, Upper }, + { 9, Upper }, + { 10, Upper }, + { 11, Upper }, + { 12, Upper }, + { 13, Upper }, + { 14, Upper }, + { 15, Upper }, + { 16, Upper }, + { 17, Upper }, + { 18, Upper }, + { 19, Upper }, + { 20, Upper }, + { 21, Upper }, + { 22, Upper }, + { 23, Upper }, + { 24, Upper }, + { 25, Upper }, + { 26, Upper }, + { 27, Upper }, // 90 Z + { 27, Punct }, // 91 [ + { 21, Mixed }, // 92 backslash + { 28, Punct }, // 93 ] + { 22, Mixed }, // 94 ^ + { 23, Mixed }, // 95 _ + { 24, Mixed }, // 96 ` + { 2, Lower }, // 97 a + { 3, Lower }, + { 4, Lower }, + { 5, Lower }, + { 6, Lower }, + { 7, Lower }, + { 8, Lower }, + { 9, Lower }, + { 10, Lower }, + { 11, Lower }, + { 12, Lower }, + { 13, Lower }, + { 14, Lower }, + { 15, Lower }, + { 16, Lower }, + { 17, Lower }, + { 18, Lower }, + { 19, Lower }, + { 20, Lower }, + { 21, Lower }, + { 22, Lower }, + { 23, Lower }, + { 24, Lower }, + { 25, Lower }, + { 26, Lower }, + { 27, Lower }, // 122 z + { 29, Punct }, // 123 { + { 25, Mixed }, // 124 | + { 30, Punct }, // 125 } + { 26, Mixed }, // 126 ~ + { 27, Mixed } // 127 DEL ^? +}; +Q_STATIC_ASSERT(sizeof(aztec_code_table) == 256); + +static const struct { + uint8_t c1; + uint8_t c2; + aztec_code_t sym; +} aztec_code_double_symbols[] = { + { '\r', '\n', { 2, Punct } }, // CR LF + { '.', ' ', { 3, Punct } }, // . SP + { ',', ' ', { 4, Punct } }, // , SP + { ':', ' ', { 5, Punct } } // : SP +}; +static const int aztec_code_double_symbols_count = sizeof(aztec_code_double_symbols) / sizeof(aztec_code_double_symbols[0]); + +static const int aztec_code_size[] = { 0, 5, 5, 5, 5, 4, 8 }; +Q_STATIC_ASSERT(sizeof(aztec_code_size) / sizeof(int) == MODE_COUNT); + +// codes for ambigious characters, ie. those that can be encoded in multiple modes +static const aztec_code_t aztec_special_chars[SPECIAL_CHAR_COUNT][MODE_COUNT - 1] = { + /* NoMode Upper Lower Mixed Punct Digit */ + { { 0, NoMode }, { 1, Upper }, { 1, Lower }, { 1, Mixed }, { 1, Upper }, { 1, Digit } }, /* SP */ + { { 0, NoMode }, { 1, Punct }, { 1, Punct }, { 14, Mixed }, { 1, Punct }, { 1, Punct } }, /* CR */ + { { 0, NoMode }, { 17, Punct }, { 17, Punct }, { 17, Punct }, { 17, Punct }, { 12, Digit } }, /* Comma */ + { { 0, NoMode }, { 19, Punct }, { 19, Punct }, { 19, Punct }, { 19, Punct }, { 13, Digit } }, /* Dot */ +}; + +// shift code table, source mode -> target mode +// NoMode indicates shift is not available, use latch instead +static const aztec_code_t aztec_shift_codes[MODE_COUNT - 1][MODE_COUNT - 1] = { + /* NoMode Upper Lower Mixed Punct Digit */ + { { 0, NoMode }, { 0, NoMode }, { 0, NoMode }, { 0, NoMode }, { 0, NoMode }, { 0, NoMode } }, + { { 0, NoMode }, { 0, NoMode }, { 0, NoMode }, { 0, NoMode }, { 0, Punct }, { 0, NoMode } }, + { { 0, NoMode }, { 28, Upper }, { 0, NoMode }, { 0, NoMode }, { 0, Punct }, { 0, NoMode } }, + { { 0, NoMode }, { 0, NoMode }, { 0, NoMode }, { 0, NoMode }, { 0, Punct }, { 0, NoMode } }, + { { 0, NoMode }, { 0, NoMode }, { 0, NoMode }, { 0, NoMode }, { 0, NoMode }, { 0, NoMode } }, + { { 0, NoMode }, { 15, Upper }, { 0, NoMode }, { 0, NoMode }, { 0, Punct }, { 0, NoMode } } +}; + +// latch code table, source mode -> target mode +static const aztec_code_t aztec_latch_codes[MODE_COUNT - 1][MODE_COUNT] = { + /* NoMode Upper Lower Mixed Punct Digit Binary */ + { { 0, NoMode }, { 0, NoMode }, { 0, NoMode }, { 0, NoMode }, { 0, NoMode }, { 0, NoMode }, { 0, NoMode } }, + { { 0, NoMode }, { 0, NoMode }, { 28, Lower }, { 29, Mixed }, { 29, Mixed }, { 30, Digit }, { 31, Binary } }, + { { 0, NoMode }, { 30, Digit }, { 0, NoMode }, { 29, Mixed }, { 29, Mixed }, { 30, Digit }, { 31, Binary } }, + { { 0, NoMode }, { 30, Digit }, { 28, Lower }, { 0, NoMode }, { 30, Punct }, { 28, Lower }, { 31, Binary } }, + { { 0, NoMode }, { 31, Upper }, { 31, Upper }, { 31, Upper }, { 0, NoMode }, { 31, Upper }, { 31, Upper } }, + { { 0, NoMode }, { 14, Upper }, { 14, Upper }, { 14, Upper }, { 14, Upper }, { 0, NoMode }, { 14, Upper } } +}; + +static Mode aztecCodeLatchTo(Mode currentMode, Mode targetMode, BitVector *v) +{ + if (currentMode == targetMode) + return targetMode; + const auto latchCode = aztec_latch_codes[currentMode][targetMode]; + qCDebug(Log) << "latch" << latchCode.code << aztec_code_size[currentMode]; + v->appendMSB(latchCode.code, aztec_code_size[currentMode]); + return static_cast(latchCode.mode); +} + +static void aztecEncodeBinary(std::vector::iterator &it, const std::vector::iterator &end, BitVector *v) +{ + // determine length of the binary sequence + const auto binEndIt = std::find_if(it, end, [](aztec_code_t sym) { return sym.mode != Binary; }); + const auto length = std::distance(it, binEndIt); + + // write length field + qCDebug(Log) << "binary length" << length; + if (length < 32) { + v->appendMSB(length, 5); + } else { + v->appendMSB(0, 5); + v->appendMSB(length - 31, 11); + } + + // write data + for (; it != binEndIt; ++it) { + qCDebug(Log) << "binary data" << (*it).code; + v->appendMSB((*it).code, 8); + } +} + +static void aztecEncodeResolveAmbigious(Mode currentMode, const std::vector::iterator &begin, const std::vector::iterator &end) +{ + Q_ASSERT(begin != end); + Q_ASSERT(currentMode != (*begin).mode); + Q_ASSERT((*begin).mode == Special); + + // forward search + auto it = begin; + for (; it != end && (*it).mode == Special; ++it) { + if (aztec_special_chars[(*it).code][currentMode].mode == currentMode) { + qCDebug(Log) << "special resolved to current mode by forward search"; + (*it).mode = aztec_special_chars[(*it).code][currentMode].mode; + (*it).code = aztec_special_chars[(*it).code][currentMode].code; + } + } + + // backward search + auto backIt = it; + while (std::distance(begin, backIt) >= 1 && it != end) { + --backIt; + if ((*backIt).mode == Special && aztec_special_chars[(*backIt).code][(*it).mode].mode == (*it).mode) { + qCDebug(Log) << "special resolved by backward search"; + (*backIt).mode = aztec_special_chars[(*backIt).code][(*it).mode].mode; + (*backIt).code = aztec_special_chars[(*backIt).code][(*it).mode].code; + } else { + break; + } + } + + // pick one if we still have an ambigious symbol + if ((*begin).mode != Special) + return; + (*begin).mode = aztec_special_chars[(*begin).code][currentMode].mode; + (*begin).code = aztec_special_chars[(*begin).code][currentMode].code; + it = begin + 1; + if (it != end && (*it).mode == Special) + aztecEncodeResolveAmbigious(static_cast((*begin).mode), it, end); +} + +static Mode aztecNextMode(Mode currentMode, const std::vector::iterator &nextSym, const std::vector::iterator &end, bool &tryShift) +{ + Q_ASSERT(currentMode != (*nextSym).mode); + Q_ASSERT(nextSym != end); + Q_ASSERT((*nextSym).mode != Special); + auto it = nextSym; + ++it; + if (it != end && (*it).mode == Special) + aztecEncodeResolveAmbigious(static_cast((*nextSym).mode), it, end); + + if ((it == end || (*it).mode == currentMode) && std::distance(nextSym, it) == 1) + tryShift = true; + + qCDebug(Log) << currentMode << (*nextSym).mode << tryShift << std::distance(nextSym, it); + return static_cast((*nextSym).mode); +} + +BitVector AztecBarcode::aztecEncode(const QByteArray &data) const +{ + // phase one: translate single and double chars to code points + std::vector codes; + codes.reserve(data.size()); + for (int i = 0; i < data.size(); ++i) { + const uint8_t c1 = data.at(i); + // double char codes + if (i < data.size() - 1) { + const uint8_t c2 = data.at(i + 1); + bool found = false; + for (int j = 0; j < aztec_code_double_symbols_count; ++j) { + const auto dblCode = aztec_code_double_symbols[j]; + if (dblCode.c1 != c1 || dblCode.c2 != c2) + continue; + codes.push_back(dblCode.sym); + ++i; + found = true; + } + if (found) + continue; + } + + // > 127 binary-only range + if (c1 > 127) + codes.push_back({c1, Binary}); + // encodable single ASCII character + else + codes.push_back(aztec_code_table[c1]); + } + + // phase two: insert shift and latch codes, translate to bit stream + Mode currentMode = Upper; + BitVector result; + for (auto it = codes.begin(); it != codes.end();) { + if ((*it).mode == Binary) { + auto newMode = aztecCodeLatchTo(currentMode, Binary, &result); + while (newMode != Binary) { + currentMode = newMode; + newMode = aztecCodeLatchTo(currentMode, Binary, &result); + } + aztecEncodeBinary(it, codes.end(), &result); + continue; + } + // resolve special codes + if ((*it).mode == Special) + aztecEncodeResolveAmbigious(currentMode, it, codes.end()); + + // deal with mode changes + Mode nextMode = currentMode; + if ((*it).mode != currentMode) { + bool tryShift = false; + const auto newMode = aztecNextMode(currentMode, it, codes.end(), tryShift); + + // shift to new mode if desired and possible + if (tryShift && aztec_shift_codes[currentMode][newMode].mode != NoMode) { + qCDebug(Log) << "shift" << aztec_shift_codes[currentMode][newMode].code << aztec_code_size[currentMode]; + result.appendMSB(aztec_shift_codes[currentMode][newMode].code, aztec_code_size[currentMode]); + currentMode = newMode; + } + + // latch to new mode + while (currentMode != newMode && newMode != NoMode && currentMode != NoMode) { + currentMode = aztecCodeLatchTo(currentMode, newMode, &result); + nextMode = currentMode; + } + } + + qCDebug(Log) << (*it).code << aztec_code_size[currentMode]; + result.appendMSB((*it).code, aztec_code_size[currentMode]); + ++it; + + currentMode = nextMode; + } + + return result; +} + +BitVector AztecBarcode::bitStuffAndPad(const BitVector& input, int codeWordSize) const +{ + BitVector res; + res.reserve(input.size()); + + // bit stuff codewords with leading codeWordSize 0/1 bits + int i = 0; + while(i < input.size() - (codeWordSize - 1)) { + int v = input.valueAtMSB(i, codeWordSize - 1); + res.appendMSB(v, codeWordSize - 1); + i += codeWordSize - 1; + if (v == 0) { + res.appendBit(true); + } else if (v == (1 << (codeWordSize - 1)) - 1) { + res.appendBit(false); + } else { + res.appendBit(input.at(i++)); + } + } + while (i < input.size()) + res.appendBit(input.at(i++)); + + // pad to nearest code word boundary + while (res.size() % codeWordSize) + res.appendBit(true); + + return res; +} + +void AztecBarcode::paintFullGrid(QImage *img) const +{ + QPainter p(img); + p.translate(img->width() / 2, img->height() / 2); + + // alignment grids + QPen pen(foregroundColor()); + pen.setDashPattern({1, 1}); + p.setPen(pen); + for (int i = 0; i < img->width() / 2; i += FullGridInterval) { + p.drawLine(-i, -FullRadius, -i, FullRadius); + p.drawLine( i, -FullRadius, i, FullRadius); + p.drawLine(-FullRadius, -i, FullRadius, -i); + p.drawLine(-FullRadius, i, FullRadius, i); + } + + // bullseye background + p.setBrush(backgroundColor()); + p.setPen(Qt::NoPen); + p.drawRect(-7, -7, 14, 14); + + // bullseye + p.setBrush(Qt::NoBrush); + p.setPen(foregroundColor()); + p.drawPoint(0, 0); + p.drawRect(-2, -2, 4, 4); + p.drawRect(-4, -4, 8, 8); + p.drawRect(-6, -6, 12, 12); + + // bullseye orientation marker + p.drawRect(-7, -7, 1, 1); + p.drawRect(7, -7, 0, 1); + p.drawPoint(7, 6); +} + +static const int aztecFullLayerOffset[] = { +// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 + 66, 64, 62, 60, 57, 55, 53, 51, 49, 47, 45, 42, 40, 38, 36, 34, 32, 30, 28, 25, 23, 21, 19, 17, 15, 13, 10, 8, 6, 4, 2, 0 +}; + +void AztecBarcode::paintFullData(QImage* img, const BitVector &data, int startLayer) const +{ + QPainter p(img); + p.setPen(foregroundColor()); + + auto it = data.begin(); + for (int layer = startLayer; layer >= 0; --layer) { + const auto x1 = aztecFullLayerOffset[layer]; + const auto y1 = x1; + const auto gridInMiddle = (x1 - FullRadius) % FullGridInterval == 0; + const auto x2 = gridInMiddle ? x1 + 2 : x1 + 1; + const auto segmentLength = FullMaxSize - 2 * y1 - 2 - (gridInMiddle ? 1 : 0); + + for (int rotation = 0; rotation < 4; ++rotation) { + p.resetTransform(); + p.translate(img->width() / 2, img->height() / 2); + p.rotate(-90 * rotation); + p.translate(-img->width() / 2, -img->height() / 2); + + for (int i = 0; it != data.end(); ++i, ++it) { + const auto x = (i % 2 == 0) ? x1 : x2; + auto y = i / 2 + y1; + if (((y - FullRadius - 1) % FullGridInterval) == 0) { // skip grid lines + ++y; + i += 2; + } + if (y >= y1 + segmentLength) + break; + if (*it) + p.drawPoint(x, y); + } + } + } +} + +void AztecBarcode::paintFullModeMessage(QImage *img, const BitVector &modeData) const +{ + Q_ASSERT(modeData.size() == FullModeMessageSize); + + QPainter p(img); + p.setPen(foregroundColor()); + + auto it = modeData.begin(); + for (int rotation = 0; rotation < 4; ++rotation) { + p.resetTransform(); + p.translate(img->width() / 2, img->height() / 2); + p.rotate(90 * rotation); + + for (int i = -5; i <= 5; ++i) { + if (i == 0) // skip grid line + continue; + if (*it) + p.drawPoint(i, -7); + ++it; + } + } +} + +QImage AztecBarcode::cropAndScaleFull(QImage *img, int layerCount, int size) +{ + const auto offset = aztecFullLayerOffset[layerCount]; + const auto minSize = FullMaxSize - 2 * offset; + setMinimumSize(QSizeF(minSize, minSize) * 4); // *4 taken from what QR does + const int scale = std::max(1, size / minSize); + + QImage out(minSize * scale, minSize * scale, img->format()); + QPainter p(&out); + p.setRenderHint(QPainter::SmoothPixmapTransform, false); + const auto srcRect = img->rect().adjusted(offset, offset, -offset, -offset); + p.drawImage(out.rect(), *img, srcRect); + return out; +} + +void AztecBarcode::paintCompactGrid(QImage *img) const +{ + QPainter p(img); + p.translate(img->width() / 2, img->height() / 2); + + // bullseye + p.setPen(foregroundColor()); + p.drawPoint(0, 0); + p.drawRect(-2, -2, 4, 4); + p.drawRect(-4, -4, 8, 8); + + // bullseye orientation marker + p.drawRect(-5, -5, 1, 1); + p.drawRect(5, -5, 0, 1); + p.drawPoint(5, 4); +} + +static const int aztecCompactLayerOffset[] = { + 6, 4, 2, 0 +}; + +void AztecBarcode::paintCompactData(QImage *img, const BitVector &data, int startLayer) const +{ + QPainter p(img); + p.setPen(foregroundColor()); + + auto it = data.begin(); + for (int layer = startLayer; layer >= 0; --layer) { + const auto x1 = aztecCompactLayerOffset[layer]; + const auto y1 = x1; + const auto x2 = x1 + 1; + const auto segmentLength = CompactMaxSize - 2 * y1 - 2; + + for (int rotation = 0; rotation < 4; ++rotation) { + p.resetTransform(); + p.translate(img->width() / 2, img->height() / 2); + p.rotate(-90 * rotation); + p.translate(-img->width() / 2, -img->height() / 2); + + for (int i = 0; it != data.end(); ++i, ++it) { + const auto x = (i % 2 == 0) ? x1 : x2; + auto y = i / 2 + y1; + if (y >= y1 + segmentLength) + break; + if (*it) + p.drawPoint(x, y); + } + } + } +} + +void AztecBarcode::paintCompactModeMessage(QImage *img, const BitVector &modeData) const +{ + Q_ASSERT(modeData.size() == CompactModeMessageSize); + + QPainter p(img); + p.setPen(foregroundColor()); + + auto it = modeData.begin(); + for (int rotation = 0; rotation < 4; ++rotation) { + p.resetTransform(); + p.translate(img->width() / 2, img->height() / 2); + p.rotate(90 * rotation); + + for (int i = -3; i <= 3; ++i) { + if (*it) + p.drawPoint(i, -5); + ++it; + } + } +} + +QImage AztecBarcode::cropAndScaleCompact(QImage *img, int layerCount, int size) +{ + const auto offset = aztecCompactLayerOffset[layerCount]; + const auto minSize = CompactMaxSize - 2 * offset; + setMinimumSize(QSizeF(minSize, minSize) * 4); // *4 taken from what QR does + const int scale = std::max(1, size / minSize); + + QImage out(minSize * scale, minSize * scale, img->format()); + QPainter p(&out); + p.setRenderHint(QPainter::SmoothPixmapTransform, false); + const auto srcRect = img->rect().adjusted(offset, offset, -offset, -offset); + p.drawImage(out.rect(), *img, srcRect); + return out; +} diff --git a/src/lib/bitvector.cpp b/src/lib/bitvector.cpp new file mode 100644 --- /dev/null +++ b/src/lib/bitvector.cpp @@ -0,0 +1,125 @@ +/* + Copyright (c) 2017 Volker Krause + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + +*/ + +#include "bitvector_p.h" + +using namespace Prison; + +BitVector::BitVector() = default; +BitVector::~BitVector() = default; + +void BitVector::appendLSB(int data, int bits) +{ + for (int i = 0; i < bits; ++i) + appendBit(data & (1 << i)); +} + +void BitVector::appendMSB(int data, int bits) +{ + for (int i = bits - 1; i >= 0; --i) + appendBit(data & (1 << i)); +} + +void BitVector::appendBit(bool bit) +{ + const auto subIdx = m_size % 8; + if (subIdx == 0) + m_data.append('\0'); + if (bit) + m_data.data()[m_data.size() - 1] |= (1 << subIdx); + ++m_size; +} + +void BitVector::append(const BitVector &other) +{ + for (int i = 0; i < other.size(); ++i) + appendBit(other.at(i)); +} + +bool BitVector::at(int index) const +{ + const auto majIdx = index / 8; + const auto minIdx = index % 8; + return (m_data.at(majIdx) & (1 << minIdx)) >> minIdx; +} + +void BitVector::clear() +{ + m_data.clear(); + m_size = 0; +} + +void BitVector::reserve(int size) +{ + m_data.reserve((size / 8) + 1); +} + +int BitVector::size() const +{ + return m_size; +} + +int BitVector::valueAtMSB(int index, int size) const +{ + int res = 0; + for (int i = 0; i < size; ++i) { + res = res << 1; + res |= (at(index + i) ? 1 : 0); + } + return res; +} + +BitVector::iterator BitVector::begin() const +{ + iterator it; + it.m_index = 0; + it.m_vector = this; + return it; +} + +BitVector::iterator BitVector::end() const +{ + iterator it; + it.m_index = m_size; + it.m_vector = this; + return it; +} + +bool BitVector::operator==(const BitVector &other) const +{ + return m_size == other.m_size && m_data == other.m_data; +} + +bool BitVector::operator!=(const Prison::BitVector& other) const +{ + return m_size != other.m_size || m_data != other.m_data; +} + +QDebug operator<<(QDebug dbg, const Prison::BitVector& v) +{ + dbg << v.m_data.toHex(); + return dbg; +} diff --git a/src/lib/bitvector_p.h b/src/lib/bitvector_p.h new file mode 100644 --- /dev/null +++ b/src/lib/bitvector_p.h @@ -0,0 +1,93 @@ +/* + Copyright (c) 2017 Volker Krause + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + +*/ + +#ifndef PRISON_BITVECTOR_P_H +#define PRISON_BITVECTOR_P_H + +#include +#include + +namespace Prison { class BitVector; } +QDebug operator<<(QDebug dbg, const Prison::BitVector &v); + +namespace Prison { + +/** Vector for working with a set of bits without byte alignment. */ +class BitVector +{ +public: + BitVector(); + ~BitVector(); + + class iterator { + public: + inline bool operator!=(const iterator &other) + { + return m_index != other.m_index; + } + inline bool operator*() const + { + return m_vector->at(m_index); + } + inline iterator operator++() + { + ++m_index; + return *this; + } + private: + friend class BitVector; + const BitVector* m_vector; + int m_index; + }; + + /** Append the lowest @p bits of @p data with the least significant bit first. */ + void appendLSB(int data, int bits); + /** Append the lowest @p bits of @p data with the most significant bit first. */ + void appendMSB(int data, int bits); + void appendBit(bool bit); + void append(const BitVector &other); + /** Returns the bit at index @p index. */ + bool at(int index) const; + void clear(); + void reserve(int size); + int size() const; + /** Returns the value starting at @p index of size @p size. */ + int valueAtMSB(int index, int size) const; + iterator begin() const; + iterator end() const; + + bool operator==(const BitVector &other) const; + bool operator!=(const BitVector &other) const; + +private: + friend QDebug (::operator<<)(QDebug dbg, const Prison::BitVector &v); + QByteArray m_data; + int m_size = 0; +}; + +} + +#endif // PRISON_BITVECTOR_P_H diff --git a/src/lib/prison.cpp b/src/lib/prison.cpp --- a/src/lib/prison.cpp +++ b/src/lib/prison.cpp @@ -25,6 +25,7 @@ */ #include "prison.h" +#include "aztecbarcode.h" #include "datamatrixbarcode.h" #include "qrcodebarcode.h" #include "code39barcode.h" @@ -41,7 +42,7 @@ case Prison::DataMatrix: return new DataMatrixBarcode; case Prison::Aztec: - return nullptr; + return new AztecBarcode; case Prison::Code39: return new Code39Barcode; case Prison::Code93: diff --git a/src/lib/reedsolomon.cpp b/src/lib/reedsolomon.cpp new file mode 100644 --- /dev/null +++ b/src/lib/reedsolomon.cpp @@ -0,0 +1,106 @@ +/* + Copyright (c) 2017 Volker Krause + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + +*/ + +#include "reedsolomon_p.h" +#include "bitvector_p.h" + +#include + +#include + +using namespace Prison; + +// See https://en.wikiversity.org/wiki/Reed%E2%80%93Solomon_codes_for_coders + +static int highestBit(int n) +{ + int i = 0; + while (n >= (1 << i)) + ++i; + return i - 1; +} + +ReedSolomon::ReedSolomon(int polynom, int symbolCount) + : m_symCount(symbolCount) +{ + m_symSize = highestBit(polynom); + + // calculate the log/alog tables + const auto logmod = (1 << m_symSize) - 1; + m_logTable.reset(new int[logmod + 1]); + m_antiLogTable.reset(new int [logmod]); + + for (int p = 1, v = 0; v < logmod; v++) { + m_antiLogTable[v] = p; + m_logTable[p] = v; + p <<= 1; + if (p & (1 << m_symSize)) + p ^= polynom; + } + + // compute the encoding polynom + m_polynom.reset(new int[m_symCount + 1]); + m_polynom[0] = 1; + for (int i = 1; i <= m_symCount; ++i) { + m_polynom[i] = 1; + for (int k = i - 1; k > 0; --k) { + if (m_polynom[k]) + m_polynom[k] = m_antiLogTable[(m_logTable[m_polynom[k]] + i) % logmod]; + m_polynom[k] ^= m_polynom[k - 1]; + } + m_polynom[0] = m_antiLogTable[(m_logTable[m_polynom[0]] + i) % logmod]; + } +} + +ReedSolomon::~ReedSolomon() = default; + +BitVector ReedSolomon::encode(const BitVector& input) const +{ + std::unique_ptr result(new int[m_symCount]); + for (int i = 0; i < m_symCount; ++i) + result[i] = 0; + + const auto logmod = (1 << m_symSize) - 1; + for (int i = 0; i < input.size() / m_symSize; i++) { + auto m = result[m_symCount - 1] ^ input.valueAtMSB(i * m_symSize, m_symSize); + for (int k = m_symCount - 1; k > 0; --k) { + if (m && m_polynom[k]) + result[k] = result[k - 1] ^ m_antiLogTable[(m_logTable[m] + m_logTable[m_polynom[k]]) % logmod]; + else + result[k] = result[k - 1]; + } + if (m && m_polynom[0]) + result[0] = m_antiLogTable[(m_logTable[m] + m_logTable[m_polynom[0]]) % logmod]; + else + result[0] = 0; + } + + BitVector v; + for (int i = m_symCount - 1; i >= 0; --i) { + v.appendMSB(result[i], m_symSize); + } + return v; +} diff --git a/src/lib/prison.cpp b/src/lib/reedsolomon_p.h copy from src/lib/prison.cpp copy to src/lib/reedsolomon_p.h --- a/src/lib/prison.cpp +++ b/src/lib/reedsolomon_p.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2010-2016 Sune Vuorela + Copyright (c) 2017 Volker Krause Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -24,29 +24,48 @@ */ -#include "prison.h" -#include "datamatrixbarcode.h" -#include "qrcodebarcode.h" -#include "code39barcode.h" -#include "code93barcode.h" +#ifndef PRISON_REEDSOLOMON_H +#define PRISON_REEDSOLOMON_H -Prison::AbstractBarcode *Prison::createBarcode(BarcodeType type) +#include + +namespace Prison { + +class BitVector; + +/** Reed Solomon checksum generator. */ +class ReedSolomon { - switch(type) - { - case Prison::Null: - return nullptr; - case Prison::QRCode: - return new QRCodeBarcode; - case Prison::DataMatrix: - return new DataMatrixBarcode; - case Prison::Aztec: - return nullptr; - case Prison::Code39: - return new Code39Barcode; - case Prison::Code93: - return new Code93Barcode; - } - return nullptr; +public: + enum GF { + GF16 = 0x13, + GF64 = 0x43, + GF256 = 0x12d, + GF1024 = 0x409, + GF4096 = 0x1069 + }; + + /** Initialize a Reed Solomon encoder with the Galois Field + * described by the bit pattern of @p polynom, for generating + * @p symbolCount error correction symbols. + */ + explicit ReedSolomon(int polynom, int symbolCount); + ReedSolomon(const ReedSolomon&) = delete; + ~ReedSolomon(); + + /** Encode the content of @p input and return the resulting + * code words. + */ + BitVector encode(const BitVector &input) const; + +private: + std::unique_ptr m_logTable; + std::unique_ptr m_antiLogTable; + std::unique_ptr m_polynom; + int m_symCount = 0; + int m_symSize = 0; +}; } + +#endif // PRISON_REEDSOLOMON_H