From df98a2279e538c34e69df8dda4a1958bcd474e26 Mon Sep 17 00:00:00 2001 From: Michael Ochmann Date: Thu, 23 Feb 2023 09:30:08 +0100 Subject: [PATCH 01/15] now exporting files with extension appended user can now specify output filename without extension, because this is handled by qr internally now --- CMakeLists.txt | 2 +- main.cpp | 2 +- src/PNGRenderer.hpp | 2 +- src/SVGRenderer.hpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9a9108c..091f0d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,7 @@ set(EXE_NAME "qr_${CMAKE_PROJECT_VERSION}-${ARCH}") project( qr - VERSION 1.0.1 + VERSION 1.1.0 LANGUAGES CXX ) diff --git a/main.cpp b/main.cpp index 2114099..93a0848 100644 --- a/main.cpp +++ b/main.cpp @@ -20,7 +20,7 @@ Usage: -f --format output file format. can be one of "cli, png, svg" -h --help show this help -i --input take data from this argument instead of stdin - -o --output output file name + -o --output output file name without extension -s --size desired output file size in pixels -t --type output QR code type. can be one of "small, medium, large" diff --git a/src/PNGRenderer.hpp b/src/PNGRenderer.hpp index ddd8b54..a0de5d7 100644 --- a/src/PNGRenderer.hpp +++ b/src/PNGRenderer.hpp @@ -13,7 +13,7 @@ class PNGRenderer : public PixelRenderer { virtual void render(const std::string& filename) override { this->generateBuffer(); - stbi_write_png(filename.c_str(), targetSize, targetSize, 4, this->bitmap.data(), sizeof(Color) * targetSize); + stbi_write_png((filename + ".png").c_str(), targetSize, targetSize, 4, this->bitmap.data(), sizeof(Color) * targetSize); } }; diff --git a/src/SVGRenderer.hpp b/src/SVGRenderer.hpp index a7a564f..ded525b 100644 --- a/src/SVGRenderer.hpp +++ b/src/SVGRenderer.hpp @@ -41,7 +41,7 @@ namespace massivedynamic { file << "" << std::endl; std::ofstream outFile; - outFile.open(filename); + outFile.open(filename + ".svg"); outFile << file.str(); outFile.close(); } From 63c018e28baf14dc57d87f42a3ac436a419ac9d3 Mon Sep 17 00:00:00 2001 From: Michael Ochmann Date: Thu, 23 Feb 2023 09:43:25 +0100 Subject: [PATCH 02/15] now printing help when called without stdin and without any parameter --- main.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/main.cpp b/main.cpp index 93a0848..d729cf2 100644 --- a/main.cpp +++ b/main.cpp @@ -51,6 +51,7 @@ int main(int argc, char* argv[]) { massivedynamic::Type type = massivedynamic::Type::MEDIUM; massivedynamic::Format format = massivedynamic::Format::CONSOLE; bool fromStdin = true; + bool anyParameterSet = false; std::string paramData = ""; for(;;) { @@ -59,6 +60,8 @@ int main(int argc, char* argv[]) { if (result == -1) break; + + anyParameterSet = true; const option* opt = &options[index]; UNUSED(opt); switch(result) { @@ -127,6 +130,8 @@ int main(int argc, char* argv[]) { if (isatty(fileno(stdin)) || data.str().empty()) { std::cerr << "ERROR: no data from stdinput." << std::endl; + if(!anyParameterSet) + printHelp(); exit(1); } } else From 0c7c830d5ba5bc47e3d221dbd9a7920ef5d0f878 Mon Sep 17 00:00:00 2001 From: Michael Ochmann Date: Thu, 23 Feb 2023 10:00:38 +0100 Subject: [PATCH 03/15] minor cleanup --- src/QR.cpp | 22 +++++++++++----------- src/QR.hpp | 1 + src/{ => renderers}/ConsoleRenderer.hpp | 0 src/{ => renderers}/PNGRenderer.hpp | 0 src/{ => renderers}/PixelRenderer.hpp | 0 src/{ => renderers}/SVGRenderer.hpp | 0 6 files changed, 12 insertions(+), 11 deletions(-) rename src/{ => renderers}/ConsoleRenderer.hpp (100%) rename src/{ => renderers}/PNGRenderer.hpp (100%) rename src/{ => renderers}/PixelRenderer.hpp (100%) rename src/{ => renderers}/SVGRenderer.hpp (100%) diff --git a/src/QR.cpp b/src/QR.cpp index cee692b..d41483d 100644 --- a/src/QR.cpp +++ b/src/QR.cpp @@ -1,11 +1,11 @@ - #include #include -#include "qrcodegen.hpp" + #include "QR.hpp" -#include "ConsoleRenderer.hpp" -#include "PNGRenderer.hpp" -#include "SVGRenderer.hpp" +#include "qrcodegen.hpp" +#include "renderers/ConsoleRenderer.hpp" +#include "renderers/PNGRenderer.hpp" +#include "renderers/SVGRenderer.hpp" namespace massivedynamic { @@ -29,14 +29,14 @@ QR::QR(const std::string& data, std::string outputFile, size_t size, Type type, qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(data.c_str(), errorCorrectionLevel); this->renderers.insert({Format::CONSOLE, std::make_unique(this->pixels, qr.getSize(), size)}); - this->renderers.insert({Format::PNG, std::make_unique(this->pixels, qr.getSize(), size)}); - this->renderers.insert({Format::SVG, std::make_unique(this->pixels, qr.getSize(), size)}); + this->renderers.insert({Format::PNG, std::make_unique(this->pixels, qr.getSize(), size)}); + this->renderers.insert({Format::SVG, std::make_unique(this->pixels, qr.getSize(), size)}); + // this is inherently stupid, but "qrcodegen" does not give access to the + // `segments` vector for (int y = 0; y < qr.getSize(); y++) { - for (int x = 0; x < qr.getSize(); x++) { - bool isFilled = qr.getModule(x, y); - this->pixels.push_back(isFilled ? true : false); - } + for (int x = 0; x < qr.getSize(); x++) + this->pixels.push_back(qr.getModule(x, y)); } this->renderers.at(format)->render(this->outputFile); diff --git a/src/QR.hpp b/src/QR.hpp index 98cb31a..ca8694a 100644 --- a/src/QR.hpp +++ b/src/QR.hpp @@ -4,6 +4,7 @@ #include #include #include + #include "qrcodegen.hpp" #include "Renderer.hpp" diff --git a/src/ConsoleRenderer.hpp b/src/renderers/ConsoleRenderer.hpp similarity index 100% rename from src/ConsoleRenderer.hpp rename to src/renderers/ConsoleRenderer.hpp diff --git a/src/PNGRenderer.hpp b/src/renderers/PNGRenderer.hpp similarity index 100% rename from src/PNGRenderer.hpp rename to src/renderers/PNGRenderer.hpp diff --git a/src/PixelRenderer.hpp b/src/renderers/PixelRenderer.hpp similarity index 100% rename from src/PixelRenderer.hpp rename to src/renderers/PixelRenderer.hpp diff --git a/src/SVGRenderer.hpp b/src/renderers/SVGRenderer.hpp similarity index 100% rename from src/SVGRenderer.hpp rename to src/renderers/SVGRenderer.hpp From 7d8092aee663c0ad0aed0448ae69d7b3c541022c Mon Sep 17 00:00:00 2001 From: Michael Ochmann Date: Thu, 23 Feb 2023 10:14:21 +0100 Subject: [PATCH 04/15] added more pixel base renderers: * now supporting JPG * now supporting BMP --- main.cpp | 7 ++++++- src/QR.cpp | 6 ++++++ src/QR.hpp | 7 ++++++- src/renderers/BMPRenderer.hpp | 21 +++++++++++++++++++++ src/renderers/JPGRenderer.hpp | 21 +++++++++++++++++++++ 5 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 src/renderers/BMPRenderer.hpp create mode 100644 src/renderers/JPGRenderer.hpp diff --git a/main.cpp b/main.cpp index d729cf2..46e97cc 100644 --- a/main.cpp +++ b/main.cpp @@ -17,7 +17,7 @@ void printHelp() { Released under MIT license. Usage: - -f --format output file format. can be one of "cli, png, svg" + -f --format output file format. can be one of "cli, png, svg, jpg, bmp" -h --help show this help -i --input take data from this argument instead of stdin -o --output output file name without extension @@ -90,12 +90,17 @@ int main(int argc, char* argv[]) { } case 'f': { std::string value = optarg; + static_assert(massivedynamic::FormatLength == 5, "exhaustive formats: did you miss to add something here?"); if (value == "cli") format = massivedynamic::Format::CONSOLE; else if (value == "svg") format = massivedynamic::Format::SVG; else if (value == "png") format = massivedynamic::Format::PNG; + else if (value == "jpg") + format = massivedynamic::Format::JPG; + else if (value == "bmp") + format = massivedynamic::Format::BMP; else { printHelp(); return 1; diff --git a/src/QR.cpp b/src/QR.cpp index d41483d..8530e43 100644 --- a/src/QR.cpp +++ b/src/QR.cpp @@ -3,9 +3,12 @@ #include "QR.hpp" #include "qrcodegen.hpp" + #include "renderers/ConsoleRenderer.hpp" #include "renderers/PNGRenderer.hpp" #include "renderers/SVGRenderer.hpp" +#include "renderers/JPGRenderer.hpp" +#include "renderers/BMPRenderer.hpp" namespace massivedynamic { @@ -28,9 +31,12 @@ QR::QR(const std::string& data, std::string outputFile, size_t size, Type type, qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(data.c_str(), errorCorrectionLevel); + static_assert(FormatLength == 5, "exhaustive formats: did you miss to add something here?"); this->renderers.insert({Format::CONSOLE, std::make_unique(this->pixels, qr.getSize(), size)}); this->renderers.insert({Format::PNG, std::make_unique(this->pixels, qr.getSize(), size)}); this->renderers.insert({Format::SVG, std::make_unique(this->pixels, qr.getSize(), size)}); + this->renderers.insert({Format::JPG, std::make_unique(this->pixels, qr.getSize(), size)}); + this->renderers.insert({Format::BMP, std::make_unique(this->pixels, qr.getSize(), size)}); // this is inherently stupid, but "qrcodegen" does not give access to the // `segments` vector diff --git a/src/QR.hpp b/src/QR.hpp index ca8694a..ed9e0ac 100644 --- a/src/QR.hpp +++ b/src/QR.hpp @@ -20,9 +20,14 @@ namespace massivedynamic { enum class Format { CONSOLE, SVG, - PNG + PNG, + JPG, + BMP, + END }; + constexpr size_t FormatLength = static_cast(Format::END); + class QR { private: std::string outputFile; diff --git a/src/renderers/BMPRenderer.hpp b/src/renderers/BMPRenderer.hpp new file mode 100644 index 0000000..ecf3348 --- /dev/null +++ b/src/renderers/BMPRenderer.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include + +#include "PixelRenderer.hpp" + +namespace massivedynamic { + +class BMPRenderer : public PixelRenderer { + public: + BMPRenderer(const std::vector& pixels, size_t sourceSize, size_t targetSize) : PixelRenderer(pixels, sourceSize, targetSize) {} + + virtual void render(const std::string& filename) override { + this->generateBuffer(); + + stbi_write_bmp((filename + ".bmp").c_str(), targetSize, targetSize, 4, this->bitmap.data()); + } + +}; + +} \ No newline at end of file diff --git a/src/renderers/JPGRenderer.hpp b/src/renderers/JPGRenderer.hpp new file mode 100644 index 0000000..abfc8f4 --- /dev/null +++ b/src/renderers/JPGRenderer.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include + +#include "PixelRenderer.hpp" + +namespace massivedynamic { + +class JPGRenderer : public PixelRenderer { + public: + JPGRenderer(const std::vector& pixels, size_t sourceSize, size_t targetSize) : PixelRenderer(pixels, sourceSize, targetSize) {} + + virtual void render(const std::string& filename) override { + this->generateBuffer(); + + stbi_write_jpg((filename + ".jpg").c_str(), targetSize, targetSize, 4, this->bitmap.data(), sizeof(Color) * targetSize); + } + +}; + +} \ No newline at end of file From dd45505f21da515d84454e1fcf9b363d03696864 Mon Sep 17 00:00:00 2001 From: Michael Ochmann Date: Thu, 23 Feb 2023 10:43:26 +0100 Subject: [PATCH 05/15] changed sizing of svgs --- docs/logo_qr.svg | 459 +++++++++++++++++----------------- src/renderers/SVGRenderer.hpp | 7 +- 2 files changed, 234 insertions(+), 232 deletions(-) diff --git a/docs/logo_qr.svg b/docs/logo_qr.svg index 3b3d41a..172ae6b 100644 --- a/docs/logo_qr.svg +++ b/docs/logo_qr.svg @@ -1,231 +1,232 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/renderers/SVGRenderer.hpp b/src/renderers/SVGRenderer.hpp index ded525b..c6a5a3a 100644 --- a/src/renderers/SVGRenderer.hpp +++ b/src/renderers/SVGRenderer.hpp @@ -20,11 +20,12 @@ namespace massivedynamic { std::stringstream file; std::string base = R"EOF( - +targetSize << " " << this->targetSize << "\" xml:space=\"preserve\">" << '\n'; + file << "viewBox=\"0 0 " << this->targetSize << " " << this->targetSize << "\" width=\"" << this->targetSize << "\" height=\"" << this->targetSize << "\" xml:space=\"preserve\">" << '\n'; file << " targetSize << "\" height=\"" << this->targetSize << "\" style=\"fill: white;\" />" << '\n'; float pixelSize = static_cast(this->targetSize) / static_cast(this->sourceSize + 2); From 2a8e54610b2f8c4d4ce0e63987fc36ee7a9229e9 Mon Sep 17 00:00:00 2001 From: Michael Ochmann Date: Fri, 24 Feb 2023 14:25:51 +0100 Subject: [PATCH 06/15] added man page support --- CMakeLists.txt | 10 ++++++- main.cpp | 75 +++++++++++++++++++++++++++++++++----------------- src/QR.cpp | 10 ++++++- src/QR.hpp | 3 +- 4 files changed, 70 insertions(+), 28 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 091f0d5..e8c22cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,5 +27,13 @@ set_target_properties(qr PROPERTIES OUTPUT_NAME ${EXE_NAME}) target_compile_options(qr PUBLIC -Wall -std=c++20 -arch ${ARCH}) target_include_directories(qr PRIVATE src lib) +add_custom_target(man ALL) +add_custom_command( + TARGET man + COMMAND help2man -h "-h" -v "-v" -o qr.7 ${EXE_NAME} + COMMAND gzip -f qr.7 +) + install(TARGETS qr RUNTIME DESTINATION bin) -install(PROGRAMS build/${EXE_NAME} DESTINATION bin RENAME qr) \ No newline at end of file +install(PROGRAMS build/${EXE_NAME} DESTINATION bin RENAME qr) +install(FILES build/qr.7.gz DESTINATION /usr/local/share/man/man7/) \ No newline at end of file diff --git a/main.cpp b/main.cpp index 46e97cc..baa1fc6 100644 --- a/main.cpp +++ b/main.cpp @@ -12,20 +12,39 @@ constexpr const char* VERSION = "@qr_VERSION@"; void printHelp() { std::string helpText = R"EOF( -(C) 2023, MikO -– a tool for generating QR codes – -Released under MIT license. - -Usage: - -f --format output file format. can be one of "cli, png, svg, jpg, bmp" - -h --help show this help - -i --input take data from this argument instead of stdin - -o --output output file name without extension - -s --size desired output file size in pixels - -t --type output QR code type. can be one of "small, medium, large" - -In pixel based renderers (i.e. PNG), the output size may not be exactly as -specified but be the nearest multiple of the actual cell size for the QR code. + +qr is a tool for generating QR codes from the commandline + +Usage: qr [OPTION]... --input "data to encapsulate" + +*Caveats* + With no `--input` parameter defined, read STDIN + + In pixel based renderers (i.e. PNG), the output size may not be exactly as + specified but be the nearest multiple of the actual cell size for the QR code. + +Options: + -f --format output file format. can be one of "cli, png, svg, jpg, bmp" + -h --help show this help + -i --input take data from this argument instead of stdin + -o --output output file name without extension + -s --size desired output file size in pixels + -t --type output QR code type. can be one of "small, medium, large" + -v --version shows version info + +Examples: + qr -i "this is from parameter" -f png -s 512 -o my_qrcode_file + + echo "this is from stdin" | qr -t small -f png -s 512 -o my_qrcode_file_with_low_ecc + +Copyright: + (C) 2023, MikO + +*License* + Released under MIT license + +Report bugs and issues at out issue tracker: + https://git.mike-ochmann.de/MassiveDynamic/qr/issues )EOF"; std::cout << "qr " << VERSION; std::cout << helpText << std::endl; @@ -37,26 +56,27 @@ int main(int argc, char* argv[]) { exit(1); } const option options[] = { - {"help", no_argument, nullptr, 'h'}, - {"input", required_argument, nullptr, 'i'}, - {"output", required_argument, nullptr, 'o'}, - {"size", required_argument, nullptr, 's'}, - {"type", required_argument, nullptr, 't'}, - {"format", required_argument, nullptr, 'f'}, + {"help", no_argument, nullptr, 'h'}, + {"input", required_argument, nullptr, 'i'}, + {"output", required_argument, nullptr, 'o'}, + {"size", required_argument, nullptr, 's'}, + {"type", required_argument, nullptr, 't'}, + {"format", required_argument, nullptr, 'f'}, + {"version", no_argument, nullptr, 'v'}, {nullptr} }; std::string outputFile; + std::string paramData; size_t segmentSize = 0; - massivedynamic::Type type = massivedynamic::Type::MEDIUM; - massivedynamic::Format format = massivedynamic::Format::CONSOLE; bool fromStdin = true; bool anyParameterSet = false; - std::string paramData = ""; + massivedynamic::Type type = massivedynamic::Type::MEDIUM; + massivedynamic::Format format = massivedynamic::Format::CONSOLE; for(;;) { int index = -1; - int result = getopt_long(argc, argv, "ho:s:t:f:i:", options, &index); + int result = getopt_long(argc, argv, "vho:s:t:f:i:", options, &index); if (result == -1) break; @@ -112,6 +132,10 @@ int main(int argc, char* argv[]) { paramData = optarg; break; } + case 'v': { + std::cout << "qr " << VERSION << std::endl; + exit(0); + } case 'h': default: printHelp(); @@ -152,7 +176,8 @@ int main(int argc, char* argv[]) { return 1; } - std::unique_ptr qr = std::make_unique(data.str(), outputFile, segmentSize, type, format); + std::unique_ptr qr = std::make_unique(data.str(), outputFile, segmentSize, type); + qr->render(format); return 0; } \ No newline at end of file diff --git a/src/QR.cpp b/src/QR.cpp index 8530e43..89f2c53 100644 --- a/src/QR.cpp +++ b/src/QR.cpp @@ -12,7 +12,7 @@ namespace massivedynamic { -QR::QR(const std::string& data, std::string outputFile, size_t size, Type type, Format format) : outputFile(std::move(outputFile)) { +QR::QR(const std::string& data, std::string outputFile, size_t size, Type type) : outputFile(std::move(outputFile)) { qrcodegen::QrCode::Ecc errorCorrectionLevel = qrcodegen::QrCode::Ecc::HIGH; @@ -45,6 +45,14 @@ QR::QR(const std::string& data, std::string outputFile, size_t size, Type type, this->pixels.push_back(qr.getModule(x, y)); } +} + +void QR::render(Format format) { + if (this->renderers.find(format) == this->renderers.end()) { + std::cerr << "ERROR: a non-valid renderer has been selected" << std::endl; + exit(1); + } + this->renderers.at(format)->render(this->outputFile); } diff --git a/src/QR.hpp b/src/QR.hpp index ed9e0ac..34006b3 100644 --- a/src/QR.hpp +++ b/src/QR.hpp @@ -34,7 +34,8 @@ namespace massivedynamic { std::vector pixels; std::unordered_map> renderers; public: - QR(const std::string& data, std::string outputFile, size_t size, Type type, Format format); + QR(const std::string& data, std::string outputFile, size_t size, Type type); + void render(Format format); }; } \ No newline at end of file From 61f308fb0363e8988e2b64858e63d11ddbb0282b Mon Sep 17 00:00:00 2001 From: Michael Ochmann Date: Sat, 25 Feb 2023 20:10:51 +0100 Subject: [PATCH 07/15] fixed `ARCH` detection in `cmake` --- CMakeLists.txt | 14 +++++++------- main.cpp | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e8c22cc..9f4c6e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,12 @@ cmake_minimum_required(VERSION 3.14...3.25) set (CXX_STANDARD 23) +project( + qr + VERSION 1.1.0 + LANGUAGES CXX +) + if (DEFINED ENV{ARCH}) set(ARCH $ENV{ARCH}) else() @@ -10,12 +16,6 @@ endif() set(EXE_NAME "qr_${CMAKE_PROJECT_VERSION}-${ARCH}") -project( - qr - VERSION 1.1.0 - LANGUAGES CXX -) - message(STATUS "ARCH:: ${ARCH}") configure_file(main.cpp main_substitute.cpp) @@ -36,4 +36,4 @@ add_custom_command( install(TARGETS qr RUNTIME DESTINATION bin) install(PROGRAMS build/${EXE_NAME} DESTINATION bin RENAME qr) -install(FILES build/qr.7.gz DESTINATION /usr/local/share/man/man7/) \ No newline at end of file +install(FILES build/qr.7.gz DESTINATION /usr/local/share/man/man7/) diff --git a/main.cpp b/main.cpp index baa1fc6..f2883ae 100644 --- a/main.cpp +++ b/main.cpp @@ -180,4 +180,4 @@ int main(int argc, char* argv[]) { qr->render(format); return 0; -} \ No newline at end of file +} From cfc0d276dcacf396e4618842f4acbba2dfe39105 Mon Sep 17 00:00:00 2001 From: Michael Ochmann Date: Sat, 25 Feb 2023 20:32:39 +0100 Subject: [PATCH 08/15] SVGRenderer: removed redundant `std::stringstream` --- src/renderers/SVGRenderer.hpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/renderers/SVGRenderer.hpp b/src/renderers/SVGRenderer.hpp index c6a5a3a..e8c5b16 100644 --- a/src/renderers/SVGRenderer.hpp +++ b/src/renderers/SVGRenderer.hpp @@ -17,7 +17,8 @@ namespace massivedynamic { this->targetSize = sourceSize * (2 * SVGRenderer::BORDER_WIDTH); } virtual void render(const std::string& filename) override { - std::stringstream file; + std::ofstream file; + file.open(filename + ".svg"); std::string base = R"EOF( @@ -41,12 +42,9 @@ namespace massivedynamic { file << "" << std::endl; - std::ofstream outFile; - outFile.open(filename + ".svg"); - outFile << file.str(); - outFile.close(); + file.close(); } }; -} \ No newline at end of file +} From 6f3827427b64241e4ab44cce4081aa740f730228 Mon Sep 17 00:00:00 2001 From: Michael Ochmann Date: Sun, 26 Feb 2023 12:52:51 +0100 Subject: [PATCH 09/15] PixelRenderer: now respecting the specified output size exactly output is now padded by the border to prevent changing the output size specified by the user. this may result in the QR code not being exactly in the center. it may be off by 1 pixel. --- main.cpp | 5 +++-- src/renderers/PixelRenderer.hpp | 11 +++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/main.cpp b/main.cpp index f2883ae..7d8fb89 100644 --- a/main.cpp +++ b/main.cpp @@ -20,8 +20,9 @@ Usage: qr [OPTION]... --input "data to encapsulate" *Caveats* With no `--input` parameter defined, read STDIN - In pixel based renderers (i.e. PNG), the output size may not be exactly as - specified but be the nearest multiple of the actual cell size for the QR code. + In pixel based renderers (i.e. PNG), the output code may not be exactly + centered, as the amount of segment may not correspond to the specified + output size. Options: -f --format output file format. can be one of "cli, png, svg, jpg, bmp" diff --git a/src/renderers/PixelRenderer.hpp b/src/renderers/PixelRenderer.hpp index ec71b6a..8ffd810 100644 --- a/src/renderers/PixelRenderer.hpp +++ b/src/renderers/PixelRenderer.hpp @@ -13,6 +13,7 @@ namespace massivedynamic { class PixelRenderer : public Renderer { protected: size_t pixelSize; + size_t border; std::vector bitmap; void drawPixelScaled(size_t x, size_t y, Color color) { @@ -23,8 +24,8 @@ namespace massivedynamic { exit(1); } - size_t absoluteX = pixelSize * x + pixelSize; - size_t absoluteY = pixelSize * y + pixelSize; + size_t absoluteX = pixelSize * x + this->border; + size_t absoluteY = pixelSize * y + this->border; for (size_t localY = absoluteY; localY < absoluteY + pixelSize; localY++) { for (size_t localX = absoluteX; localX < absoluteX + pixelSize; localX++) { @@ -40,10 +41,8 @@ namespace massivedynamic { PixelRenderer(const std::vector& pixels, size_t sourceSize, size_t targetSize) : Renderer(pixels, sourceSize, targetSize), pixelSize(0) { this->targetSize = this->targetSize == 0 ? (sourceSize + 2) * 2 : this->targetSize; - - // here we make shure, `targetSize` will be a multiple of `sourceSize` - this->pixelSize = round(static_cast(this->targetSize) / static_cast(this->sourceSize + 2)); - this->targetSize = (this->sourceSize + 2) * this->pixelSize; + this->pixelSize = round(static_cast(this->targetSize) / static_cast(this->sourceSize + 2)); + this->border = round((this->targetSize - this->pixelSize * this->sourceSize) / 2); this->bitmap = std::vector(this->targetSize * this->targetSize, Colors::WHITE); } From 576bdf8430589d0f5abecceb473e821382aabcca Mon Sep 17 00:00:00 2001 From: Michael Ochmann Date: Sun, 26 Feb 2023 16:25:14 +0100 Subject: [PATCH 10/15] code quality: more code cleanup * removed unnecessary enum class `massivedynamic::Type` * removed some not needed newlines * cleaned up error handling by utilizing asserts more --- main.cpp | 10 ++++++---- src/QR.cpp | 31 ++++++------------------------- src/QR.hpp | 9 +-------- src/renderers/ConsoleRenderer.hpp | 2 +- 4 files changed, 14 insertions(+), 38 deletions(-) diff --git a/main.cpp b/main.cpp index 7d8fb89..bcf4613 100644 --- a/main.cpp +++ b/main.cpp @@ -3,6 +3,7 @@ #include #include +#include "qrcodegen.hpp" #include "QR.hpp" #define UNUSED(var) (void) var; @@ -72,7 +73,7 @@ int main(int argc, char* argv[]) { size_t segmentSize = 0; bool fromStdin = true; bool anyParameterSet = false; - massivedynamic::Type type = massivedynamic::Type::MEDIUM; + qrcodegen::QrCode::Ecc type = qrcodegen::QrCode::Ecc::LOW; massivedynamic::Format format = massivedynamic::Format::CONSOLE; for(;;) { @@ -98,12 +99,13 @@ int main(int argc, char* argv[]) { case 't': { std::string value = optarg; if (value == "small") - type = massivedynamic::Type::SMALL; + type = qrcodegen::QrCode::Ecc::LOW; else if (value == "medium") - type = massivedynamic::Type::MEDIUM; + type = qrcodegen::QrCode::Ecc::MEDIUM; else if (value == "large") - type = massivedynamic::Type::LARGE; + type = qrcodegen::QrCode::Ecc::HIGH; else { + std::cerr << "ERROR: type (-t, --type) has to be one of 'small', 'medium' or 'large'" << std::endl; printHelp(); return 1; } diff --git a/src/QR.cpp b/src/QR.cpp index 89f2c53..4512691 100644 --- a/src/QR.cpp +++ b/src/QR.cpp @@ -1,8 +1,8 @@ #include #include +#include #include "QR.hpp" -#include "qrcodegen.hpp" #include "renderers/ConsoleRenderer.hpp" #include "renderers/PNGRenderer.hpp" @@ -12,24 +12,8 @@ namespace massivedynamic { -QR::QR(const std::string& data, std::string outputFile, size_t size, Type type) : outputFile(std::move(outputFile)) { - - qrcodegen::QrCode::Ecc errorCorrectionLevel = qrcodegen::QrCode::Ecc::HIGH; - - switch(type) { - case Type::SMALL: - errorCorrectionLevel = qrcodegen::QrCode::Ecc::LOW; - break; - case Type::MEDIUM: - errorCorrectionLevel = qrcodegen::QrCode::Ecc::MEDIUM; - break; - default: - case Type::LARGE: - errorCorrectionLevel = qrcodegen::QrCode::Ecc::HIGH; - break; - } - - qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(data.c_str(), errorCorrectionLevel); +QR::QR(const std::string& data, std::string outputFile, size_t size, qrcodegen::QrCode::Ecc type) : outputFile(std::move(outputFile)) { + qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(data.c_str(), type); static_assert(FormatLength == 5, "exhaustive formats: did you miss to add something here?"); this->renderers.insert({Format::CONSOLE, std::make_unique(this->pixels, qr.getSize(), size)}); @@ -38,8 +22,8 @@ QR::QR(const std::string& data, std::string outputFile, size_t size, Type type) this->renderers.insert({Format::JPG, std::make_unique(this->pixels, qr.getSize(), size)}); this->renderers.insert({Format::BMP, std::make_unique(this->pixels, qr.getSize(), size)}); - // this is inherently stupid, but "qrcodegen" does not give access to the - // `segments` vector + // this is inherently stupid, but "qrcodegen::QrCode" does not give access to the + // `segments` vector member and the class itself is marked final. for (int y = 0; y < qr.getSize(); y++) { for (int x = 0; x < qr.getSize(); x++) this->pixels.push_back(qr.getModule(x, y)); @@ -48,10 +32,7 @@ QR::QR(const std::string& data, std::string outputFile, size_t size, Type type) } void QR::render(Format format) { - if (this->renderers.find(format) == this->renderers.end()) { - std::cerr << "ERROR: a non-valid renderer has been selected" << std::endl; - exit(1); - } + assert(this->renderers.find(format) != this->renderers.end() && "ERROR: a non-valid renderer has been selected"); this->renderers.at(format)->render(this->outputFile); } diff --git a/src/QR.hpp b/src/QR.hpp index 34006b3..d2ed8c9 100644 --- a/src/QR.hpp +++ b/src/QR.hpp @@ -11,12 +11,6 @@ namespace massivedynamic { typedef uint32_t Color; - enum class Type { - SMALL, - MEDIUM, - LARGE - }; - enum class Format { CONSOLE, SVG, @@ -27,14 +21,13 @@ namespace massivedynamic { }; constexpr size_t FormatLength = static_cast(Format::END); - class QR { private: std::string outputFile; std::vector pixels; std::unordered_map> renderers; public: - QR(const std::string& data, std::string outputFile, size_t size, Type type); + QR(const std::string& data, std::string outputFile, size_t size, qrcodegen::QrCode::Ecc type); void render(Format format); }; diff --git a/src/renderers/ConsoleRenderer.hpp b/src/renderers/ConsoleRenderer.hpp index 8896974..1299bad 100644 --- a/src/renderers/ConsoleRenderer.hpp +++ b/src/renderers/ConsoleRenderer.hpp @@ -21,7 +21,7 @@ class ConsoleRenderer : public Renderer { std::cout << '\n'; } std::cout << std::string(border, '\n'); - std::cout << std::endl; + std::cout.flush(); } }; From 98b1cddd79646a6fdc243a7f1180de25fdcc0ad7 Mon Sep 17 00:00:00 2001 From: Michael Ochmann Date: Sun, 26 Feb 2023 16:42:51 +0100 Subject: [PATCH 11/15] code quality: deleted copy & move constructors --- src/QR.cpp | 2 +- src/QR.hpp | 4 ++++ src/Renderer.hpp | 5 ++++- src/renderers/PixelRenderer.hpp | 10 ++++------ 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/QR.cpp b/src/QR.cpp index 4512691..608e2cf 100644 --- a/src/QR.cpp +++ b/src/QR.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include #include "QR.hpp" diff --git a/src/QR.hpp b/src/QR.hpp index d2ed8c9..ed90cf1 100644 --- a/src/QR.hpp +++ b/src/QR.hpp @@ -28,6 +28,10 @@ namespace massivedynamic { std::unordered_map> renderers; public: QR(const std::string& data, std::string outputFile, size_t size, qrcodegen::QrCode::Ecc type); + QR(const QR&&) = delete; + QR(QR&) = delete; + ~QR() = default; + void render(Format format); }; diff --git a/src/Renderer.hpp b/src/Renderer.hpp index b91e954..4f968ec 100644 --- a/src/Renderer.hpp +++ b/src/Renderer.hpp @@ -11,7 +11,10 @@ class Renderer { size_t targetSize; public: Renderer(const std::vector& pixels, size_t sourceSize, size_t targetSize) : pixels(pixels), sourceSize(sourceSize), targetSize(targetSize) {} - virtual ~Renderer() = default; + Renderer(const Renderer&&) = delete; + Renderer(Renderer&) = delete; + virtual ~Renderer() = default; + virtual void render(const std::string& filename) = 0; }; diff --git a/src/renderers/PixelRenderer.hpp b/src/renderers/PixelRenderer.hpp index 8ffd810..e8e1179 100644 --- a/src/renderers/PixelRenderer.hpp +++ b/src/renderers/PixelRenderer.hpp @@ -2,6 +2,7 @@ #include #include +#include #include "Renderer.hpp" #define STB_IMAGE_WRITE_IMPLEMENTATION @@ -19,10 +20,7 @@ namespace massivedynamic { void drawPixelScaled(size_t x, size_t y, Color color) { size_t pixelSize = this->pixelSize; - if (pixelSize < 1) { - std::cerr << "ERROR: output file size is too small" << std::endl; - exit(1); - } + assert(pixelSize > 1 && "ERROR: output file size is too small"); size_t absoluteX = pixelSize * x + this->border; size_t absoluteY = pixelSize * y + this->border; @@ -42,12 +40,12 @@ namespace massivedynamic { PixelRenderer(const std::vector& pixels, size_t sourceSize, size_t targetSize) : Renderer(pixels, sourceSize, targetSize), pixelSize(0) { this->targetSize = this->targetSize == 0 ? (sourceSize + 2) * 2 : this->targetSize; this->pixelSize = round(static_cast(this->targetSize) / static_cast(this->sourceSize + 2)); - this->border = round((this->targetSize - this->pixelSize * this->sourceSize) / 2); + this->border = round(static_cast(this->targetSize - this->pixelSize * this->sourceSize) / 2.0f); this->bitmap = std::vector(this->targetSize * this->targetSize, Colors::WHITE); } - void generateBuffer() { + virtual void generateBuffer() { for (size_t y = 0; y < sourceSize; y++) { for (size_t x = 0; x < sourceSize; x++) { if (!pixels.at(y * sourceSize + x)) From d3850939cd6449ad93353bcb81ca66945da066d3 Mon Sep 17 00:00:00 2001 From: Michael Ochmann Date: Sun, 26 Feb 2023 17:09:42 +0100 Subject: [PATCH 12/15] code quality: getting rid of `std::vector` of all the renderers --- src/QR.cpp | 24 +++++++++++------------- src/QR.hpp | 11 ++++++++--- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/QR.cpp b/src/QR.cpp index 608e2cf..7cee284 100644 --- a/src/QR.cpp +++ b/src/QR.cpp @@ -12,16 +12,8 @@ namespace massivedynamic { -QR::QR(const std::string& data, std::string outputFile, size_t size, qrcodegen::QrCode::Ecc type) : outputFile(std::move(outputFile)) { - qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(data.c_str(), type); - - static_assert(FormatLength == 5, "exhaustive formats: did you miss to add something here?"); - this->renderers.insert({Format::CONSOLE, std::make_unique(this->pixels, qr.getSize(), size)}); - this->renderers.insert({Format::PNG, std::make_unique(this->pixels, qr.getSize(), size)}); - this->renderers.insert({Format::SVG, std::make_unique(this->pixels, qr.getSize(), size)}); - this->renderers.insert({Format::JPG, std::make_unique(this->pixels, qr.getSize(), size)}); - this->renderers.insert({Format::BMP, std::make_unique(this->pixels, qr.getSize(), size)}); - +QR::QR(const std::string& data, std::string outputFile, size_t size, qrcodegen::QrCode::Ecc type) : outputFile(std::move(outputFile)), + qr(qrcodegen::QrCode::encodeText(data.c_str(), type)), size(size) { // this is inherently stupid, but "qrcodegen::QrCode" does not give access to the // `segments` vector member and the class itself is marked final. for (int y = 0; y < qr.getSize(); y++) { @@ -32,9 +24,15 @@ QR::QR(const std::string& data, std::string outputFile, size_t size, qrcodegen:: } void QR::render(Format format) { - assert(this->renderers.find(format) != this->renderers.end() && "ERROR: a non-valid renderer has been selected"); - - this->renderers.at(format)->render(this->outputFile); + static_assert(FormatLength == 5, "exhaustive formats: did you miss to add something here?"); + switch(format) { + case Format::CONSOLE : this->renderTyped(); break; + case Format::PNG : this->renderTyped(); break; + case Format::SVG : this->renderTyped(); break; + case Format::JPG : this->renderTyped(); break; + case Format::BMP : this->renderTyped(); break; + case Format::END : break; + } } } \ No newline at end of file diff --git a/src/QR.hpp b/src/QR.hpp index ed90cf1..edcc80d 100644 --- a/src/QR.hpp +++ b/src/QR.hpp @@ -2,8 +2,6 @@ #include #include -#include -#include #include "qrcodegen.hpp" #include "Renderer.hpp" @@ -25,7 +23,8 @@ namespace massivedynamic { private: std::string outputFile; std::vector pixels; - std::unordered_map> renderers; + qrcodegen::QrCode qr; + size_t size; public: QR(const std::string& data, std::string outputFile, size_t size, qrcodegen::QrCode::Ecc type); QR(const QR&&) = delete; @@ -33,6 +32,12 @@ namespace massivedynamic { ~QR() = default; void render(Format format); + + template + void renderTyped() { + T renderer = T(this->pixels, this->qr.getSize(), this->size); + renderer.render(this->outputFile); + } }; } \ No newline at end of file From 4d7a7bfd6985ea5f901ea42b1852a0646016cfab Mon Sep 17 00:00:00 2001 From: Michael Ochmann Date: Sun, 26 Feb 2023 17:11:37 +0100 Subject: [PATCH 13/15] code quality: making all the pixel based renderers `final` as they are only a thin wrapper of `stbi` functions --- src/renderers/BMPRenderer.hpp | 2 +- src/renderers/JPGRenderer.hpp | 2 +- src/renderers/PNGRenderer.hpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/renderers/BMPRenderer.hpp b/src/renderers/BMPRenderer.hpp index ecf3348..0448068 100644 --- a/src/renderers/BMPRenderer.hpp +++ b/src/renderers/BMPRenderer.hpp @@ -6,7 +6,7 @@ namespace massivedynamic { -class BMPRenderer : public PixelRenderer { +class BMPRenderer final : public PixelRenderer { public: BMPRenderer(const std::vector& pixels, size_t sourceSize, size_t targetSize) : PixelRenderer(pixels, sourceSize, targetSize) {} diff --git a/src/renderers/JPGRenderer.hpp b/src/renderers/JPGRenderer.hpp index abfc8f4..1d2a63d 100644 --- a/src/renderers/JPGRenderer.hpp +++ b/src/renderers/JPGRenderer.hpp @@ -6,7 +6,7 @@ namespace massivedynamic { -class JPGRenderer : public PixelRenderer { +class JPGRenderer final: public PixelRenderer { public: JPGRenderer(const std::vector& pixels, size_t sourceSize, size_t targetSize) : PixelRenderer(pixels, sourceSize, targetSize) {} diff --git a/src/renderers/PNGRenderer.hpp b/src/renderers/PNGRenderer.hpp index a0de5d7..25c2875 100644 --- a/src/renderers/PNGRenderer.hpp +++ b/src/renderers/PNGRenderer.hpp @@ -6,7 +6,7 @@ namespace massivedynamic { -class PNGRenderer : public PixelRenderer { +class PNGRenderer final: public PixelRenderer { public: PNGRenderer(const std::vector& pixels, size_t sourceSize, size_t targetSize) : PixelRenderer(pixels, sourceSize, targetSize) {} From 425a0f0b665cbfa761558ce6fa90168e14e1bd86 Mon Sep 17 00:00:00 2001 From: Michael Ochmann Date: Sun, 26 Feb 2023 17:15:16 +0100 Subject: [PATCH 14/15] code quality: made unused switch cases more obvious --- src/QR.cpp | 2 +- src/QR.hpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/QR.cpp b/src/QR.cpp index 7cee284..5be5764 100644 --- a/src/QR.cpp +++ b/src/QR.cpp @@ -31,7 +31,7 @@ void QR::render(Format format) { case Format::SVG : this->renderTyped(); break; case Format::JPG : this->renderTyped(); break; case Format::BMP : this->renderTyped(); break; - case Format::END : break; + UNUSED_CASE(Format::END); } } diff --git a/src/QR.hpp b/src/QR.hpp index edcc80d..df9f4e7 100644 --- a/src/QR.hpp +++ b/src/QR.hpp @@ -6,6 +6,8 @@ #include "qrcodegen.hpp" #include "Renderer.hpp" +#define UNUSED_CASE(name) case name: break; + namespace massivedynamic { typedef uint32_t Color; From f9654cc253fb112e36b011db8767434125c6fff9 Mon Sep 17 00:00:00 2001 From: Michael Ochmann Date: Sun, 26 Feb 2023 20:28:50 +0100 Subject: [PATCH 15/15] readme: added basic project information * about usage * about building * about ready to use binaries --- README.md | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/README.md b/README.md index 9404d84..16a25a1 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,54 @@ ![qr](docs/logo_qr.svg) +## Usage +This software currently supports outputting to the following formats: + +* svg +* PNG +* JPG +* Bitmap + +or directly to the console, as kind of an *"ascii art"*. +after installing via `make install`, you can read more in the man page by calling `man qr`. +### Options +``` + -f --format output file format. can be one of "cli, png, svg, jpg, bmp" + -h --help show this help + -i --input take data from this argument instead of stdin + -o --output output file name without extension + -s --size desired output file size in pixels + -t --type output QR code type. can be one of "small, medium, large" + -v --version shows version info +``` + +### Examples +```bash +bash> qr -i "this is from parameter" -f png -s 512 -o my_qrcode_file + +bash> echo "this is from stdin" | qr -t small -f png -s 512 -o my_qrcode_file_with_low_ecc +``` + +## Installation +You can just pick the newest version from the ["Releases" page][releases], or build the software yourself. + +### Building +You will need: +* cmake +* make +* a modern C++ compiler that supports `c++20` + +```bash +# clone the repo +bash ~/> git clone https://git.mike-ochmann.de/MassiveDynamic/qr.git +# move into the repository +bash ~/> cd qr +# create the build directory and move into it +bash ~/qr> mkdir build && cd build +# run `cmake` +bash ~/qr/build> cmake .. +# make and install the software +bash ~/qr/build> make install +``` + +[releases]: releases/ \ No newline at end of file