/* ptrnt - print labels on linux Copyright (C) 2023 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 . */ #include "graphics/Monochrome.hpp" #include #include #include namespace ptprnt::graphics { // Constructor from grayscale data MonochromeData::MonochromeData(const std::vector& 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 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((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 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 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