diff --git a/.gitignore b/.gitignore index 259148f..d4b7e92 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,5 @@ *.exe *.out *.app + +*.pro.user diff --git a/BitmapPanel.cpp b/BitmapPanel.cpp new file mode 100644 index 0000000..e6db3ab --- /dev/null +++ b/BitmapPanel.cpp @@ -0,0 +1,90 @@ +// +// (c)2021 by Lucky Resistor. https://luckyresistor.me/ +// +// 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 "BitmapPanel.hpp" + + +#include "Converter.hpp" +#include "BitmapPreview.hpp" + +#include +#include +#include +#include + + +BitmapPanel::BitmapPanel(QWidget *parent) : QWidget(parent) +{ + initializeUi(); +} + + +void BitmapPanel::setImage(const QImage &image) +{ + _bitmapPreview->setImage(image); +} + + +void BitmapPanel::setConverter(const Converter *converter) +{ + _bitmapPreview->setConverter(converter); + _bitmapPreview->setFixedSize(_bitmapPreview->sizeHint()); +} + + +void BitmapPanel::initializeUi() +{ + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + setMinimumHeight(300); + + auto mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(16, 16, 16, 4); + mainLayout->setSpacing(4); + mainLayout->addWidget(new QLabel(tr("Loaded Bitmap:"))); + + auto scrollArea = new QScrollArea(); + QPalette imagePalette = palette(); + imagePalette.setColor(QPalette::Window, QColor::fromRgb(32, 32, 32)); + scrollArea->setPalette(imagePalette); + scrollArea->setBackgroundRole(QPalette::Window); + mainLayout->addWidget(scrollArea); + + _bitmapPreview = new BitmapPreview(); + _bitmapPreview->setFixedSize(_bitmapPreview->sizeHint()); + _bitmapPreview->setPalette(imagePalette); + scrollArea->setWidget(_bitmapPreview); + scrollArea->setAlignment(Qt::AlignCenter); + + auto previewSettingsPanel = new QFrame(); + auto previewSettingsLayout = new QHBoxLayout(previewSettingsPanel); + previewSettingsLayout->setContentsMargins(0, 0, 0, 0); + previewSettingsLayout->setSpacing(4); + previewSettingsLayout->addStretch(); + previewSettingsLayout->addWidget(new QLabel(tr("Overlay Mode:"))); + _overlaySelector = new QComboBox(); + _overlaySelector->addItem(tr("None"), static_cast(OverlayMode::None)); + _overlaySelector->addItem(tr("Bit Assignments"), static_cast(OverlayMode::BitAssigments)); + _overlaySelector->addItem(tr("Pixel Interpretation"), static_cast(OverlayMode::PixelInterpretation)); + _overlaySelector->setCurrentIndex(0); + previewSettingsLayout->addWidget(_overlaySelector); + mainLayout->addWidget(previewSettingsPanel); + + connect(_overlaySelector, &QComboBox::currentIndexChanged, [=]{ + _bitmapPreview->setOverlayMode(static_cast(_overlaySelector->currentData().toInt())); + }); +} + + diff --git a/BitmapPanel.hpp b/BitmapPanel.hpp new file mode 100644 index 0000000..d51e3fb --- /dev/null +++ b/BitmapPanel.hpp @@ -0,0 +1,51 @@ +// +// (c)2021 by Lucky Resistor. https://luckyresistor.me/ +// +// 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 . +// +#pragma once + + +#include + + +class BitmapPreview; +class Converter; +class QComboBox; + + +class BitmapPanel : public QWidget +{ + Q_OBJECT + +public: + explicit BitmapPanel(QWidget *parent = nullptr); + +public: + /// Assign a new image. + /// + void setImage(const QImage &image); + + /// Set the current converter. + /// + void setConverter(const Converter *converter); + +private: + void initializeUi(); + +private: + BitmapPreview *_bitmapPreview; ///< The bitmap preview; + QComboBox *_overlaySelector; ///< The selector for the overlays. +}; + diff --git a/BitmapPreview.cpp b/BitmapPreview.cpp new file mode 100644 index 0000000..cfb33ff --- /dev/null +++ b/BitmapPreview.cpp @@ -0,0 +1,154 @@ +// +// (c)2021 by Lucky Resistor. https://luckyresistor.me/ +// +// 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 "BitmapPreview.hpp" + + +#include "Converter.hpp" +#include "OverlayPainter.hpp" + +#include +#include + + +namespace { +const int cImagePadding = 32; +const int cMinimumDisplayFactor = 12; +const int cMinimumWidth = 400; +const int cMinimumHeight = 200; +} + + +BitmapPreview::BitmapPreview(QWidget *parent) +: + QWidget(parent), + _displayFactor(cMinimumDisplayFactor), + _minimumSize(cMinimumWidth, cMinimumHeight), + _overlayMode(OverlayMode::None), + _converter(nullptr) +{ +} + + +void BitmapPreview::setImage(const QImage &image) +{ + if (_image != image) { + _image = image; + _pixmap = QPixmap::fromImage(image); + if (_converter != nullptr) { + setGeneratedSize(_converter->generatedSize(_image.size())); + } + recalculate(); + update(); + } +} + + +void BitmapPreview::setGeneratedSize(const QSize &size) +{ + if (_generatedSize != size) { + _generatedSize = size; + recalculate(); + update(); + } +} + + +void BitmapPreview::setOverlayMode(OverlayMode overlayMode) +{ + if (_overlayMode != overlayMode) { + _overlayMode = overlayMode; + update(); + } +} + + +void BitmapPreview::setConverter(const Converter *converter) +{ + if (_converter != converter) { + _converter = converter; + if (_converter != nullptr) { + setGeneratedSize(_converter->generatedSize(_image.size())); + } + update(); + } +} + + +QSize BitmapPreview::sizeHint() const +{ + return _minimumSize; +} + + +QSize BitmapPreview::minimumSizeHint() const +{ + return _minimumSize; +} + + +void BitmapPreview::paintEvent(QPaintEvent *pe) +{ + QPainter p(this); + if (!_pixmap.isNull()) { + qreal x = (width() - _minimumSize.width())/2 + cImagePadding; + qreal y = (height() - _minimumSize.height())/2 + cImagePadding; + qreal width = (_pixmap.width()*_displayFactor); + qreal height = (_pixmap.height()*_displayFactor); + p.fillRect(QRectF(x, y, width, height), Qt::white); + p.drawPixmap(QRectF(x, y, width, height), _pixmap, _pixmap.rect()); + if (_converter != nullptr) { + if (_overlayMode != OverlayMode::None) { + p.setOpacity(0.75); + p.fillRect(rect(), palette().window()); + } + p.setOpacity(1.0); + p.translate(x, y); + OverlayPainter op(&p, pe->rect(), _displayFactor, _pixmap.size(), _generatedSize); + _converter->paintOverlay(op, _overlayMode, _image); + } + } else { + p.drawText(QRect(0, 0, width(), height()), Qt::AlignCenter, tr("No Bitmap Loaded")); + } +} + + +void BitmapPreview::resizeEvent(QResizeEvent *e) +{ + recalculate(); + QWidget::resizeEvent(e); +} + + +void BitmapPreview::recalculate() +{ + if (_pixmap.isNull()) { + _displayFactor = cMinimumDisplayFactor; + _minimumSize = QSize(cMinimumWidth, cMinimumHeight); + } else { + auto calculationSize = _pixmap.size(); + if (_generatedSize.width() > calculationSize.width() || _generatedSize.height() > calculationSize.height()) { + calculationSize = _generatedSize; + } + int factorX = qMax(cMinimumWidth / calculationSize.width(), cMinimumDisplayFactor); + int factorY = qMax(cMinimumWidth / calculationSize.height(), cMinimumDisplayFactor); + _displayFactor = qMin(factorX, factorY); + _minimumSize = QSize(calculationSize.width()*_displayFactor, calculationSize.height()*_displayFactor) + + QSize(cImagePadding*2, cImagePadding*2); + } +} + + diff --git a/BitmapPreview.hpp b/BitmapPreview.hpp new file mode 100644 index 0000000..c8b39d3 --- /dev/null +++ b/BitmapPreview.hpp @@ -0,0 +1,78 @@ +// +// (c)2021 by Lucky Resistor. https://luckyresistor.me/ +// +// 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 . +// +#pragma once + + +#include "OverlayMode.hpp" + +#include +#include +#include + + +class Converter; + + +/// Preview the bitmap. +/// +class BitmapPreview : public QWidget +{ + Q_OBJECT + +public: + explicit BitmapPreview(QWidget *parent = nullptr); + +public: + /// Assign a new image. + /// + void setImage(const QImage &image); + + /// Set the overlay mode. + /// + void setOverlayMode(OverlayMode overlayMode); + + /// Set the current converter. + /// + void setConverter(const Converter *converter); + +public: + QSize sizeHint() const override; + QSize minimumSizeHint() const override; + +protected: // QWidget interface + void paintEvent(QPaintEvent *event) override; + void resizeEvent(QResizeEvent *event) override; + +private: + /// Recalculate all sizes + /// + void recalculate(); + + /// Set the generated image size. + /// + void setGeneratedSize(const QSize &size); + +private: + QImage _image; ///< The current image. + QSize _generatedSize; ///< The generated image size. + QPixmap _pixmap; ///< QPixmap representation of the image. + int _displayFactor; ///< The display factor. + QSize _minimumSize; ///< The preferred size of this widget. + OverlayMode _overlayMode; ///< The overlay mode. + const Converter *_converter; ///< The current converter. +}; + diff --git a/Converter.cpp b/Converter.cpp new file mode 100644 index 0000000..48db2f8 --- /dev/null +++ b/Converter.cpp @@ -0,0 +1,53 @@ +// +// (c)2021 by Lucky Resistor. https://luckyresistor.me/ +// +// 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 "Converter.hpp" + + +Converter::Converter(const QString &displayName) + : _displayName(displayName) +{ +} + + +QString Converter::displayName() const +{ + return _displayName; +} + + +QSize Converter::generatedSize(const QSize &imageSize) const +{ + return imageSize; +} + + +LegendDataPtr Converter::legendData(OverlayMode) const +{ + auto ld = LegendData::create(); + ld->addEntry(colorBitmapSizeOriginal, colorBitmapSizeGenerated, QObject::tr("Bitmap Size Original/Generated")); + return ld; +} + + +void Converter::paintOverlay(OverlayPainter &op, OverlayMode, const QImage&) const +{ + op.drawPixelOutline(op.imageRect(), colorBitmapSizeOriginal, 1, 2); + if (op.generatedSize().isValid() && !op.generatedSize().isEmpty()) { + op.drawPixelOutline(op.generatedRect(), colorBitmapSizeGenerated, 1, 2); + } +} + diff --git a/Converter.hpp b/Converter.hpp new file mode 100644 index 0000000..c0cd353 --- /dev/null +++ b/Converter.hpp @@ -0,0 +1,89 @@ +// +// (c)2021 by Lucky Resistor. https://luckyresistor.me/ +// +// 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 . +// +#pragma once + + +#include "LegendData.hpp" +#include "OverlayPainter.hpp" +#include "OverlayMode.hpp" + +#include +#include +#include + + +/// Base class for all converters +/// +class Converter +{ +public: + /// Create a new converter. + /// + /// @param displayName The name displayed in the UI for this coonverter. + /// + Converter(const QString &displayName); + + /// dtor + /// + virtual ~Converter() = default; + +public: + /// Get the display name for the converter. + /// + QString displayName() const; + +public: + /// Return the size of the generated bitmap data. + /// + /// @param imageSize The size of the image. + /// @return The size of the generated bitmap data. + /// + virtual QSize generatedSize(const QSize &imageSize) const; + + /// Generate the code from the given image. + /// + /// @param image The image to convert. + /// @param parameter A map with parameters passed to this converter. + /// @return The generated code. + /// + virtual QString generateCode(const QImage &image, const QVariantMap ¶meter) const = 0; + + /// Return a legend for the bitmap preview. + /// + virtual LegendDataPtr legendData(OverlayMode mode) const; + + /// Draw the bitmap overlay. + /// + /// @param p The painter. + /// @param mode The overlay mode (never `None`). + /// @param image The image. + /// + virtual void paintOverlay(OverlayPainter &p, OverlayMode mode, const QImage &image) const; + +protected: + /// The color for the image frame. + /// + static constexpr QColor colorBitmapSizeOriginal = QColor(64, 64, 255); + + /// The color for the generated frame. + /// + static constexpr QColor colorBitmapSizeGenerated = QColor(128, 128, 255); + +private: + QString _displayName; ///< The displayed name. +}; + diff --git a/ConverterFramebuf.cpp b/ConverterFramebuf.cpp new file mode 100644 index 0000000..058e900 --- /dev/null +++ b/ConverterFramebuf.cpp @@ -0,0 +1,46 @@ +// +// (c)2021 by Lucky Resistor. https://luckyresistor.me/ +// +// 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 "ConverterFramebuf.hpp" + + +#include + + +ConverterFramebuf::ConverterFramebuf(const QString &displayName) + : Converter(displayName) +{ +} + + +QString ConverterFramebuf::createCode(const QByteArray &data, const QSize &size, const QString &format) +{ + QString result; + QTextStream ts(&result); + ts << "fb = framebuf.FrameBuffer(bytearray(\n"; + for (int i = 0; i < data.size(); ++i) { + if ((i & 0x1f) == 0) { + if (i > 0) { + ts << "'\n"; + } + ts << " b'"; + } + ts << QString("\\x%1").arg(static_cast(data.at(i))&0xff, 2, 16, QChar('0')); + } + ts << "'),\n"; + ts << " " << size.width() << ", " << size.height() << ", framebuf." << format << ")\n"; + return result; +} diff --git a/ConverterFramebuf.hpp b/ConverterFramebuf.hpp new file mode 100644 index 0000000..8a261fc --- /dev/null +++ b/ConverterFramebuf.hpp @@ -0,0 +1,39 @@ +// +// (c)2021 by Lucky Resistor. https://luckyresistor.me/ +// +// 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 . +// +#pragma once + + +#include "Converter.hpp" + + +/// Base class of framebuf converters. +/// +class ConverterFramebuf : public Converter +{ +public: + ConverterFramebuf(const QString &displayName); + +protected: + /// Create a framebuf code segment. + /// + /// @param data The byte data. + /// @param size The buffer size. + /// @param format The name of the format. + /// + static QString createCode(const QByteArray &data, const QSize &size, const QString &format); +}; + diff --git a/ConverterFramebufMono.cpp b/ConverterFramebufMono.cpp new file mode 100644 index 0000000..faa0496 --- /dev/null +++ b/ConverterFramebufMono.cpp @@ -0,0 +1,148 @@ +// +// (c)2021 by Lucky Resistor. https://luckyresistor.me/ +// +// 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 "ConverterFramebufMono.hpp" + + + +ConverterFramebufMono::ConverterFramebufMono( + const QString &displayName, + UnitOrientation unitOrientation, + BitDirection bitDirection, + int unitSize) +: + ConverterFramebuf(displayName), + _unitOrientation(unitOrientation), + _bitDirection(bitDirection), + _unitSize(unitSize) +{ +} + + +QSize ConverterFramebufMono::generatedSize(const QSize &imageSize) const +{ + if (_unitOrientation == UnitOrientation::Vertical) { + if ((imageSize.height() % _unitSize) != 0) { + return QSize(imageSize.width(), ((imageSize.height()/_unitSize)+1)*_unitSize); + } else { + return imageSize; + } + } else { + if ((imageSize.width() % _unitSize) != 0) { + return QSize(((imageSize.width()/_unitSize)+1)*_unitSize, imageSize.height()); + } else { + return imageSize; + } + } +} + + +LegendDataPtr ConverterFramebufMono::legendData(OverlayMode mode) const +{ + auto data = ConverterFramebuf::legendData(mode); + if (mode == OverlayMode::BitAssigments) { + data->addEntry(colorBitAssignment1, colorBitAssignment2, QObject::tr("Bit Assignment")); + } else { + data->addEntry(colorPixelInterpretation, QObject::tr("Pixel Interpretation")); + } + return data; +} + + +void ConverterFramebufMono::paintOverlay(OverlayPainter &p, OverlayMode mode, const QImage &image) const +{ + ConverterFramebuf::paintOverlay(p, mode, image); + if (mode == OverlayMode::BitAssigments) { + const auto bitL = (_bitDirection == BitDirection::LSB ? "0" : QString::number(_unitSize-1)); + const auto bitH = (_bitDirection == BitDirection::LSB ? QString::number(_unitSize-1) : "0"); + bool colorFlag = false; + if (_unitOrientation == UnitOrientation::Vertical) { + for (int y = 0; y < p.imageSize().height(); y += _unitSize) { + for (int x = 0; x < p.imageSize().width(); ++x) { + QRect rect(x, y, 1, _unitSize); + if (p.arePixelUpdated(rect)) { + const auto color = colorFlag ? colorBitAssignment1 : colorBitAssignment2; + p.drawPixelOutline(rect, color, 1); + p.drawPixelText(QRect(x, y, 1, 1), color, bitL); + p.drawPixelText(QRect(x, y+_unitSize-1, 1, 1), color, bitH); + } + colorFlag = !colorFlag; + } + if (p.imageSize().width() % 2 == 0) { + colorFlag = !colorFlag; + } + } + } else { + for (int y = 0; y < p.imageSize().height(); ++y) { + for (int x = 0; x < p.imageSize().width(); x += _unitSize) { + QRect rect(x, y, _unitSize, 1); + if (p.arePixelUpdated(rect)) { + const auto color = colorFlag ? colorBitAssignment1 : colorBitAssignment2; + p.drawPixelOutline(rect, color, 1); + p.drawPixelText(QRect(x, y, 1, 1), color, bitH); + p.drawPixelText(QRect(x+_unitSize-1, y, 1, 1), color, bitL); + } + colorFlag = !colorFlag; + } + if ((p.imageSize().width()/_unitSize) % 2 == 0) { + colorFlag = !colorFlag; + } + } + } + } else if (mode == OverlayMode::PixelInterpretation) { + for (int y = 0; y < p.generatedSize().height(); ++y) { + for (int x = 0; x < p.generatedSize().width(); ++x) { + auto text = (getPixel(x, y, image) ? "1" : "0"); + const QRect rect(x, y, 1, 1); + if (p.arePixelUpdated(rect)) { + p.drawPixelText(rect, colorPixelInterpretation, text); + } + } + } + } +} + + +bool ConverterFramebufMono::getPixel(int x, int y, const QImage &image) +{ + if (x < 0 || y < 0 || x >= image.width() || y >= image.height()) { + return false; + } + auto color = image.pixelColor(x, y); + float h, s, l, a; + color.getHslF(&h, &s, &l, &a); + if (a < 0.5) { + return false; + } + return l < 0.5; +} + + +uint32_t ConverterFramebufMono::readUnit(int x, int y, int dx, int dy, int count, const QImage &image) +{ + uint32_t result = 0; + for (int i = 0; i < count; ++i) { + result <<= 1; + if (getPixel(x, y, image)) { + result |= 0b1; + } + x += dx; + y += dy; + } + return result; +} + + diff --git a/ConverterFramebufMono.hpp b/ConverterFramebufMono.hpp new file mode 100644 index 0000000..54bd17b --- /dev/null +++ b/ConverterFramebufMono.hpp @@ -0,0 +1,78 @@ +// +// (c)2021 by Lucky Resistor. https://luckyresistor.me/ +// +// 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 . +// +#pragma once + + +#include "ConverterFramebuf.hpp" + + +/// The base class of all mono converters +/// +class ConverterFramebufMono : public ConverterFramebuf +{ +protected: + enum class UnitOrientation { + Horizontal, + Vertical + }; + + enum class BitDirection { + LSB, + MSB + }; + +public: + /// Create a new mono converter. + /// + /// @param displayName The display name. + /// @param unitOrientation The unit direction. + /// @param unitSize The number of bits per unit. + /// + ConverterFramebufMono( + const QString &displayName, + UnitOrientation unitOrientation, + BitDirection bitDirection, + int unitSize); + +public: // Converter interface + QSize generatedSize(const QSize &imageSize) const override; + LegendDataPtr legendData(OverlayMode mode) const override; + void paintOverlay(OverlayPainter &p, OverlayMode mode, const QImage &image) const override; + +protected: + /// Interpret a single pixel. + /// + /// Returns `false` if the pixel is out of bounds. + /// + static bool getPixel(int x, int y, const QImage &image); + + /// Read a single unit. + /// + static uint32_t readUnit(int x, int y, int dx, int dy, int count, const QImage &image); + + /// The colors for the bit assignments + /// + static constexpr QColor colorBitAssignment1 = QColor(240, 40, 40); + static constexpr QColor colorBitAssignment2 = QColor(240, 80, 80); + static constexpr QColor colorPixelInterpretation = QColor(240, 240, 40); + +protected: + UnitOrientation _unitOrientation; ///< The unit orientatioon. + BitDirection _bitDirection; ///< The bit direction. + int _unitSize; ///< The number of bits per unit. +}; + diff --git a/ConverterFramebufMonoHLSB.cpp b/ConverterFramebufMonoHLSB.cpp new file mode 100644 index 0000000..779e881 --- /dev/null +++ b/ConverterFramebufMonoHLSB.cpp @@ -0,0 +1,38 @@ +// +// (c)2021 by Lucky Resistor. https://luckyresistor.me/ +// +// 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 "ConverterFramebufMonoHLSB.hpp" + + +#include + + +ConverterFramebufMonoHLSB::ConverterFramebufMonoHLSB() + : ConverterFramebufMono("MicroPython Framebuf Mono HLSB", UnitOrientation::Horizontal, BitDirection::LSB, 8) +{ +} + + +QString ConverterFramebufMonoHLSB::generateCode(const QImage &image, const QVariantMap&) const +{ + QByteArray data; + for (int y = 0; y < image.height(); ++y) { + for (int x = 0; x < image.width(); x += 8) { + data.append(static_cast(readUnit(x, y, 1, 0, 8, image))); + } + } + return createCode(data, generatedSize(image.size()), "MONO_HLSB"); +} diff --git a/ConverterFramebufMonoHLSB.hpp b/ConverterFramebufMonoHLSB.hpp new file mode 100644 index 0000000..5c6aa75 --- /dev/null +++ b/ConverterFramebufMonoHLSB.hpp @@ -0,0 +1,31 @@ +// +// (c)2021 by Lucky Resistor. https://luckyresistor.me/ +// +// 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 . +// +#pragma once + + +#include "ConverterFramebufMono.hpp" + + +class ConverterFramebufMonoHLSB : public ConverterFramebufMono +{ +public: + ConverterFramebufMonoHLSB(); + +public: // Converter interface + QString generateCode(const QImage &image, const QVariantMap ¶meter) const override; +}; + diff --git a/ConverterFramebufMonoHMSB.cpp b/ConverterFramebufMonoHMSB.cpp new file mode 100644 index 0000000..fed9891 --- /dev/null +++ b/ConverterFramebufMonoHMSB.cpp @@ -0,0 +1,36 @@ +// +// (c)2021 by Lucky Resistor. https://luckyresistor.me/ +// +// 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 "ConverterFramebufMonoHMSB.hpp" + + +ConverterFramebufMonoHMSB::ConverterFramebufMonoHMSB() + : ConverterFramebufMono("MicroPython Framebuf Mono HMSB", UnitOrientation::Horizontal,BitDirection::MSB, 8) +{ +} + + +QString ConverterFramebufMonoHMSB::generateCode(const QImage &image, const QVariantMap&) const +{ + QByteArray data; + for (int y = 0; y < image.height(); ++y) { + for (int x = 0; x < image.width(); x += 8) { + data.append(static_cast(readUnit(x+7, y, -1, 0, 8, image))); + } + } + return createCode(data, generatedSize(image.size()), "MONO_HMSB"); +} + diff --git a/ConverterFramebufMonoHMSB.hpp b/ConverterFramebufMonoHMSB.hpp new file mode 100644 index 0000000..73a505b --- /dev/null +++ b/ConverterFramebufMonoHMSB.hpp @@ -0,0 +1,31 @@ +// +// (c)2021 by Lucky Resistor. https://luckyresistor.me/ +// +// 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 . +// +#pragma once + + +#include "ConverterFramebufMono.hpp" + + +class ConverterFramebufMonoHMSB : public ConverterFramebufMono +{ +public: + ConverterFramebufMonoHMSB(); + +public: // Converter interface + QString generateCode(const QImage &image, const QVariantMap ¶meter) const override; +}; + diff --git a/ConverterFramebufMonoVLSB.cpp b/ConverterFramebufMonoVLSB.cpp new file mode 100644 index 0000000..868f6c1 --- /dev/null +++ b/ConverterFramebufMonoVLSB.cpp @@ -0,0 +1,36 @@ +// +// (c)2021 by Lucky Resistor. https://luckyresistor.me/ +// +// 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 "ConverterFramebufMonoVLSB.hpp" + + +ConverterFramebufMonoVLSB::ConverterFramebufMonoVLSB() + : ConverterFramebufMono("MicroPython Framebuf Mono VLSB", UnitOrientation::Vertical, BitDirection::LSB, 8) +{ +} + + +QString ConverterFramebufMonoVLSB::generateCode(const QImage &image, const QVariantMap&) const +{ + QByteArray data; + for (int y = 0; y < image.height(); y += 8) { + for (int x = 0; x < image.width(); ++x) { + data.append(static_cast(readUnit(x, y+7, 0, -1, 8, image))); + } + } + return createCode(data, generatedSize(image.size()), "MONO_VLSB"); +} + diff --git a/ConverterFramebufMonoVLSB.hpp b/ConverterFramebufMonoVLSB.hpp new file mode 100644 index 0000000..c871502 --- /dev/null +++ b/ConverterFramebufMonoVLSB.hpp @@ -0,0 +1,31 @@ +// +// (c)2021 by Lucky Resistor. https://luckyresistor.me/ +// +// 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 . +// +#pragma once + + +#include "ConverterFramebufMono.hpp" + + +class ConverterFramebufMonoVLSB : public ConverterFramebufMono +{ +public: + ConverterFramebufMonoVLSB(); + +public: // Converter interface + QString generateCode(const QImage &image, const QVariantMap ¶meter) const override; +}; + diff --git a/LegendData.cpp b/LegendData.cpp new file mode 100644 index 0000000..21e79a9 --- /dev/null +++ b/LegendData.cpp @@ -0,0 +1,51 @@ +// +// (c)2021 by Lucky Resistor. https://luckyresistor.me/ +// +// 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 "LegendData.hpp" + + +#include "LegendEntry.hpp" + + +LegendDataPtr LegendData::create() +{ + return LegendDataPtr(new LegendData()); +} + + +LegendData::~LegendData() +{ + qDeleteAll(_entryList); +} + + +void LegendData::addEntry(const QColor &color, const QString &text) +{ + _entryList.append(new LegendEntry(color, text)); +} + + +void LegendData::addEntry(const QColor &color1, const QColor &color2, const QString &text) +{ + _entryList.append(new LegendEntry(color1, color2, text)); +} + + +const QList& LegendData::entryList() const +{ + return _entryList; +} + diff --git a/LegendData.hpp b/LegendData.hpp new file mode 100644 index 0000000..03b73a3 --- /dev/null +++ b/LegendData.hpp @@ -0,0 +1,76 @@ +// +// (c)2021 by Lucky Resistor. https://luckyresistor.me/ +// +// 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 . +// +#pragma once + + +#include +#include +#include + + +class LegendEntry; + + +class LegendData; +using LegendDataPtr = QSharedPointer; + + +/// Legend data. +/// +class LegendData +{ +public: + /// Create a new legend data object. + /// + static LegendDataPtr create(); + +private: + /// private ctor + /// + LegendData() = default; + +public: + /// dtor + /// + ~LegendData(); + +public: + /// Add a new entry to the legend. + /// + /// @param color The color. + /// @param text The text for the entry. + /// + void addEntry(const QColor &color, const QString &text); + + /// Add a new entry to the legend. + /// + /// @param color1 The first color. + /// @param color2 The second color. + /// @param text The text for the entry. + /// + void addEntry(const QColor &color1, const QColor &color2, const QString &text); + + /// Get the list with all entries. + /// + const QList& entryList() const; + +private: + QList _entryList; ///< The list with all entries. +}; + + + diff --git a/LegendEntry.cpp b/LegendEntry.cpp new file mode 100644 index 0000000..c14f805 --- /dev/null +++ b/LegendEntry.cpp @@ -0,0 +1,48 @@ +// +// (c)2021 by Lucky Resistor. https://luckyresistor.me/ +// +// 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 "LegendEntry.hpp" + + +LegendEntry::LegendEntry(const QColor &color, const QString &text) + : _color1(color), _text(text) +{ +} + + +LegendEntry::LegendEntry(const QColor &color1, const QColor &color2, const QString &text) + : _color1(color1), _color2(color2), _text(text) +{ +} + + +QColor LegendEntry::color1() const +{ + return _color1; +} + + +QColor LegendEntry::color2() const +{ + return _color2; +} + + +QString LegendEntry::text() const +{ + return _text; +} + diff --git a/LegendEntry.hpp b/LegendEntry.hpp new file mode 100644 index 0000000..5b5b884 --- /dev/null +++ b/LegendEntry.hpp @@ -0,0 +1,49 @@ +// +// (c)2021 by Lucky Resistor. https://luckyresistor.me/ +// +// 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 . +// +#pragma once + + +#include +#include + + +/// A entry in the legend data. +/// +class LegendEntry +{ +public: + enum Identifier : int { + BitmapSize = 0, + BitAssignment = 1, + ReadDirection = 2, + }; + +public: + LegendEntry(const QColor &color, const QString &text); + LegendEntry(const QColor &color1, const QColor &color2, const QString &text); + +public: + QColor color1() const; + QColor color2() const; + QString text() const; + +private: + QColor _color1; + QColor _color2; + QString _text; +}; + diff --git a/MainWindow.cpp b/MainWindow.cpp new file mode 100644 index 0000000..ebc820a --- /dev/null +++ b/MainWindow.cpp @@ -0,0 +1,303 @@ +// +// (c)2021 by Lucky Resistor. https://luckyresistor.me/ +// +// 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 "MainWindow.hpp" + + +#include "BitmapPanel.hpp" +#include "ConverterFramebufMonoVLSB.hpp" +#include "ConverterFramebufMonoHLSB.hpp" +#include "ConverterFramebufMonoHMSB.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) +{ + initializeConverterList(); + initializeUi(); + initializeMenu(); + loadSettings(); +} + + +MainWindow::~MainWindow() +{ +} + + +void MainWindow::initializeConverterList() +{ + _converterList.append(new ConverterFramebufMonoVLSB()); + _converterList.append(new ConverterFramebufMonoHMSB()); + _converterList.append(new ConverterFramebufMonoHLSB()); +} + + +void MainWindow::initializeUi() +{ + setMinimumSize(800, 600); + setWindowTitle(tr("Micropython Bitmap Tool by Lucky Resistor")); + + auto centralWidget = new QWidget(); + centralWidget->setObjectName("CentralWidget"); + setCentralWidget(centralWidget); + + auto mainLayout = new QHBoxLayout(centralWidget); + mainLayout->setContentsMargins(0, 0, 0, 0); + + auto settingsPanel = new QFrame(); + settingsPanel->setObjectName("SettingsPanel"); + mainLayout->addWidget(settingsPanel); + + auto settingsLayout = new QVBoxLayout(settingsPanel); + settingsLayout->setSpacing(4); + + auto logo = new QLabel(); + logo->setFixedSize(300, 250); + logo->setPixmap(QPixmap(":/images/AppLogo.png")); + settingsLayout->addWidget(logo); + auto versionLabel = new QLabel(tr("Version %1").arg(qApp->applicationVersion())); + versionLabel->setAlignment(Qt::AlignCenter); + settingsLayout->addWidget(versionLabel); + settingsLayout->addSpacing(16); + + settingsLayout->addWidget(new QLabel(tr("Generated Format:"))); + _formatSelector = new QComboBox(); + for (auto converter : _converterList) { + _formatSelector->addItem(converter->displayName()); + } + _formatSelector->setCurrentIndex(0); + settingsLayout->addWidget(_formatSelector); + + settingsLayout->addStretch(); + + settingsLayout->addWidget(new QLabel(tr("Loaded Bitmap Info:"))); + _bitmapInfo = new QLabel(tr("No Bitmap Loaded")); + _bitmapInfo->setObjectName("BitmapInfo"); + _bitmapInfo->setMinimumHeight(200); + _bitmapInfo->setAlignment(Qt::AlignTop|Qt::AlignLeft); + settingsLayout->addWidget(_bitmapInfo); + + auto loadButton = new QPushButton(); + loadButton->setText(tr("Load Bitmap")); + settingsLayout->addWidget(loadButton); + connect(loadButton, &QPushButton::clicked, this, &MainWindow::onLoadBitmap); + + auto previewSplittter = new QSplitter(Qt::Vertical); + previewSplittter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + previewSplittter->setChildrenCollapsible(false); + mainLayout->addWidget(previewSplittter); + + _bitmapPanel = new BitmapPanel(); + previewSplittter->addWidget(_bitmapPanel); + + auto codePanel = new QWidget(); + codePanel->setMinimumHeight(200); + auto codeLayout = new QVBoxLayout(codePanel); + codeLayout->setContentsMargins(16, 4, 16, 16); + codeLayout->setSpacing(4); + codeLayout->addWidget(new QLabel(tr("Generated Code:"))); + _codePreview = new QPlainTextEdit(); + _codePreview->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + _codePreview->setReadOnly(true); + _codePreview->setWordWrapMode(QTextOption::NoWrap); + codeLayout->addWidget(_codePreview); + +#ifdef Q_OS_WIN32 + _codePreview->setFont(QFont("Consolas", 12)); +#else +#ifdef Q_OS_MAC + _codePreview->setFont(QFont("Menlo", 12)); +#else + _codePreview->setFont(QFont("Lucida Console", 12)); +#endif +#endif + + auto codeActions = new QFrame(); + auto codeActionsLayout = new QHBoxLayout(codeActions); + codeActionsLayout->setContentsMargins(0, 0, 0, 0); + codeActionsLayout->setSpacing(4); + codeActionsLayout->addStretch(); + + auto copyCodeButton = new QPushButton(); + copyCodeButton->setText(tr("Copy Code")); + codeActionsLayout->addWidget(copyCodeButton); + connect(copyCodeButton, &QPushButton::clicked, [=]{ + qApp->clipboard()->setText(_codePreview->toPlainText()); + }); + codeLayout->addWidget(codeActions); + + previewSplittter->addWidget(codePanel); + previewSplittter->setStretchFactor(0, 4); + previewSplittter->setStretchFactor(1, 1); + + connect(_formatSelector, &QComboBox::currentIndexChanged, [=]{ + _bitmapPanel->setConverter(selectedConverter()); + updateBitmapInfo(); + updateCode(); + }); +} + + +void MainWindow::initializeMenu() +{ + auto menuFile = menuBar()->addMenu(tr("File")); + auto actionLoadBitmap = menuFile->addAction(tr("Load Bitmap...")); + actionLoadBitmap->setShortcut(QKeySequence("Ctrl+O")); + connect(actionLoadBitmap, &QAction::triggered, this, &MainWindow::onLoadBitmap); + auto actionQuit = menuFile->addAction(tr("Quit")); + connect(actionQuit, &QAction::triggered, [=]{ + qApp->quit(); + }); + + auto menuHelp = menuBar()->addMenu(tr("Help")); + auto actionAbout = menuHelp->addAction(tr("About...")); + connect(actionAbout, &QAction::triggered, [=]{ + QMessageBox::about(this, tr("Micropython Bitmap Tool by Lucky Resistor"), + tr("

