Compare commits

...

11 Commits

Author SHA1 Message Date
Michael Ochmann d56b9ef2b2 Merge pull request 'Release Version 1.1.0' (#1) from feature/v1.1.0 into master 2 years ago
Michael Ochmann f9654cc253 readme: added basic project information 2 years ago
Michael Ochmann 425a0f0b66 code quality: made unused switch cases more obvious 2 years ago
Michael Ochmann 4d7a7bfd69 code quality: making all the pixel based renderers `final` 2 years ago
Michael Ochmann d3850939cd code quality: getting rid of `std::vector` of all the renderers 2 years ago
Michael Ochmann 98b1cddd79 code quality: deleted copy & move constructors 2 years ago
Michael Ochmann 576bdf8430 code quality: more code cleanup 2 years ago
Michael Ochmann 6f3827427b PixelRenderer: now respecting the specified output size exactly 2 years ago
Michael Ochmann cfc0d276dc SVGRenderer: removed redundant `std::stringstream` 2 years ago
Michael Ochmann 61f308fb03 fixed `ARCH` detection in `cmake` 2 years ago
Michael Ochmann 2a8e54610b added man page support 2 years ago
  1. 22
      CMakeLists.txt
  2. 51
      README.md
  3. 86
      main.cpp
  4. 47
      src/QR.cpp
  5. 27
      src/QR.hpp
  6. 5
      src/Renderer.hpp
  7. 2
      src/renderers/BMPRenderer.hpp
  8. 2
      src/renderers/ConsoleRenderer.hpp
  9. 2
      src/renderers/JPGRenderer.hpp
  10. 2
      src/renderers/PNGRenderer.hpp
  11. 19
      src/renderers/PixelRenderer.hpp
  12. 10
      src/renderers/SVGRenderer.hpp

@ -2,6 +2,12 @@ cmake_minimum_required(VERSION 3.14...3.25)
set (CXX_STANDARD 23) set (CXX_STANDARD 23)
project(
qr
VERSION 1.1.0
LANGUAGES CXX
)
if (DEFINED ENV{ARCH}) if (DEFINED ENV{ARCH})
set(ARCH $ENV{ARCH}) set(ARCH $ENV{ARCH})
else() else()
@ -10,12 +16,6 @@ endif()
set(EXE_NAME "qr_${CMAKE_PROJECT_VERSION}-${ARCH}") set(EXE_NAME "qr_${CMAKE_PROJECT_VERSION}-${ARCH}")
project(
qr
VERSION 1.1.0
LANGUAGES CXX
)
message(STATUS "ARCH:: ${ARCH}") message(STATUS "ARCH:: ${ARCH}")
configure_file(main.cpp main_substitute.cpp) configure_file(main.cpp main_substitute.cpp)
@ -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_compile_options(qr PUBLIC -Wall -std=c++20 -arch ${ARCH})
target_include_directories(qr PRIVATE src lib) 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(TARGETS qr RUNTIME DESTINATION bin)
install(PROGRAMS build/${EXE_NAME} DESTINATION bin RENAME qr) install(PROGRAMS build/${EXE_NAME} DESTINATION bin RENAME qr)
install(FILES build/qr.7.gz DESTINATION /usr/local/share/man/man7/)

@ -3,3 +3,54 @@
![qr](docs/logo_qr.svg) ![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/

@ -3,6 +3,7 @@
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include "qrcodegen.hpp"
#include "QR.hpp" #include "QR.hpp"
#define UNUSED(var) (void) var; #define UNUSED(var) (void) var;
@ -12,20 +13,40 @@ constexpr const char* VERSION = "@qr_VERSION@";
void printHelp() { void printHelp() {
std::string helpText = R"EOF( std::string helpText = R"EOF(
(C) 2023, MikO <miko@massivedynamic.eu>
a tool for generating QR codes qr is a tool for generating QR codes from the commandline
Released under MIT license.
Usage: qr [OPTION]... --input "data to encapsulate"
Usage:
-f --format output file format. can be one of "cli, png, svg, jpg, bmp" *Caveats*
-h --help show this help With no `--input` parameter defined, read STDIN
-i --input take data from this argument instead of stdin
-o --output output file name without extension In pixel based renderers (i.e. PNG), the output code may not be exactly
-s --size desired output file size in pixels centered, as the amount of segment may not correspond to the specified
-t --type output QR code type. can be one of "small, medium, large" output size.
In pixel based renderers (i.e. PNG), the output size may not be exactly as Options:
specified but be the nearest multiple of the actual cell size for the QR code. -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 <miko@massivedynamic.eu>
*License*
Released under MIT license
Report bugs and issues at out issue tracker:
https://git.mike-ochmann.de/MassiveDynamic/qr/issues
)EOF"; )EOF";
std::cout << "qr " << VERSION; std::cout << "qr " << VERSION;
std::cout << helpText << std::endl; std::cout << helpText << std::endl;
@ -37,26 +58,27 @@ int main(int argc, char* argv[]) {
exit(1); exit(1);
} }
const option options[] = { const option options[] = {
{"help", no_argument, nullptr, 'h'}, {"help", no_argument, nullptr, 'h'},
{"input", required_argument, nullptr, 'i'}, {"input", required_argument, nullptr, 'i'},
{"output", required_argument, nullptr, 'o'}, {"output", required_argument, nullptr, 'o'},
{"size", required_argument, nullptr, 's'}, {"size", required_argument, nullptr, 's'},
{"type", required_argument, nullptr, 't'}, {"type", required_argument, nullptr, 't'},
{"format", required_argument, nullptr, 'f'}, {"format", required_argument, nullptr, 'f'},
{"version", no_argument, nullptr, 'v'},
{nullptr} {nullptr}
}; };
std::string outputFile; std::string outputFile;
std::string paramData;
size_t segmentSize = 0; size_t segmentSize = 0;
massivedynamic::Type type = massivedynamic::Type::MEDIUM;
massivedynamic::Format format = massivedynamic::Format::CONSOLE;
bool fromStdin = true; bool fromStdin = true;
bool anyParameterSet = false; bool anyParameterSet = false;
std::string paramData = ""; qrcodegen::QrCode::Ecc type = qrcodegen::QrCode::Ecc::LOW;
massivedynamic::Format format = massivedynamic::Format::CONSOLE;
for(;;) { for(;;) {
int index = -1; 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) if (result == -1)
break; break;
@ -77,12 +99,13 @@ int main(int argc, char* argv[]) {
case 't': { case 't': {
std::string value = optarg; std::string value = optarg;
if (value == "small") if (value == "small")
type = massivedynamic::Type::SMALL; type = qrcodegen::QrCode::Ecc::LOW;
else if (value == "medium") else if (value == "medium")
type = massivedynamic::Type::MEDIUM; type = qrcodegen::QrCode::Ecc::MEDIUM;
else if (value == "large") else if (value == "large")
type = massivedynamic::Type::LARGE; type = qrcodegen::QrCode::Ecc::HIGH;
else { else {
std::cerr << "ERROR: type (-t, --type) has to be one of 'small', 'medium' or 'large'" << std::endl;
printHelp(); printHelp();
return 1; return 1;
} }
@ -112,6 +135,10 @@ int main(int argc, char* argv[]) {
paramData = optarg; paramData = optarg;
break; break;
} }
case 'v': {
std::cout << "qr " << VERSION << std::endl;
exit(0);
}
case 'h': case 'h':
default: default:
printHelp(); printHelp();
@ -152,7 +179,8 @@ int main(int argc, char* argv[]) {
return 1; return 1;
} }
std::unique_ptr<massivedynamic::QR> qr = std::make_unique<massivedynamic::QR>(data.str(), outputFile, segmentSize, type, format); std::unique_ptr<massivedynamic::QR> qr = std::make_unique<massivedynamic::QR>(data.str(), outputFile, segmentSize, type);
qr->render(format);
return 0; return 0;
} }

@ -1,8 +1,8 @@
#include <iostream> #include <iostream>
#include <vector> #include <vector>
#include <cassert>
#include "QR.hpp" #include "QR.hpp"
#include "qrcodegen.hpp"
#include "renderers/ConsoleRenderer.hpp" #include "renderers/ConsoleRenderer.hpp"
#include "renderers/PNGRenderer.hpp" #include "renderers/PNGRenderer.hpp"
@ -12,40 +12,27 @@
namespace massivedynamic { 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, qrcodegen::QrCode::Ecc type) : outputFile(std::move(outputFile)),
qr(qrcodegen::QrCode::encodeText(data.c_str(), type)), size(size) {
qrcodegen::QrCode::Ecc errorCorrectionLevel = qrcodegen::QrCode::Ecc::HIGH; // this is inherently stupid, but "qrcodegen::QrCode" does not give access to the
// `segments` vector member and the class itself is marked final.
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);
static_assert(FormatLength == 5, "exhaustive formats: did you miss to add something here?");
this->renderers.insert({Format::CONSOLE, std::make_unique<ConsoleRenderer>(this->pixels, qr.getSize(), size)});
this->renderers.insert({Format::PNG, std::make_unique<PNGRenderer>(this->pixels, qr.getSize(), size)});
this->renderers.insert({Format::SVG, std::make_unique<SVGRenderer>(this->pixels, qr.getSize(), size)});
this->renderers.insert({Format::JPG, std::make_unique<JPGRenderer>(this->pixels, qr.getSize(), size)});
this->renderers.insert({Format::BMP, std::make_unique<BMPRenderer>(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 y = 0; y < qr.getSize(); y++) {
for (int x = 0; x < qr.getSize(); x++) for (int x = 0; x < qr.getSize(); x++)
this->pixels.push_back(qr.getModule(x, y)); this->pixels.push_back(qr.getModule(x, y));
} }
this->renderers.at(format)->render(this->outputFile); }
void QR::render(Format format) {
static_assert(FormatLength == 5, "exhaustive formats: did you miss to add something here?");
switch(format) {
case Format::CONSOLE : this->renderTyped<ConsoleRenderer>(); break;
case Format::PNG : this->renderTyped<PNGRenderer>(); break;
case Format::SVG : this->renderTyped<SVGRenderer>(); break;
case Format::JPG : this->renderTyped<JPGRenderer>(); break;
case Format::BMP : this->renderTyped<BMPRenderer>(); break;
UNUSED_CASE(Format::END);
}
} }
} }

