From 3dc5da6fc8087c47f58baad2756ea7b0a4904927 Mon Sep 17 00:00:00 2001 From: Moritz Martinius Date: Thu, 11 Sep 2025 10:02:43 +0200 Subject: [PATCH] Fix Monochrome class with new data structure, unit tests. There is work to be done still --- .clang-tidy | 1 - .vscode/c_cpp_properties.json | 2 +- .vscode/launch.json | 2 +- .vscode/settings.json | 6 - meson.build | 29 +++- src/P700Printer.cpp | 76 +++++---- src/P700Printer.hpp | 1 + src/PtouchPrint.cpp | 4 +- src/graphics/Bitmap.cpp | 40 ++--- src/graphics/Bitmap.hpp | 16 +- src/graphics/Monochrome.cpp | 199 ++++++++++++++++++++-- src/graphics/Monochrome.hpp | 43 ++++- src/hello.txt | 7 - src/interface/IPrinterDriver.hpp | 2 + tests/bitmap_test/bitmap_test.cpp | 23 +-- tests/monochrome_test/monochrome_test.cpp | 116 +++++++++++-- 16 files changed, 436 insertions(+), 131 deletions(-) delete mode 100644 src/hello.txt diff --git a/.clang-tidy b/.clang-tidy index 63d1f0d..6b7a80a 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -2,7 +2,6 @@ Checks: "clang-diagnostic-*,clang-analyzer-*,cppcoreguidelines-*,modernize-*,-modernize-use-trailing-return-type" WarningsAsErrors: true HeaderFilterRegex: "" -AnalyzeTemporaryDtors: false FormatStyle: google CheckOptions: - key: cert-dcl16-c.NewSuffixes diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 84aeb11..eb6a4b8 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -4,7 +4,7 @@ "name": "Linux", "compilerPath": "/usr/bin/clang", "cStandard": "c11", - "cppStandard": "c++17", + "cppStandard": "c++20", "compileCommands": "${workspaceFolder}/builddir/compile_commands.json", "browse": { "path": ["${workspaceFolder}"] diff --git a/.vscode/launch.json b/.vscode/launch.json index 5ccd544..a958035 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -14,7 +14,7 @@ "cwd": "${fileDirname}", "environment": [], "externalConsole": false, - "MIMode": "gdb", + "MIMode": "lldb", "setupCommands": [ { "description": "Automatische Strukturierung und Einrückung für \"gdb\" aktivieren", diff --git a/.vscode/settings.json b/.vscode/settings.json index eef27fa..bf2c87e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -83,11 +83,5 @@ "charconv": "cpp", "*.ipp": "cpp" }, - "clang-tidy.buildPath": "builddir/", "clangd.onConfigChanged": "restart", - "C_Cpp.default.compileCommands": "/home/moritz/src/ptouch-prnt/builddir/compile_commands.json", - "gcovViewer.buildDirectories": [ - "/home/moritz/Projekte/ptouch-prnt/builddir" - ], - "C_Cpp.default.configurationProvider": "mesonbuild.mesonbuild" } diff --git a/meson.build b/meson.build index eff703f..4c11044 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,22 @@ -project('ptprnt', 'cpp', - version: 'v0.1.0-'+run_command('git', 'rev-parse', '--short', 'HEAD', check: true).stdout().strip(), +project( + 'ptprnt', + 'cpp', + version: 'v0.1.0-' + run_command( + 'git', + 'rev-parse', + '--short', + 'HEAD', + check: true, + ).stdout().strip(), license: 'GPLv3', - default_options : ['c_std=c11', 'cpp_std=c++2a', 'b_sanitize=none', 'b_lto=true', 'b_lto_mode=thin', 'b_thinlto_cache=true'] + default_options: [ + 'c_std=c11', + 'cpp_std=c++20', + 'b_sanitize=none', + 'b_lto=true', + 'b_lto_mode=thin', + 'b_thinlto_cache=true', + ], ) usb_dep = dependency('libusb-1.0') @@ -21,13 +36,13 @@ incdir = include_directories('src') subdir('src') ptprnt_exe = executable( - 'ptprnt', + 'ptprnt', 'src/main.cpp', install: true, - dependencies : [usb_dep, log_dep, fmt_dep, pangocairo_dep, cli11_dep], + dependencies: [usb_dep, log_dep, fmt_dep, pangocairo_dep, cli11_dep], include_directories: incdir, sources: [ptprnt_srcs], - cpp_args : ['-DPROJ_VERSION="'+meson.project_version()+'"'], + cpp_args: ['-DPROJ_VERSION="' + meson.project_version() + '"'], ) @@ -40,4 +55,4 @@ if not gtest_dep.found() error('MESON_SKIP_TEST: gtest not installed.') endif -subdir('tests') \ No newline at end of file +subdir('tests') diff --git a/src/P700Printer.cpp b/src/P700Printer.cpp index fec1b7a..dd80361 100644 --- a/src/P700Printer.cpp +++ b/src/P700Printer.cpp @@ -1,4 +1,4 @@ -/* +/* ptrnt - print labels on linux Copyright (C) 2023 Moritz Martinius @@ -44,7 +44,7 @@ const PrinterInfo P700Printer::mInfo = {.driverName = "P700", .pixelLines = 128}; P700Printer::~P700Printer() { - detachUsbDevice(); + P700Printer::detachUsbDevice(); if (mUsbHndl) { mUsbHndl->close(); } @@ -92,6 +92,7 @@ bool P700Printer::attachUsbDevice(std::shared_ptr usbHnd } if (!usbHndl->detachKernelDriver(0)) { + spdlog::error("Device is already in use or couldn't be detached from kernel: {}", usbHndl->getLastErrorString()); return false; @@ -119,53 +120,66 @@ bool P700Printer::detachUsbDevice() { } bool P700Printer::printBitmap(const graphics::Bitmap& bitmap) { + // Convert bitmap to MonochromeData and delegate to printMonochromeData + auto pixels = bitmap.getPixelsCpy(); + auto mono = graphics::Monochrome(pixels, bitmap.getWidth(), bitmap.getHeight()); + auto monoData = mono.getMonochromeData(); + + return printMonochromeData(monoData); +} + +bool P700Printer::printMonochromeData(const graphics::MonochromeData& data) { #ifdef DRYRUN spdlog::debug("DRYRUN enabled"); - for (unsigned int lineNo = 0; lineNo < bitmap.getHeight(); lineNo++) { - auto line = bitmap.getLine(lineNo); - auto monoLine = graphics::Monochrome(*line); - monoLine.visualize(); - } + data.visualize(); #endif - + send(p700::commands::RASTER_START); std::vector rastercmd(4); rastercmd[0] = 0x47; rastercmd[1] = 0x00; // size +1 rastercmd[2] = 0x00; rastercmd[3] = 0x00; // size -1 - for (unsigned int i = 0; i < bitmap.getWidth(); i++) { - auto bmcol = bitmap.getCol(i); - if (!bmcol) { - spdlog::error("Out of bounds bitmap access"); - break; + + // Process data column by column for the printer + for (uint32_t col = 0; col < data.width; col++) { + std::vector columnData; + + // Extract column data bit by bit + for (uint32_t row = 0; row < data.height; row += 8) { + uint8_t byte = 0; + for (int bit = 0; bit < 8 && (row + bit) < data.height; bit++) { + if (data.getBit(col, row + bit)) { + byte |= (1 << (7 - bit)); + } + } + columnData.push_back(byte); } - auto monocol = graphics::Monochrome(*bmcol); - auto col = monocol.get(); - - std::vector buf(0); + + std::vector buf; buf.insert(buf.begin(), rastercmd.begin(), rastercmd.end()); - buf.insert(std::next(buf.begin(), 4), col.begin(), col.end()); - - buf[1] = col.size() + 1; - buf[3] = col.size() - 1; + buf.insert(std::next(buf.begin(), 4), columnData.begin(), columnData.end()); + buf[1] = columnData.size() + 1; + buf[3] = columnData.size() - 1; + if (!send(buf)) { spdlog::error("Error sending buffer to printer"); break; - }; + } } - + send(p700::commands::EJECT); return true; } bool P700Printer::printLabel(std::unique_ptr label) { - // not quite sure if I should stack allocate Bitmap, but data is held on the heap anyway (std::vector). - auto bm = graphics::Bitmap(label->getWidth(), label->getHeight()); + // Convert label directly to MonochromeData + auto pixels = label->getRaw(); + auto mono = graphics::Monochrome(pixels, label->getWidth(), label->getHeight()); + auto monoData = mono.getMonochromeData(); + spdlog::debug("Label has {}x{}px size", label->getWidth(), label->getHeight()); - bm.setPixels(label->getRaw()); - printBitmap(bm); - return true; + return printMonochromeData(monoData); } bool P700Printer::print() { @@ -182,7 +196,7 @@ bool P700Printer::send(const std::vector& data) { return false; } - int tx = 0; + size_t tx = 0; #ifndef DRYRUN if (!mUsbHndl->bulkTransfer(0x02, data, &tx, 0)) { @@ -194,7 +208,7 @@ bool P700Printer::send(const std::vector& data) { spdlog::trace("USB raw data(len {}): {}", data.size(), spdlog::to_hex(data)); #endif - if (tx != static_cast(data.size())) { + if (tx != data.size()) { spdlog::error("Could not transfer all data via USB bulk transfer. Only sent {} of {} bytes", tx, data.size()); return false; } @@ -208,4 +222,4 @@ bool P700Printer::init() { cmd[101] = 0x40; /* @ */ return send(cmd); } -} // namespace ptprnt::printer \ No newline at end of file +} // namespace ptprnt::printer diff --git a/src/P700Printer.hpp b/src/P700Printer.hpp index 5c96c3b..1309db6 100644 --- a/src/P700Printer.hpp +++ b/src/P700Printer.hpp @@ -71,6 +71,7 @@ class P700Printer : public ::ptprnt::IPrinterDriver { bool attachUsbDevice(std::shared_ptr usbHndl) override; bool detachUsbDevice() override; bool printBitmap(const graphics::Bitmap& bitmap) override; + bool printMonochromeData(const graphics::MonochromeData& data) override; bool printLabel(const std::unique_ptr label) override; bool print() override; diff --git a/src/PtouchPrint.cpp b/src/PtouchPrint.cpp index 5ac960e..4641b98 100644 --- a/src/PtouchPrint.cpp +++ b/src/PtouchPrint.cpp @@ -150,10 +150,10 @@ int PtouchPrint::run() { } label->create(labelText); label->writeToPng("./testlabel.png"); - if (!printer->printLabel(std::move(label))) { + /*if (!printer->printLabel(std::move(label))) { spdlog::error("An error occured while printing"); return -1; - } + }*/ return 0; } diff --git a/src/graphics/Bitmap.cpp b/src/graphics/Bitmap.cpp index 9254d8d..1bbcb13 100644 --- a/src/graphics/Bitmap.cpp +++ b/src/graphics/Bitmap.cpp @@ -21,8 +21,8 @@ #include -#include -#include +#include +#include #include namespace ptprnt::graphics { @@ -56,35 +56,29 @@ template } template -[[nodiscard]] std::optional> Bitmap::getLine(uint16_t line) const { - if (line >= mHeight) { - // out of bound - return std::nullopt; +[[nodiscard]] std::vector Bitmap::getLine(const uint16_t lineNo) const { + if (lineNo >= mHeight) { + throw(std::out_of_range("Line is out of range!")); } - - auto lineStart = mPixels.begin() + (line * mWidth); - auto lineEnd = mPixels.begin() + ((line + 1) * mWidth); + auto lineStart = mPixels.begin() + (lineNo * mWidth); + auto lineEnd = mPixels.begin() + ((lineNo + 1) * mWidth); return std::vector(lineStart, lineEnd); } // TODO: I guess this is borked template -[[nodiscard]] std::optional> Bitmap::getCol(uint16_t col) const { - if (col >= mWidth) { - // out of bound - return std::nullopt; +[[nodiscard]] std::vector Bitmap::getCol(const uint16_t colNo) const { + if (colNo >= mWidth) { + throw(std::out_of_range("Col is out of range!")); } - // first pixel is always beginning of the col - std::vector colPixels(mHeight); - auto it = std::next(mPixels.begin(), col); - - for (auto& colElement : colPixels) { - colElement = *it; - std::advance(it, mWidth); + std::vector col{}; + col.reserve(mHeight); + for (size_t i{0}; i <= mPixels.size(); i++) { + if (i % mWidth == colNo) { + col.push_back(mPixels[i]); + } } - - return colPixels; + return col; } - } // namespace ptprnt::graphics \ No newline at end of file diff --git a/src/graphics/Bitmap.hpp b/src/graphics/Bitmap.hpp index 1a2a84a..11a2b46 100644 --- a/src/graphics/Bitmap.hpp +++ b/src/graphics/Bitmap.hpp @@ -19,19 +19,17 @@ #pragma once -#include - #include #include -#include +#include #include namespace ptprnt::graphics { -typedef uint8_t ALPHA8; // Alpha only, 8 bit per pixel -typedef uint32_t RGBX8; // RGB, least significant byte unused, 8 bit per channel -typedef uint32_t RGBA8; // RGB, least significant byte alpha, 8 bit per channel -typedef uint32_t ARGB8; // RGB, most significant byte alpha, 8 bit per channel +using ALPHA8 = std::uint8_t; // Alpha only, 8 bit per pixel +using RGBX8 = std::uint32_t; // RGB, least significant byte unused, 8 bit per channel +using RGBA8 = std::uint32_t; // RGB, least significant byte alpha, 8 bit per channel +using ARGB8 = std::uint32_t; // RGB, most significant byte alpha, 8 bit per channel template class Bitmap { @@ -48,8 +46,8 @@ class Bitmap { [[nodiscard]] uint16_t getHeight() const; bool setPixels(const std::vector& pixels); [[nodiscard]] std::vector getPixelsCpy() const; - [[nodiscard]] std::optional> getLine(uint16_t line) const; - [[nodiscard]] std::optional> getCol(uint16_t col) const; + [[nodiscard]] std::vector getLine(uint16_t line) const; + [[nodiscard]] std::vector getCol(uint16_t col) const; void visualize() const; private: diff --git a/src/graphics/Monochrome.cpp b/src/graphics/Monochrome.cpp index 496d549..9de0f2c 100644 --- a/src/graphics/Monochrome.cpp +++ b/src/graphics/Monochrome.cpp @@ -25,7 +25,11 @@ #include namespace ptprnt::graphics { -Monochrome::Monochrome(const std::vector& grayscale) : mPixels(std::move(grayscale)) {} +Monochrome::Monochrome(const std::vector& grayscale, uint32_t width, uint32_t height) + : mPixels(grayscale), mWidth(width), mHeight(height) {} + +Monochrome::Monochrome(const std::span grayscale, uint32_t width, uint32_t height) + : mPixels(grayscale.begin(), grayscale.end()), mWidth(width), mHeight(height) {} void Monochrome::setThreshold(uint8_t threshhold) { mThreshhold = threshhold; @@ -36,23 +40,30 @@ void Monochrome::invert(bool shouldInvert) { } std::vector Monochrome::get() { - std::vector outPixels( - (static_cast((mPixels.size() / 8)) + (std::floor(mPixels.size() % 8 + 0.9)))); - unsigned int outIndex = 0; + // Calculate output size for packed format: (width + 7) / 8 bytes per row + uint32_t stride = (mWidth + 7) / 8; + size_t outputSize = stride * mHeight; + std::vector outPixels(outputSize, 0); - for (unsigned int byteNo = 0; byteNo < mPixels.size(); byteNo += 8) { - for (unsigned int bitNo = 0; bitNo <= 7 && (byteNo + bitNo < mPixels.size()); bitNo++) { - if (mPixels[byteNo + bitNo] > mThreshhold) { - outPixels[outIndex] |= (1 << (7 - bitNo)); - } else { - outPixels[outIndex] &= ~(1 << (7 - bitNo)); + // Pack pixels row by row for correct 2D layout + for (uint32_t y = 0; y < mHeight; ++y) { + for (uint32_t x = 0; x < mWidth; ++x) { + size_t pixelIndex = y * mWidth + x; // Row-major index in input + size_t byteIndex = y * stride + x / 8; // Byte index in packed output + size_t bitIndex = 7 - (x % 8); // MSB first + + // Convert grayscale pixel to bit based on threshold + bool pixelOn = mPixels[pixelIndex] > mThreshhold; + if (mShouldInvert) { + pixelOn = !pixelOn; + } + + if (pixelOn) { + outPixels[byteIndex] |= (1 << bitIndex); } } - if (mShouldInvert) { - outPixels[outIndex] = ~outPixels[outIndex]; - } - outIndex++; } + return outPixels; } @@ -71,4 +82,164 @@ void Monochrome::visualize() { std::cout << std::endl; } +MonochromeData Monochrome::getMonochromeData() { + auto processedBytes = get(); + // Calculate stride based on packed monochrome data (1 bit per pixel, 8 pixels per byte) + auto stride = static_cast((mWidth + 7) / 8); + return {std::move(processedBytes), stride, Orientation::LANDSCAPE, mWidth, mHeight}; +} + +// MonochromeData transformation methods implementation +void MonochromeData::transformTo(Orientation targetOrientation) { + if (orientation == targetOrientation) { + return; // No transformation needed + } + + auto rotatedData = createRotatedData(targetOrientation); + bytes = std::move(rotatedData); + + // Update dimensions and stride based on rotation + switch (targetOrientation) { + case Orientation::PORTRAIT: + case Orientation::PORTRAIT_FLIPPED: + // Swap width and height for portrait orientations + std::swap(width, height); + stride = (width + 7) / 8; // Recalculate stride for new width + break; + case Orientation::LANDSCAPE: + case Orientation::LANDSCAPE_FLIPPED: + // Keep original stride calculation + stride = (width + 7) / 8; + break; + } + + orientation = targetOrientation; +} + +bool MonochromeData::getBit(uint32_t x, uint32_t y) const { + if (x >= width || y >= height) { + return false; + } + + uint32_t byteIndex = y * stride + x / 8; + uint32_t bitIndex = 7 - (x % 8); // MSB first + + if (byteIndex >= bytes.size()) { + return false; + } + + return (bytes[byteIndex] >> bitIndex) & 1; +} + +void MonochromeData::setBit(uint32_t x, uint32_t y, bool value) { + if (x >= width || y >= height) { + return; + } + + uint32_t byteIndex = y * stride + x / 8; + uint32_t bitIndex = 7 - (x % 8); // MSB first + + if (byteIndex >= bytes.size()) { + return; + } + + if (value) { + bytes[byteIndex] |= (1 << bitIndex); + } else { + bytes[byteIndex] &= ~(1 << bitIndex); + } +} + +std::vector MonochromeData::createRotatedData(Orientation targetOrientation) const { + uint32_t newWidth, newHeight; + + // Determine new dimensions + switch (targetOrientation) { + case Orientation::PORTRAIT: + case Orientation::PORTRAIT_FLIPPED: + newWidth = height; + newHeight = width; + break; + case Orientation::LANDSCAPE: + case Orientation::LANDSCAPE_FLIPPED: + default: + newWidth = width; + newHeight = height; + break; + } + + uint32_t newStride = (newWidth + 7) / 8; + std::vector newBytes(newStride * newHeight, 0); + + // Create a temporary MonochromeData for the new image + MonochromeData tempData; + tempData.bytes = std::move(newBytes); + tempData.stride = newStride; + tempData.width = newWidth; + tempData.height = newHeight; + tempData.orientation = targetOrientation; + + // Copy pixels with appropriate transformation + for (uint32_t y = 0; y < height; ++y) { + for (uint32_t x = 0; x < width; ++x) { + bool pixel = getBit(x, y); + uint32_t newX, newY; + + switch (targetOrientation) { + case Orientation::LANDSCAPE: + newX = x; + newY = y; + break; + case Orientation::PORTRAIT: // 90 degrees clockwise + newX = height - 1 - y; + newY = x; + break; + case Orientation::LANDSCAPE_FLIPPED: // 180 degrees + newX = width - 1 - x; + newY = height - 1 - y; + break; + case Orientation::PORTRAIT_FLIPPED: // 270 degrees clockwise + newX = y; + newY = width - 1 - x; + break; + } + + tempData.setBit(newX, newY, pixel); + } + } + + return std::move(tempData.bytes); +} + +void MonochromeData::visualize() const { + std::cout << "MonochromeData visualization (" << width << "x" << height << ", orientation: "; + + switch (orientation) { + case Orientation::LANDSCAPE: + std::cout << "LANDSCAPE"; + break; + case Orientation::PORTRAIT: + std::cout << "PORTRAIT"; + break; + case Orientation::LANDSCAPE_FLIPPED: + std::cout << "LANDSCAPE_FLIPPED"; + break; + case Orientation::PORTRAIT_FLIPPED: + std::cout << "PORTRAIT_FLIPPED"; + break; + } + + std::cout << "):" << std::endl; + + // Print the image row by row + for (uint32_t y = 0; y < height; ++y) { + for (uint32_t x = 0; x < width; ++x) { + bool pixel = getBit(x, y); + std::cout << (pixel ? "█" : "."); + } + std::cout << std::endl; + } + std::cout << std::endl; +} + } // namespace ptprnt::graphics \ No newline at end of file diff --git a/src/graphics/Monochrome.hpp b/src/graphics/Monochrome.hpp index 9b7db86..f87f69a 100644 --- a/src/graphics/Monochrome.hpp +++ b/src/graphics/Monochrome.hpp @@ -20,22 +20,61 @@ #pragma once #include +#include +#include #include "graphics/Bitmap.hpp" namespace ptprnt::graphics { + +enum class Orientation { + LANDSCAPE = 0, // 0 degrees + PORTRAIT = 1, // 90 degrees clockwise + LANDSCAPE_FLIPPED = 2, // 180 degrees + PORTRAIT_FLIPPED = 3 // 270 degrees clockwise (90 counter-clockwise) +}; + +struct MonochromeData { + std::vector bytes; + uint32_t stride; + Orientation orientation; + uint32_t width; // Width in pixels + uint32_t height; // Height in pixels + + MonochromeData() : stride(0), orientation(Orientation::LANDSCAPE), width(0), height(0) {} + + MonochromeData(std::vector data, uint32_t stride_bytes, Orientation orient = Orientation::LANDSCAPE, + uint32_t w = 0, uint32_t h = 0) + : bytes(std::move(data)), stride(stride_bytes), orientation(orient), width(w), height(h) {} + + // Transform the image data to the target orientation + void transformTo(Orientation targetOrientation); + + // Visualize the monochrome data on stdout + void visualize() const; + + // Helper methods for orientation transformations + [[nodiscard]] bool getBit(uint32_t x, uint32_t y) const; + void setBit(uint32_t x, uint32_t y, bool value); + [[nodiscard]] std::vector createRotatedData(Orientation targetOrientation) const; +}; + class Monochrome { public: - Monochrome(const std::vector& grayscale); + Monochrome(const std::vector& grayscale, uint32_t width, uint32_t height); + Monochrome(const std::span grayscale, uint32_t width, uint32_t height); ~Monochrome() = default; void setThreshold(uint8_t); void invert(bool shouldInvert); void visualize(); std::vector get(); + MonochromeData getMonochromeData(); private: - const std::vector& mPixels; + std::vector mPixels; + uint32_t mWidth; + uint32_t mHeight; uint8_t mThreshhold = UINT8_MAX / 2; bool mShouldInvert = false; }; diff --git a/src/hello.txt b/src/hello.txt deleted file mode 100644 index a5916b9..0000000 --- a/src/hello.txt +++ /dev/null @@ -1,7 +0,0 @@ -{ - "text" : " Hello", - "font" : "FreeSans 32", - "single-paragraph" : true, - "alignment" : "center", - "height" : 0 -} diff --git a/src/interface/IPrinterDriver.hpp b/src/interface/IPrinterDriver.hpp index f199bc8..7a60462 100644 --- a/src/interface/IPrinterDriver.hpp +++ b/src/interface/IPrinterDriver.hpp @@ -23,6 +23,7 @@ #include #include "graphics/Bitmap.hpp" +#include "graphics/Monochrome.hpp" #include "graphics/interface/ILabel.hpp" #include "interface/IPrinterTypes.hpp" #include "libusbwrap/interface/IUsbDevice.hpp" @@ -40,6 +41,7 @@ class IPrinterDriver { virtual bool attachUsbDevice(std::shared_ptr usbHndl) = 0; virtual bool detachUsbDevice() = 0; virtual bool printBitmap(const graphics::Bitmap& bitmap) = 0; + virtual bool printMonochromeData(const graphics::MonochromeData& data) = 0; virtual bool printLabel(const std::unique_ptr label) = 0; virtual bool print() = 0; }; diff --git a/tests/bitmap_test/bitmap_test.cpp b/tests/bitmap_test/bitmap_test.cpp index 3185b0e..4e71d4f 100644 --- a/tests/bitmap_test/bitmap_test.cpp +++ b/tests/bitmap_test/bitmap_test.cpp @@ -22,7 +22,6 @@ #include #include -#include #include TEST(basic_test, Bitmap_createBitmapWithCertainSize_yieldsSpecifiedSize) { @@ -36,34 +35,28 @@ TEST(basic_test, Bitmap_createBitmapWithCertainSize_yieldsSpecifiedSize) { TEST(basic_test, Bitmap_getBitmapLineOutsideOfImage_yieldsNullopt) { auto bm = ptprnt::graphics::Bitmap(16, 8); // line 8 is out of bounds, count begins with 0 - auto outOfBoundsLine = bm.getLine(8); - ASSERT_EQ(std::nullopt, outOfBoundsLine); + EXPECT_ANY_THROW(auto outOfBoundsLine = bm.getLine(8)); } TEST(basic_test, Bitmap_getBitmapLineInsideOfImage_yieldsValidLineSize) { - auto bm = ptprnt::graphics::Bitmap(16, 8); - auto line = bm.getLine(7); - if (!line) { - FAIL() << "Returned line is invalid"; - } - auto lineSize = line->size(); + auto bm = ptprnt::graphics::Bitmap(16, 8); + auto line = bm.getLine(7); + auto lineSize = line.size(); ASSERT_EQ(16, lineSize); } TEST(basic_test, Bitmap_getBitmapColOutsideOfImage_yieldsNullopt) { auto bm = ptprnt::graphics::Bitmap(16, 8); // col 16 is out of bounds, count begins with 0 - auto outOfBoundsCol = bm.getCol(16); - ASSERT_EQ(std::nullopt, outOfBoundsCol); + + EXPECT_ANY_THROW(auto outOfBoundsCol = bm.getCol(16)); } TEST(basic_test, Bitmap_getBitmapColInsideOfImage_yieldsValidColSize) { auto bm = ptprnt::graphics::Bitmap(16, 8); auto col = bm.getCol(15); - if (!col) { - FAIL() << "Returned Col is invalid"; - } - auto colSize = col->size(); + + auto colSize = col.size(); ASSERT_EQ(8, colSize); } diff --git a/tests/monochrome_test/monochrome_test.cpp b/tests/monochrome_test/monochrome_test.cpp index 43b8342..93a153f 100644 --- a/tests/monochrome_test/monochrome_test.cpp +++ b/tests/monochrome_test/monochrome_test.cpp @@ -22,22 +22,22 @@ #include TEST(basic_test, Monochrome_convertGrayscale_yieldsMonochrome) { - const std::vector pixels({0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00}); + const std::vector pixels( + {0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00}); const std::vector expected({0b10101010, 0b10101010}); - auto mono = ptprnt::graphics::Monochrome(pixels); + auto mono = ptprnt::graphics::Monochrome(pixels, 16, 1); auto out = mono.get(); EXPECT_EQ(out, expected); } TEST(basic_test, Monochrome_convertInvertedGrayscale_yieldsInvertedMonochrome) { - const std::vector pixels({0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00}); + const std::vector pixels( + {0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00}); const std::vector expected({0b01010101, 0b01010101}); - auto mono = ptprnt::graphics::Monochrome(pixels); + auto mono = ptprnt::graphics::Monochrome(pixels, 16, 1); mono.invert(true); auto out = mono.get(); @@ -45,11 +45,11 @@ TEST(basic_test, Monochrome_convertInvertedGrayscale_yieldsInvertedMonochrome) { } TEST(basic_test, Monochrome_convertWithCustomThreshhold_yieldsMonochromeRespectingThreshhold) { - const std::vector pixels({0x0F, 0x11, 0x0F, 0x11, 0x0F, 0x11, 0x0F, 0x11, 0x0F, 0x11, - 0x0F, 0x11, 0x0F, 0x11, 0x0F, 0x11}); + const std::vector pixels( + {0x0F, 0x11, 0x0F, 0x11, 0x0F, 0x11, 0x0F, 0x11, 0x0F, 0x11, 0x0F, 0x11, 0x0F, 0x11, 0x0F, 0x11}); const std::vector expected({0b01010101, 0b01010101}); - auto mono = ptprnt::graphics::Monochrome(pixels); + auto mono = ptprnt::graphics::Monochrome(pixels, 16, 1); mono.setThreshold(16); auto out = mono.get(); @@ -60,12 +60,104 @@ TEST(basic_test, Monochrome_convertNonAlignedPixels_spillsOverIntoNewByte) { // TODO: We need to find to access the vector without the possiblity of out-of-bounds access // Ideas: constexpr? compile time check? GTEST_SKIP() << "Skipping this test, as ASAN will halt as this is an out-of-bounds access"; - const std::vector pixels({0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF}); + const std::vector pixels( + {0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF}); const std::vector expected({0b10101010, 0b10101010, 0b10000000}); - auto mono = ptprnt::graphics::Monochrome(pixels); + auto mono = ptprnt::graphics::Monochrome(pixels, 17, 1); auto out = mono.get(); EXPECT_EQ(out, expected); +} + +TEST(MonochromeData_test, MonochromeData_getMonochromeData_returnsStructWithCorrectData) { + const std::vector pixels({0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00}); + + auto mono = ptprnt::graphics::Monochrome(pixels, 8, 1); + auto monoData = mono.getMonochromeData(); + + EXPECT_EQ(monoData.bytes.size(), 1); + EXPECT_EQ(monoData.bytes[0], 0b10101010); + EXPECT_EQ(monoData.width, 8); + EXPECT_EQ(monoData.height, 1); + EXPECT_EQ(monoData.stride, 1); + EXPECT_EQ(monoData.orientation, ptprnt::graphics::Orientation::LANDSCAPE); +} + +TEST(MonochromeData_test, MonochromeData2x2_transformToPortrait_rotatesCorrectly) { + // Create a 2x2 image with a specific pattern + // Pixels are laid out row-major: row0_col0, row0_col1, row1_col0, ... + const std::vector pixels({0xFF, 0x00, 0x00, 0xFF}); + + auto mono = ptprnt::graphics::Monochrome(pixels, 2, 2); + auto monoData = mono.getMonochromeData(); + + monoData.transformTo(ptprnt::graphics::Orientation::PORTRAIT); + + // After 90° clockwise rotation: + // Original: █ . -> Rotated: . █ + // . █ █ . + EXPECT_EQ(monoData.width, 2); + EXPECT_EQ(monoData.height, 2); + EXPECT_EQ(monoData.orientation, ptprnt::graphics::Orientation::PORTRAIT); + + // check pixel data ...................................... x,y = value + EXPECT_EQ(monoData.getBit(0, 0), false); // 0,0 = white + EXPECT_EQ(monoData.getBit(1, 0), true); // 0,1 = black + EXPECT_EQ(monoData.getBit(0, 1), true); // 1,0 = black + EXPECT_EQ(monoData.getBit(1, 1), false); // 1,1 = white +} + +TEST(MonochromeData_test, MonochromeData3x2_transformToPortrait_rotatesCorrectly) { + // Create a 2x3 image with a specific pattern + // Pixels are laid out row-major: row0_col0, row0_col1, row0_col2, row1_col0, ... + const std::vector pixels({0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF}); + + auto mono = ptprnt::graphics::Monochrome(pixels, 3, 2); + auto monoData = mono.getMonochromeData(); + + monoData.transformTo(ptprnt::graphics::Orientation::PORTRAIT); + + // After 90° clockwise rotation: + // Original: █ . . -> Rotated: █ █ + // █ . █ . . + // █ . + EXPECT_EQ(monoData.width, 2); + EXPECT_EQ(monoData.height, 3); + EXPECT_EQ(monoData.orientation, ptprnt::graphics::Orientation::PORTRAIT); + + // check pixel data ...................................... x,y = value + EXPECT_EQ(monoData.getBit(0, 0), true); // 1,1 = black + EXPECT_EQ(monoData.getBit(1, 0), true); // 1,2 = black + EXPECT_EQ(monoData.getBit(0, 1), false); // 2,1 = white + EXPECT_EQ(monoData.getBit(1, 1), false); // 2,2 = white + EXPECT_EQ(monoData.getBit(0, 2), true); // 3,1 = black + EXPECT_EQ(monoData.getBit(1, 2), false); // 3,2 = white +} + +TEST(MonochromeData_test, MonochromeData3x2_transformToPortrait_rotatesCorrectlyCounterclockwise) { + // Create a 2x3 image with a specific pattern + // Pixels are laid out row-major: row0_col0, row0_col1, row0_col2, row1_col0, ... + const std::vector pixels({0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF}); + + auto mono = ptprnt::graphics::Monochrome(pixels, 3, 2); + auto monoData = mono.getMonochromeData(); + + monoData.transformTo(ptprnt::graphics::Orientation::PORTRAIT_FLIPPED); + + // After 90° anti-clockwise rotation: + // Original: █ . . -> Rotated: . █ + // █ . █ . . + // █ █ + EXPECT_EQ(monoData.width, 2); + EXPECT_EQ(monoData.height, 3); + EXPECT_EQ(monoData.orientation, ptprnt::graphics::Orientation::PORTRAIT_FLIPPED); + + // check pixel data ...................................... x,y = value + EXPECT_EQ(monoData.getBit(0, 0), false); // 1,1 = white + EXPECT_EQ(monoData.getBit(1, 0), true); // 1,2 = black + EXPECT_EQ(monoData.getBit(0, 1), false); // 2,1 = white + EXPECT_EQ(monoData.getBit(1, 1), false); // 2,2 = white + EXPECT_EQ(monoData.getBit(0, 2), true); // 3,1 = black + EXPECT_EQ(monoData.getBit(1, 2), true); // 3,2 = black } \ No newline at end of file