diff --git a/CMakeLists.txt b/CMakeLists.txt
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -13,8 +13,10 @@
include(ECMGenerateHeaders)
include(ECMGeneratePriFile)
include(ECMAddQch)
+include(ECMAddTests)
include(CMakePackageConfigHelpers)
include(ECMSetupVersion)
+include(ECMQtDeclareLoggingCategory)
include(KDEInstallDirs)
include(KDECMakeSettings)
include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE)
@@ -42,6 +44,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,18 @@
+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/aztec.qrc)
+
+ecm_add_test(${aztecbarcodetest_srcs} TEST_NAME prison-aztecbarcodetest LINK_LIBRARIES Qt5::Test KF5::Prison)
+
+ecm_add_test(
+ reedsolomontest.cpp
+ ../src/lib/bitvector.cpp
+ ../src/lib/reedsolomon.cpp
+ TEST_NAME prison-reedsolomontest
+ LINK_LIBRARIES Qt5::Test KF5::Prison
+)
diff --git a/autotests/aztec/aztec.qrc b/autotests/aztec/aztec.qrc
new file mode 100644
--- /dev/null
+++ b/autotests/aztec/aztec.qrc
@@ -0,0 +1,29 @@
+
+
+ rendering/aztec-full-grid.png
+ rendering/aztec-full-data-0011.png
+ rendering/aztec-full-data-0101.png
+ rendering/aztec-full-data-1001.png
+ rendering/aztec-full-data-1010.png
+ rendering/aztec-full-data-1111.png
+ rendering/aztec-full-mode-1111.png
+ rendering/aztec-full-mode-1234.png
+ rendering/aztec-full-mode-1234-rev.png
+
+ rendering/aztec-compact-grid.png
+ rendering/aztec-compact-data-0011.png
+ rendering/aztec-compact-data-0101.png
+ rendering/aztec-compact-data-1001.png
+ rendering/aztec-compact-data-1010.png
+ rendering/aztec-compact-data-1111.png
+ rendering/aztec-compact-mode-1111.png
+ rendering/aztec-compact-mode-1234.png
+ rendering/aztec-compact-mode-1234-rev.png
+
+ encoding/aztec-complete-compact1.png
+ encoding/aztec-complete-compact3.png
+ encoding/aztec-complete-compact4.png
+ encoding/aztec-complete-full5.png
+ encoding/aztec-complete-big.png
+
+
diff --git a/autotests/aztec/encoding/aztec-complete-big.png b/autotests/aztec/encoding/aztec-complete-big.png
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@
+
+ 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(":/rendering/aztec-full-grid.png"));
+ ref = ref.convertToFormat(img.format());
+ QCOMPARE(img, ref);
+ }
+
+ void testCompactGrid()
+ {
+ 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(":/rendering/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") << 32;
+
+ 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") << 32;
+
+ v.clear();
+ for (int i = 0; i < 1248; ++i)
+ v.appendLSB(0xffff, 16);
+ QTest::newRow("1111-31") << v << QStringLiteral("aztec-full-data-1111.png") << 32;
+
+ 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") << 16;
+
+ v.clear();
+ for (int i = 0; i < 16; ++i)
+ v.appendLSB(0xCC, 8);
+ QTest::newRow("0011-0") << v << QStringLiteral("aztec-full-data-0011.png") << 1;
+ }
+
+ 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(":/rendering/") + 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(":/rendering/") + 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") << 4;
+
+ 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") << 4;
+
+ v.clear();
+ for (int i = 0; i < 304; ++i)
+ v.appendLSB(0xffff, 16);
+ QTest::newRow("1111-3") << v << QStringLiteral("aztec-compact-data-1111.png") << 4;
+
+ 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") << 3;
+
+ v.clear();
+ for (int i = 0; i < 13; ++i)
+ v.appendLSB(0xCC, 8);
+ QTest::newRow("0011-0") << v << QStringLiteral("aztec-compact-data-0011.png") << 1;
+ }
+
+ 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(":/rendering/") + 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(":/rendering/") + 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("compact 3 layer") << QStringLiteral("M1KRAUSE/VOLKER ABCDEFG TXLRIXBT 0212 309Y014E0063 100") << "aztec-complete-compact3.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(":/encoding/") + 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 layerCount) 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 layerCount) 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,794 @@
+/*
+ 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) * layer;
+}
+
+static int aztecFullDataBits(int layer)
+{
+ return (112 + 16 * layer) * layer;
+}
+
+QImage AztecBarcode::paintImage(const QSizeF& size)
+{
+ const auto inputData = aztecEncode(data().toLatin1());
+
+ int layerCount = 0;
+ 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 = 0;
+ // find the smallest layout we can put the data in, while leaving 23% for error correction
+ for (auto i = 1; i <= FullLayerCount; ++i) {
+ if (aztecFullDataBits(i) * 0.77 > (inputData.size() + stuffSize)) {
+ layerCount = i;
+ break;
+ }
+ }
+ for (auto i = 1; i <= CompactLayerCount; ++i) {
+ if (aztecCompactDataBits(i) * 0.77 > (inputData.size() + stuffSize)) {
+ layerCount = i;
+ compactMode = true;
+ break;
+ }
+ }
+ if (layerCount == 0) {
+ 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 - 1, 2);
+ modeMsg.appendMSB(codewordCount - 1, 6);
+ ReedSolomon rs(ReedSolomon::GF16, 5);
+ modeMsg.append(rs.encode(modeMsg));
+ } else {
+ modeMsg.appendMSB(layerCount - 1, 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 layerCount) const
+{
+ QPainter p(img);
+ p.setPen(foregroundColor());
+
+ auto it = data.begin();
+ for (int layer = layerCount - 1; 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 - 1];
+ 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 layerCount) const
+{
+ QPainter p(img);
+ p.setPen(foregroundColor());
+
+ auto it = data.begin();
+ for (int layer = layerCount - 1; 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 - 1];
+ 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,130 @@
+/*
+ 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,111 @@
+/*
+ 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_P_H
+#define PRISON_REEDSOLOMON_P_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_P_H