All checks were successful
Build ptprnt / build (push) Successful in 3m41s
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
259 lines
7.7 KiB
C++
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
|