@ -2,21 +2,15 @@
#include <vector> #include <vector>
#include <string> #include <string>
#include <memory>
#include <unordered_map>
#include "qrcodegen.hpp" #include "qrcodegen.hpp"
#include "Renderer.hpp" #include "Renderer.hpp"
#define UNUSED_CASE(name) case name: break;
namespace massivedynamic { namespace massivedynamic {
typedef uint32_t Color; typedef uint32_t Color;
enum class Type {
SMALL,
MEDIUM,
LARGE
};
enum class Format { enum class Format {
CONSOLE, CONSOLE,
SVG, SVG,
@ -27,14 +21,25 @@ namespace massivedynamic {
}; };
constexpr size_t FormatLength = static_cast<size_t>(Format::END); constexpr size_t FormatLength = static_cast<size_t>(Format::END);
class QR { class QR {
private: private:
std::string outputFile; std::string outputFile;
std::vector<bool> pixels; std::vector<bool> pixels;
std::unordered_map<Format, std::unique_ptr<Renderer>> renderers; qrcodegen::QrCode qr;
size_t size;
public: 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, qrcodegen::QrCode::Ecc type);
QR(const QR&&) = delete;
QR(QR&) = delete;
~QR() = default;
void render(Format format);
template<typename T>
void renderTyped() {
T renderer = T(this->pixels, this->qr.getSize(), this->size);
renderer.render(this->outputFile);
}
}; };
} }