Micropython Bitmap Tool

(c)2021 by Lucky Resistor

" + "

Version %1

").arg(qApp->applicationVersion())); + }); + auto actionAboutQt = menuHelp->addAction(tr("Information About Qt...")); + connect(actionAboutQt, &QAction::triggered, [=]{ + QMessageBox::aboutQt(this); + }); +} + + +void MainWindow::loadSettings() +{ + QSettings settings; + restoreGeometry(settings.value("mainWindow.geometry").toByteArray()); + restoreState(settings.value("mainWindow.state").toByteArray()); + _formatSelector->setCurrentIndex(settings.value("format.index").toInt(0)); +} + + +void MainWindow::saveSettings() +{ + QSettings settings; + settings.setValue("mainWindow.geometry", saveGeometry()); + settings.setValue("mainWindow.state", saveState()); + settings.setValue("format.index", _formatSelector->currentIndex()); +} + + +Converter *MainWindow::selectedConverter() const +{ + auto converter = _converterList.value(_formatSelector->currentIndex()); + if (converter == nullptr) { + converter = _converterList.first(); + } + return converter; +} + + +void MainWindow::updateBitmapInfo() +{ + QString text; + QTextStream ts(&text); + ts << "

"; + ts << "Bitmap Size: " << _currentImage.width() << "x" << _currentImage.height() << "
"; + ts << "Bit Depth: " << _currentImage.depth() << "
"; + ts << "Color Format: "; + switch (_currentImage.pixelFormat().colorModel()) { + case QPixelFormat::RGB: ts << "RGB"; break; + case QPixelFormat::BGR: ts << "BGR"; break; + case QPixelFormat::Indexed: ts << "Indexed"; break; + case QPixelFormat::Grayscale: ts << "Grayscale"; break; + case QPixelFormat::CMYK: ts << "CMYK"; break; + case QPixelFormat::HSL: ts << "HSL"; break; + case QPixelFormat::HSV: ts << "HSV"; break; + case QPixelFormat::YUV: ts << "YUV"; break; + case QPixelFormat::Alpha: ts << "Alpha"; break; + } + ts << "
"; + const auto gs = selectedConverter()->generatedSize(_currentImage.size()); + ts << "Generated Size: " << gs.width() << "x" << gs.height() << "

