Files
ptprnt/src/graphics/Monochrome.cpp
Moritz Martinius 05cd9d244c
All checks were successful
Build ptprnt / build (push) Successful in 3m41s
Generate labels with pangocairo (#8)
Goal of this PR is to have some basic labels generated with pangocairo
- size of the canvas should be matching the input text and grow/shrink accordingly
- basic formatting options like fontsize and align should be working

Reviewed-on: moritz/ptouch-prnt#8
2025-10-12 20:07:18 +00:00

259 lines
7.7 KiB
C++

/*
ptrnt - print labels on linux
Copyright (C) 2023-2025 Moritz Martinius
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "graphics/Monochrome.hpp"
#include <cmath>
#include <iostream>
#include <vector>
namespace ptprnt::graphics {
// Constructor from grayscale data
MonochromeData::MonochromeData(const std::vector<uint8_t>& grayscale, uint32_t width, uint32_t height,
Orientation orient)
: stride(0),
orientation(orient),
width(width),
height(height),
mPixels(grayscale),
mIsProcessed(false) {}
MonochromeData::MonochromeData(const std::span<uint8_t> grayscale, uint32_t width, uint32_t height,
Orientation orient)
: stride(0),
orientation(orient),
width(width),
height(height),
mPixels(grayscale.begin(), grayscale.end()),
mIsProcessed(false) {}
void MonochromeData::setThreshold(uint8_t threshold) {
mThreshold = threshold;
mIsProcessed = false; // Mark as needing reprocessing
}
void MonochromeData::invert(bool shouldInvert) {
mShouldInvert = shouldInvert;
mIsProcessed = false; // Mark as needing reprocessing
}
MonochromeData MonochromeData::get() {
if (!mIsProcessed) {
processGrayscaleToMonochrome();
mIsProcessed = true;
}
// Return a copy of the processed data
MonochromeData result;
result.bytes = bytes;
result.stride = stride;
result.orientation = orientation;
result.width = width;
result.height = height;
result.mIsProcessed = true;
return result;
}
void MonochromeData::processGrayscaleToMonochrome() {
// Calculate stride based on packed monochrome data (1 bit per pixel, 8 pixels per byte)
stride = static_cast<uint32_t>((width + 7) / 8);
// Create the monochrome byte array
bytes.clear();
bytes.resize(stride * height, 0);
// Convert grayscale to monochrome
for (uint32_t y = 0; y < height; ++y) {
for (uint32_t x = 0; x < width; ++x) {
uint32_t pixelIndex = y * width + x;
if (pixelIndex < mPixels.size()) {
uint8_t pixelValue = mPixels[pixelIndex];
// Apply threshold
bool isSet = pixelValue >= mThreshold;
// Apply inversion if needed
if (mShouldInvert) {
isSet = !isSet;
}
// Set the bit in the monochrome data
if (isSet) {
setBit(x, y, true);
}
}
}
}
}
// 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<uint8_t> MonochromeData::createRotatedData(Orientation targetOrientation) const {
uint32_t newWidth = 0, newHeight = 0;
// 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<uint8_t> 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 = 0, newY = 0;
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