@ -11,7 +11,10 @@ class Renderer {
size_t targetSize; size_t targetSize;
public: public:
Renderer(const std::vector<bool>& pixels, size_t sourceSize, size_t targetSize) : pixels(pixels), sourceSize(sourceSize), targetSize(targetSize) {} Renderer(const std::vector<bool>& 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; virtual void render(const std::string& filename) = 0;
}; };

@ -6,7 +6,7 @@
namespace massivedynamic { namespace massivedynamic {
class BMPRenderer : public PixelRenderer { class BMPRenderer final : public PixelRenderer {
public: public:
BMPRenderer(const std::vector<bool>& pixels, size_t sourceSize, size_t targetSize) : PixelRenderer(pixels, sourceSize, targetSize) {} BMPRenderer(const std::vector<bool>& pixels, size_t sourceSize, size_t targetSize) : PixelRenderer(pixels, sourceSize, targetSize) {}

@ -21,7 +21,7 @@ class ConsoleRenderer : public Renderer {
std::cout << '\n'; std::cout << '\n';
} }
std::cout << std::string(border, '\n'); std::cout << std::string(border, '\n');
std::cout << std::endl; std::cout.flush();
} }
}; };

@ -6,7 +6,7 @@
namespace massivedynamic { namespace massivedynamic {
class JPGRenderer : public PixelRenderer { class JPGRenderer final: public PixelRenderer {
public: public:
JPGRenderer(const std::vector<bool>& pixels, size_t sourceSize, size_t targetSize) : PixelRenderer(pixels, sourceSize, targetSize) {} JPGRenderer(const std::vector<bool>& pixels, size_t sourceSize, size_t targetSize) : PixelRenderer(pixels, sourceSize, targetSize) {}

@ -6,7 +6,7 @@
namespace massivedynamic { namespace massivedynamic {
class PNGRenderer : public PixelRenderer { class PNGRenderer final: public PixelRenderer {
public: public:
PNGRenderer(const std::vector<bool>& pixels, size_t sourceSize, size_t targetSize) : PixelRenderer(pixels, sourceSize, targetSize) {} PNGRenderer(const std::vector<bool>& pixels, size_t sourceSize, size_t targetSize) : PixelRenderer(pixels, sourceSize, targetSize) {}

@ -2,6 +2,7 @@
#include <math.h> #include <math.h>
#include <iostream> #include <iostream>
#include <cassert>
#include "Renderer.hpp" #include "Renderer.hpp"
#define STB_IMAGE_WRITE_IMPLEMENTATION #define STB_IMAGE_WRITE_IMPLEMENTATION
@ -13,18 +14,16 @@ namespace massivedynamic {
class PixelRenderer : public Renderer { class PixelRenderer : public Renderer {
protected: protected:
size_t pixelSize; size_t pixelSize;
size_t border;
std::vector<Color> bitmap; std::vector<Color> bitmap;
void drawPixelScaled(size_t x, size_t y, Color color) { void drawPixelScaled(size_t x, size_t y, Color color) {
size_t pixelSize = this->pixelSize; size_t pixelSize = this->pixelSize;
if (pixelSize < 1) { assert(pixelSize > 1 && "ERROR: output file size is too small");
std::cerr << "ERROR: output file size is too small" << std::endl;
exit(1);
}
size_t absoluteX = pixelSize * x + pixelSize; size_t absoluteX = pixelSize * x + this->border;
size_t absoluteY = pixelSize * y + pixelSize; size_t absoluteY = pixelSize * y + this->border;
for (size_t localY = absoluteY; localY < absoluteY + pixelSize; localY++) { for (size_t localY = absoluteY; localY < absoluteY + pixelSize; localY++) {
for (size_t localX = absoluteX; localX < absoluteX + pixelSize; localX++) { for (size_t localX = absoluteX; localX < absoluteX + pixelSize; localX++) {
@ -40,15 +39,13 @@ namespace massivedynamic {
PixelRenderer(const std::vector<bool>& pixels, size_t sourceSize, size_t targetSize) : Renderer(pixels, sourceSize, targetSize), pixelSize(0) { PixelRenderer(const std::vector<bool>& pixels, size_t sourceSize, size_t targetSize) : Renderer(pixels, sourceSize, targetSize), pixelSize(0) {
this->targetSize = this->targetSize == 0 ? (sourceSize + 2) * 2 : this->targetSize; this->targetSize = this->targetSize == 0 ? (sourceSize + 2) * 2 : this->targetSize;
this->pixelSize = round(static_cast<float>(this->targetSize) / static_cast<float>(this->sourceSize + 2));
// here we make shure, `targetSize` will be a multiple of `sourceSize` this->border = round(static_cast<float>(this->targetSize - this->pixelSize * this->sourceSize) / 2.0f);
this->pixelSize = round(static_cast<float>(this->targetSize) / static_cast<float>(this->sourceSize + 2));
this->targetSize = (this->sourceSize + 2) * this->pixelSize;
this->bitmap = std::vector<Color>(this->targetSize * this->targetSize, Colors::WHITE); this->bitmap = std::vector<Color>(this->targetSize * this->targetSize, Colors::WHITE);
} }
void generateBuffer() { virtual void generateBuffer() {
for (size_t y = 0; y < sourceSize; y++) { for (size_t y = 0; y < sourceSize; y++) {
for (size_t x = 0; x < sourceSize; x++) { for (size_t x = 0; x < sourceSize; x++) {
if (!pixels.at(y * sourceSize + x)) if (!pixels.at(y * sourceSize + x))

@ -17,7 +17,8 @@ namespace massivedynamic {
this->targetSize = sourceSize * (2 * SVGRenderer::BORDER_WIDTH); this->targetSize = sourceSize * (2 * SVGRenderer::BORDER_WIDTH);
} }
virtual void render(const std::string& filename) override { virtual void render(const std::string& filename) override {
std::stringstream file; std::ofstream file;
file.open(filename + ".svg");
std::string base = R"EOF(<?xml version="1.0" encoding="utf-8"?> std::string base = R"EOF(<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Massive Dynamic qr, massivedynamic::SVGRenderer --> <!-- Generator: Massive Dynamic qr, massivedynamic::SVGRenderer -->
@ -41,12 +42,9 @@ namespace massivedynamic {
file << "</svg>" << std::endl; file << "</svg>" << std::endl;
std::ofstream outFile; file.close();
outFile.open(filename + ".svg");
outFile << file.str();
outFile.close();
} }
}; };
} }

Loading…
Cancel
Save