"; + _bitmapInfo->setText(text); +} + + +void MainWindow::updateCode() +{ + QString code; + if (!_currentImage.isNull()) { + code = selectedConverter()->generateCode(_currentImage, QVariantMap()); + } + _codePreview->setPlainText(code); +} + + +void MainWindow::onLoadBitmap() +{ + QSettings settings; + auto lastDir = settings.value("bitmap.lastDir").toString(); + auto fileName = QFileDialog::getOpenFileName( + this, + tr("Open Bitmap"), + lastDir, + tr("Images (*.png *.jpg *.jpeg *.gif *.pbm *.pgm *.ppm *.xbm *.xpm *.bpm)")); + if (fileName.isEmpty()) { + return; + } + QFileInfo fileInfo(fileName); + settings.setValue("bitmap.lastDir", fileInfo.absoluteDir().path()); + QImage newImage; + if (!newImage.load(fileName)) { + QMessageBox::warning(this, tr("Loading Image Failed"), + tr("

Failed to load the image

" + "

Could not load the specified image file. " + "Make sure the image is in a supported format.

")); + return; + } + if (newImage.width() > 0x100 || newImage.height() > 0x100) { + QMessageBox::warning(this, tr("Image is Too Large"), + tr("

Image is Too Large

" + "

One dimension of the loaded image is larger than " + "256 pixel. This software is designed for small bitmaps.

")); + return; + } + + _currentImage = newImage; + _bitmapPanel->setImage(newImage); + _bitmapPanel->setConverter(selectedConverter()); + updateBitmapInfo(); + updateCode(); +} + + +void MainWindow::closeEvent(QCloseEvent *event) +{ + saveSettings(); + QMainWindow::closeEvent(event); +} + diff --git a/MainWindow.hpp b/MainWindow.hpp new file mode 100644 index 0000000..4107239 --- /dev/null +++ b/MainWindow.hpp @@ -0,0 +1,89 @@ +// +// (c)2021 by Lucky Resistor. https://luckyresistor.me/ +// +// 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 . +// +#pragma once + + +#include + + +class Converter; +class QPlainTextEdit; +class QComboBox; +class QLabel; +class BitmapPanel; + + +/// The main widnow +/// +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = nullptr); + ~MainWindow() override; + +private: + /// Initialize the converter list + /// + void initializeConverterList(); + + /// Initialize the user interface. + /// + void initializeUi(); + + /// Initialize the menu + /// + void initializeMenu(); + + /// Load all settings + /// + void loadSettings(); + + /// Save all settings. + /// + void saveSettings(); + + /// The current converter. + /// + Converter* selectedConverter() const; + + /// Update the bitmap info. + /// + void updateBitmapInfo(); + + /// Update the code. + /// + void updateCode(); + +private Q_SLOTS: + /// Load a bitmap. + /// + void onLoadBitmap(); + +protected: // Implement QWidget + void closeEvent(QCloseEvent *event) override; + +private: + QComboBox *_formatSelector; ///< The format selector. + QPlainTextEdit *_codePreview; ///< The generated code. + BitmapPanel *_bitmapPanel; ///< The bitmap panel. + QImage _currentImage; ///< The current loaded image + QLabel *_bitmapInfo; ///< The label with the image info. + QList _converterList; ///< A list of available converters. +}; + diff --git a/MicropythonBitmapTool.pro b/MicropythonBitmapTool.pro new file mode 100644 index 0000000..c736f35 --- /dev/null +++ b/MicropythonBitmapTool.pro @@ -0,0 +1,55 @@ +QT += core gui + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +CONFIG += c++17 + +TARGET = "MicroPython Bitmap Tool" + +# You can make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + BitmapPanel.cpp \ + BitmapPreview.cpp \ + Converter.cpp \ + ConverterFramebuf.cpp \ + ConverterFramebufMono.cpp \ + ConverterFramebufMonoHLSB.cpp \ + ConverterFramebufMonoHMSB.cpp \ + ConverterFramebufMonoVLSB.cpp \ + LegendData.cpp \ + LegendEntry.cpp \ + OverlayPainter.cpp \ + main.cpp \ + MainWindow.cpp + +HEADERS += \ + BitmapPanel.hpp \ + BitmapPreview.hpp \ + Converter.hpp \ + ConverterFramebuf.hpp \ + ConverterFramebufMono.hpp \ + ConverterFramebufMonoHLSB.hpp \ + ConverterFramebufMonoHMSB.hpp \ + ConverterFramebufMonoVLSB.hpp \ + LegendData.hpp \ + LegendEntry.hpp \ + MainWindow.hpp \ + OverlayMode.hpp \ + OverlayPainter.hpp + +RESOURCES += \ + data/data.qrc + +macos:ICON = data/AppIcon.icns +win32:RC_ICONS = data/AppIcon.ico + +# Default rules for deployment. +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target + +DISTFILES += \ + data/application.css diff --git a/OverlayMode.hpp b/OverlayMode.hpp new file mode 100644 index 0000000..22eecd3 --- /dev/null +++ b/OverlayMode.hpp @@ -0,0 +1,25 @@ +// +// (c)2021 by Lucky Resistor. https://luckyresistor.me/ +// +// 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 . +// +#pragma once + + +enum class OverlayMode : int { + None = 0, + BitAssigments = 1, + PixelInterpretation = 2 +}; + diff --git a/OverlayPainter.cpp b/OverlayPainter.cpp new file mode 100644 index 0000000..338cfad --- /dev/null +++ b/OverlayPainter.cpp @@ -0,0 +1,71 @@ +// +// (c)2021 by Lucky Resistor. https://luckyresistor.me/ +// +// 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 "OverlayPainter.hpp" + + +OverlayPainter::OverlayPainter( + QPainter *painter, + const QRect &updateRect, + int displayFactor, + const QSize &imageSize, + const QSize &generatedSize) +: + _p(painter), + _updateRect(updateRect), + _displayFactor(displayFactor), + _imageSize(imageSize), + _generatedSize(generatedSize) +{ +#ifdef Q_OS_WIN32 + _pixelFont = QFont("Consolas"); +#else +#ifdef Q_OS_MAC + _pixelFont = QFont("Menlo"); +#else + _pixelFont = QFont("Lucida Console"); +#endif +#endif + _pixelFont.setPixelSize(displayFactor - 1); + _p->setFont(_pixelFont); +} + + +void OverlayPainter::drawPixelOutline(const QRect &rect, const QColor &outlineColor, int outlineWidth, int offset) +{ + _p->setBrush(QBrush()); + _p->setPen(QPen(outlineColor, outlineWidth, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin)); + const qreal offset1 = static_cast(outlineWidth)/2 - static_cast(offset); + const qreal offset2 = -static_cast(outlineWidth)/2 + static_cast(offset); + const auto rectToDraw = pixelRectF(rect).adjusted(offset1, offset1, offset2, offset2); + _p->drawRect(rectToDraw); +} + + +void OverlayPainter::drawPixelText(const QRect &rect, const QColor &textColor, const QString &text) +{ + _p->setBrush(QBrush()); + _p->setPen(QPen(textColor)); + _p->drawText(pixelRectF(rect), Qt::AlignCenter, text); +} + + +bool OverlayPainter::arePixelUpdated(const QRect &rect) const +{ + const auto rectToDraw = pixelRectF(rect).adjusted(-64, -64, 64, 64); + return rectToDraw.intersects(_updateRect); +} + diff --git a/OverlayPainter.hpp b/OverlayPainter.hpp new file mode 100644 index 0000000..e8f2d2d --- /dev/null +++ b/OverlayPainter.hpp @@ -0,0 +1,106 @@ +// +// (c)2021 by Lucky Resistor. https://luckyresistor.me/ +// +// 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 . +// +#pragma once + + +#include + + +/// The overlay painter. +/// +class OverlayPainter +{ +public: + /// ctor + /// + OverlayPainter( + QPainter *painter, + const QRect &updateRect, + int displayFactor, + const QSize &imageSize, + const QSize &generatedSize); + +public: + /// Access the painter. + /// + inline QPainter *painter() const { return _p; }; + + /// Access the display factor. + /// + inline int displayFactor() const { return _displayFactor; }; + + /// Access the image size. + /// + inline const QSize& imageSize() const { return _imageSize; } + + /// Access the generated size. + /// + inline const QSize& generatedSize() const { return _generatedSize; } + + /// Access the image rect. + /// + inline const QRect imageRect() const { return QRect(QPoint(0, 0), imageSize()); } + + /// Access the generated rect + /// + inline const QRect generatedRect() const { return QRect(QPoint(0, 0), generatedSize()); } + + /// Get a rectangle for a pixal area. + /// + inline QRectF pixelRectF(int x, int y, int width, int height) const { + return QRectF(x * _displayFactor, y * _displayFactor, width * _displayFactor, height * _displayFactor); + } + + /// Get a rectangle for a pixel rect. + /// + inline QRectF pixelRectF(const QRect &rect) const { + return pixelRectF(rect.x(), rect.y(), rect.width(), rect.height()); + } + + /// Draw a outline around a pixel area. + /// + /// @param rect The rect for the pixel area. + /// @param outlineColor The color of the outline. + /// @param outlineWidth THe width of the outline in pixels. + /// @param offset The offset of the outline. +1 = around the area, 0 in the area. + /// + void drawPixelOutline( + const QRect &rect, + const QColor &outlineColor, + int outlineWidth = 1, + int offset = 0); + + /// Write text into a pixel area + /// + void drawPixelText( + const QRect &rect, + const QColor &textColor, + const QString &text); + + /// Check if a pixel region ready for repaint. + /// + bool arePixelUpdated(const QRect &rect) const; + +private: + QPainter *_p; ///< The painter used. + QRect _updateRect; ///< The update rect. + int _displayFactor; ///< The display factor. + QSize _imageSize; ///< The size of the image. + QSize _generatedSize; ///< The generated size. + QFont _pixelFont; ///< The font used to draw into the pixels. +}; + diff --git a/README.md b/README.md index 57800ca..d80d86d 100644 --- a/README.md +++ b/README.md @@ -1 +1,5 @@ -# MicroPythonBitmapTool \ No newline at end of file +# MicroPython Bitmap Tool + +https://luckyresistor.me/applications/micropython-bitmap-tool/ + + diff --git a/data/AppIcon.icns b/data/AppIcon.icns new file mode 100644 index 0000000..6c01a0f Binary files /dev/null and b/data/AppIcon.icns differ diff --git a/data/AppIcon.ico b/data/AppIcon.ico new file mode 100644 index 0000000..5c182ed Binary files /dev/null and b/data/AppIcon.ico differ diff --git a/data/AppLogo.png b/data/AppLogo.png new file mode 100644 index 0000000..159a917 Binary files /dev/null and b/data/AppLogo.png differ diff --git a/data/AppLogo@2x.png b/data/AppLogo@2x.png new file mode 100644 index 0000000..a26784f Binary files /dev/null and b/data/AppLogo@2x.png differ diff --git a/data/AppLogo@3x.png b/data/AppLogo@3x.png new file mode 100644 index 0000000..3c1a363 Binary files /dev/null and b/data/AppLogo@3x.png differ diff --git a/data/application.css b/data/application.css new file mode 100644 index 0000000..eb2e3bb --- /dev/null +++ b/data/application.css @@ -0,0 +1,16 @@ + +#CentralWidget, QMainWindow { + margin: 0; + padding: 0; +} + +#SettingsPanel { + background-color: #616875; + padding: 16px; +} + +#BitmapInfo { + border: 1px solid rgba(0,0,0,0.25); + padding: 8px; + margin: 0px 0px 16px 0px; +} diff --git a/data/data.qrc b/data/data.qrc new file mode 100644 index 0000000..eed1e07 --- /dev/null +++ b/data/data.qrc @@ -0,0 +1,10 @@ + + + AppLogo.png + AppLogo@2x.png + AppLogo@3x.png + + + application.css + + diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..eae2dc7 --- /dev/null +++ b/main.cpp @@ -0,0 +1,38 @@ +// +// (c)2021 by Lucky Resistor. https://luckyresistor.me/ +// +// 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 "MainWindow.hpp" + +#include +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + a.setApplicationName("MicroPython Bitmap Tool"); + a.setApplicationVersion("1.0"); + a.setApplicationDisplayName("MicroPython Bitmap Tool"); + a.setOrganizationDomain("luckyresistor.me"); + a.setOrganizationName("Lucky Resistoor"); + QFile styleSheet(":/stylesheet/application.css"); + styleSheet.open(QIODevice::ReadOnly); + a.setStyleSheet(QString::fromUtf8(styleSheet.readAll())); + styleSheet.close(); + + MainWindow w; + w.show(); + return a.exec(); +}