diff --git a/ApplicationController.cpp b/ApplicationController.cpp new file mode 100644 index 0000000..efd2c7b --- /dev/null +++ b/ApplicationController.cpp @@ -0,0 +1,67 @@ +// +// (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 "ApplicationController.hpp" + + +ApplicationController *ApplicationController::_instance = nullptr; + + +ApplicationController::ApplicationController(QObject *parent) + : QObject(parent) +{ + _instance = this; + + // Start as soon the main loop is running. + QMetaObject::invokeMethod(this, &ApplicationController::start, Qt::QueuedConnection); +} + + +QFont ApplicationController::monospaceFont() const +{ + QFont monospaceFont; +#ifdef Q_OS_WIN32 + monospaceFont = QFont("Consolas", 12); +#else +#ifdef Q_OS_MAC + monospaceFont = QFont("Menlo", 12); +#else + monospaceFont = QFont("Lucida Console", 12); +#endif +#endif + return monospaceFont; +} + + +void ApplicationController::start() +{ + _mainWindow = new MainWindow(); + _mainWindow->show(); +} + + +ApplicationController *ApplicationController::instance() +{ + return _instance; +} + + +ApplicationController* gApp() +{ + return ApplicationController::instance(); +} + + diff --git a/ApplicationController.hpp b/ApplicationController.hpp new file mode 100644 index 0000000..be7700e --- /dev/null +++ b/ApplicationController.hpp @@ -0,0 +1,57 @@ +// +// (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 "MainWindow.hpp" + +#include + + +/// The application controller +/// +class ApplicationController : public QObject +{ + Q_OBJECT + +public: + /// ctor + /// + explicit ApplicationController(QObject *parent = nullptr); + +public: + /// Get the default monospaced font. + /// + QFont monospaceFont() const; + +public: + /// Get the global instance. + /// + static ApplicationController* instance(); + +private: + /// Start the application. + /// + Q_SLOT void start(); + +private: + static ApplicationController *_instance; ///< The controller instance. + MainWindow *_mainWindow; ///< The main window. +}; + + +ApplicationController* gApp(); diff --git a/BitmapConverter.cpp b/BitmapConverter.cpp new file mode 100644 index 0000000..f15f3c5 --- /dev/null +++ b/BitmapConverter.cpp @@ -0,0 +1,29 @@ +// +// (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 "BitmapConverter.hpp" + + +BitmapConverter::BitmapConverter(const QString &displayName) + : Converter(displayName) +{ +} + + +Converter::Mode BitmapConverter::mode() const +{ + return Mode::Bitmap; +} diff --git a/BitmapConverter.hpp b/BitmapConverter.hpp new file mode 100644 index 0000000..757e712 --- /dev/null +++ b/BitmapConverter.hpp @@ -0,0 +1,45 @@ +// +// (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" + + +/// The base class for all bitmap converters. +/// +class BitmapConverter : public Converter +{ +public: + /// ctor + /// + BitmapConverter(const QString &displayName); + +public: // implement Converter + Mode mode() const override; + +public: + /// 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; +}; + + diff --git a/BitmapPanel.cpp b/BitmapPanel.cpp index e6db3ab..f71e71a 100644 --- a/BitmapPanel.cpp +++ b/BitmapPanel.cpp @@ -17,16 +17,19 @@ #include "BitmapPanel.hpp" +#include "ApplicationController.hpp" #include "Converter.hpp" #include "BitmapPreview.hpp" -#include -#include -#include -#include +#include +#include +#include +#include +#include -BitmapPanel::BitmapPanel(QWidget *parent) : QWidget(parent) +BitmapPanel::BitmapPanel(QWidget *parent) + : QWidget(parent), _converter(nullptr) { initializeUi(); } @@ -35,16 +38,49 @@ BitmapPanel::BitmapPanel(QWidget *parent) : QWidget(parent) void BitmapPanel::setImage(const QImage &image) { _bitmapPreview->setImage(image); + _bitmapPreview->setFixedSize(_bitmapPreview->sizeHint()); } void BitmapPanel::setConverter(const Converter *converter) { - _bitmapPreview->setConverter(converter); + if (_converter != converter) { + _converter = converter; + _bitmapPreview->setConverter(converter); + _bitmapPreview->setFixedSize(_bitmapPreview->sizeHint()); + _characterSelector->setVisible(_converter->mode() == Converter::Mode::Font); + } +} + + +void BitmapPanel::setCharacters(const QString &characters) +{ + if (_characters != characters) { + _characters = characters; + _characterSelector->clear(); + for (int i = 0; i < characters.size(); ++i) { + const auto c = _characters.at(i); + _characterSelector->addItem(QString("%1: %2 0x%3").arg(i, 3, 10, QChar('0')) + .arg(QString(c)).arg(c.unicode(), 4, 16, QChar('0')), c); + } + _characterSelector->setCurrentIndex(0); + } +} + + +void BitmapPanel::setParameter(const QVariantMap ¶meter) +{ + _bitmapPreview->setParameter(parameter); _bitmapPreview->setFixedSize(_bitmapPreview->sizeHint()); } +QChar BitmapPanel::selectedCharacter() const +{ + return _characterSelector->currentData().toChar(); +} + + void BitmapPanel::initializeUi() { setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); @@ -72,6 +108,35 @@ void BitmapPanel::initializeUi() auto previewSettingsLayout = new QHBoxLayout(previewSettingsPanel); previewSettingsLayout->setContentsMargins(0, 0, 0, 0); previewSettingsLayout->setSpacing(4); + + _fontConversionTools = new QFrame(); + auto fontConversionLayout = new QHBoxLayout(_fontConversionTools); + fontConversionLayout->setContentsMargins(0, 0, 0, 0); + fontConversionLayout->setSpacing(2); + previewSettingsLayout->addWidget(_fontConversionTools); + + auto previousCharButton = new QPushButton("<"); + fontConversionLayout->addWidget(previousCharButton); + _characterSelector = new QComboBox(); + _characterSelector->setFont(gApp()->monospaceFont()); + fontConversionLayout->addWidget(_characterSelector); + auto nextCharButton = new QPushButton(">"); + fontConversionLayout->addWidget(nextCharButton); + connect(_characterSelector, &QComboBox::currentIndexChanged, [=]{ + Q_EMIT selectedCharacterChanged(_characterSelector->currentData().toChar()); + }); + connect(previousCharButton, &QPushButton::clicked, [=]{ + auto currentIndex = _characterSelector->currentIndex(); + if (currentIndex > 0) { + _characterSelector->setCurrentIndex(currentIndex - 1); + } + }); + connect(nextCharButton, &QPushButton::clicked, [=]{ + auto currentIndex = _characterSelector->currentIndex(); + if (currentIndex < (_characterSelector->count()-1)) { + _characterSelector->setCurrentIndex(currentIndex + 1); + } + }); previewSettingsLayout->addStretch(); previewSettingsLayout->addWidget(new QLabel(tr("Overlay Mode:"))); _overlaySelector = new QComboBox(); diff --git a/BitmapPanel.hpp b/BitmapPanel.hpp index d51e3fb..f30a7f0 100644 --- a/BitmapPanel.hpp +++ b/BitmapPanel.hpp @@ -23,13 +23,18 @@ class BitmapPreview; class Converter; class QComboBox; +class QFrame; +/// A panel with the scrollable bitmap preview. +/// class BitmapPanel : public QWidget { Q_OBJECT public: + /// Create the panel. + /// explicit BitmapPanel(QWidget *parent = nullptr); public: @@ -41,11 +46,34 @@ public: /// void setConverter(const Converter *converter); -private: - void initializeUi(); + /// Set the current character set. + /// + void setCharacters(const QString &characters); + + /// Set the current parameter. + /// + void setParameter(const QVariantMap ¶meter); + + /// Get the current selected character + /// + QChar selectedCharacter() const; private: + /// Initialize all UI elements. + /// + void initializeUi(); + +Q_SIGNALS: + /// Emitted if the selected character changes. + /// + void selectedCharacterChanged(QChar c); + +private: + const Converter *_converter; ///< The current converter. + QString _characters; ///< The list of characters to convert. BitmapPreview *_bitmapPreview; ///< The bitmap preview; + QFrame *_fontConversionTools; ///< The area with tools for font conversion. + QComboBox *_characterSelector; ///< The selector for a single character. QComboBox *_overlaySelector; ///< The selector for the overlays. }; diff --git a/BitmapPreview.cpp b/BitmapPreview.cpp index 6080adb..e8c88ba 100644 --- a/BitmapPreview.cpp +++ b/BitmapPreview.cpp @@ -48,9 +48,7 @@ void BitmapPreview::setImage(const QImage &image) if (_image != image) { _image = image; _pixmap = QPixmap::fromImage(image); - if (_converter != nullptr) { - setGeneratedSize(_converter->generatedSize(_image.size())); - } + updateGeneratedSize(); recalculate(); update(); } @@ -80,9 +78,18 @@ void BitmapPreview::setConverter(const Converter *converter) { if (_converter != converter) { _converter = converter; - if (_converter != nullptr) { - setGeneratedSize(_converter->generatedSize(_image.size())); - } + updateGeneratedSize(); + update(); + } +} + + +void BitmapPreview::setParameter(const QVariantMap ¶meter) +{ + if (_parameter != parameter) { + _parameter = parameter; + updateGeneratedSize(); + recalculate(); update(); } } @@ -118,7 +125,7 @@ void BitmapPreview::paintEvent(QPaintEvent *pe) p.setOpacity(1.0); p.translate(x, y); OverlayPainter op(&p, pe->rect(), _displayFactor, _pixmap.size(), _generatedSize); - _converter->paintOverlay(op, _overlayMode, _image); + _converter->paintOverlay(op, _overlayMode, _image, _parameter); } } else { p.setPen(Qt::white); @@ -134,6 +141,16 @@ void BitmapPreview::resizeEvent(QResizeEvent *e) } +void BitmapPreview::updateGeneratedSize() +{ + if (_converter != nullptr && _converter->mode() == Converter::Mode::Bitmap) { + setGeneratedSize(_converter->generatedSize(_image.size(), _parameter)); + } else { + setGeneratedSize(_image.size()); + } +} + + void BitmapPreview::recalculate() { if (_pixmap.isNull()) { diff --git a/BitmapPreview.hpp b/BitmapPreview.hpp index c8b39d3..8b32032 100644 --- a/BitmapPreview.hpp +++ b/BitmapPreview.hpp @@ -22,6 +22,7 @@ #include #include #include +#include class Converter; @@ -49,6 +50,10 @@ public: /// void setConverter(const Converter *converter); + /// Set the current parameter. + /// + void setParameter(const QVariantMap ¶meter); + public: QSize sizeHint() const override; QSize minimumSizeHint() const override; @@ -58,6 +63,10 @@ protected: // QWidget interface void resizeEvent(QResizeEvent *event) override; private: + /// update the generated size + /// + void updateGeneratedSize(); + /// Recalculate all sizes /// void recalculate(); @@ -74,5 +83,6 @@ private: QSize _minimumSize; ///< The preferred size of this widget. OverlayMode _overlayMode; ///< The overlay mode. const Converter *_converter; ///< The current converter. + QVariantMap _parameter; ///< The current set of parameter. }; diff --git a/Converter.cpp b/Converter.cpp index 48db2f8..f15f825 100644 --- a/Converter.cpp +++ b/Converter.cpp @@ -29,12 +29,18 @@ QString Converter::displayName() const } -QSize Converter::generatedSize(const QSize &imageSize) const +QSize Converter::generatedSize(const QSize &imageSize, const QVariantMap&) const { return imageSize; } +ParameterDefinitionPtr Converter::createParameterDefinition() const +{ + return ParameterDefinition::create(); +} + + LegendDataPtr Converter::legendData(OverlayMode) const { auto ld = LegendData::create(); @@ -43,7 +49,7 @@ LegendDataPtr Converter::legendData(OverlayMode) const } -void Converter::paintOverlay(OverlayPainter &op, OverlayMode, const QImage&) const +void Converter::paintOverlay(OverlayPainter &op, OverlayMode, const QImage&, const QVariantMap&) const { op.drawPixelOutline(op.imageRect(), colorBitmapSizeOriginal, 1, 2); if (op.generatedSize().isValid() && !op.generatedSize().isEmpty()) { diff --git a/Converter.hpp b/Converter.hpp index c0cd353..fe0a188 100644 --- a/Converter.hpp +++ b/Converter.hpp @@ -20,6 +20,7 @@ #include "LegendData.hpp" #include "OverlayPainter.hpp" #include "OverlayMode.hpp" +#include "ParameterDefinition.hpp" #include #include @@ -30,10 +31,17 @@ /// class Converter { +public: + enum class Mode { + Bitmap, + Font + }; + public: /// Create a new converter. /// /// @param displayName The name displayed in the UI for this coonverter. + /// @param mode The converter mode. /// Converter(const QString &displayName); @@ -46,21 +54,21 @@ public: /// QString displayName() const; + /// Get the mode for this converter. + /// + virtual Mode mode() const = 0; + 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; + virtual QSize generatedSize(const QSize &imageSize, const QVariantMap ¶meter) const; - /// Generate the code from the given image. + /// Create the parameter definition for the converter. /// - /// @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; + virtual ParameterDefinitionPtr createParameterDefinition() const; /// Return a legend for the bitmap preview. /// @@ -71,8 +79,9 @@ public: /// @param p The painter. /// @param mode The overlay mode (never `None`). /// @param image The image. + /// @param parameter The current set of parameter. /// - virtual void paintOverlay(OverlayPainter &p, OverlayMode mode, const QImage &image) const; + virtual void paintOverlay(OverlayPainter &p, OverlayMode mode, const QImage &image, const QVariantMap ¶meter) const; protected: /// The color for the image frame. diff --git a/ConverterFramebuf.cpp b/ConverterFramebuf.cpp index 058e900..173744d 100644 --- a/ConverterFramebuf.cpp +++ b/ConverterFramebuf.cpp @@ -21,7 +21,7 @@ ConverterFramebuf::ConverterFramebuf(const QString &displayName) - : Converter(displayName) + : BitmapConverter(displayName) { } diff --git a/ConverterFramebuf.hpp b/ConverterFramebuf.hpp index 8a261fc..58d1e7a 100644 --- a/ConverterFramebuf.hpp +++ b/ConverterFramebuf.hpp @@ -17,12 +17,12 @@ #pragma once -#include "Converter.hpp" +#include "BitmapConverter.hpp" /// Base class of framebuf converters. /// -class ConverterFramebuf : public Converter +class ConverterFramebuf : public BitmapConverter { public: ConverterFramebuf(const QString &displayName); diff --git a/ConverterFramebufMono.cpp b/ConverterFramebufMono.cpp index faa0496..ab6cc7b 100644 --- a/ConverterFramebufMono.cpp +++ b/ConverterFramebufMono.cpp @@ -25,124 +25,29 @@ ConverterFramebufMono::ConverterFramebufMono( int unitSize) : ConverterFramebuf(displayName), - _unitOrientation(unitOrientation), - _bitDirection(bitDirection), - _unitSize(unitSize) + MonoTools(unitOrientation, bitDirection, unitSize) { } -QSize ConverterFramebufMono::generatedSize(const QSize &imageSize) const +QSize ConverterFramebufMono::generatedSize(const QSize &imageSize, const QVariantMap ¶meter) 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; - } - } + return monoGeneratedSize(imageSize, parameter); } 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")); - } + addMonoLegendData(data, mode); return data; } -void ConverterFramebufMono::paintOverlay(OverlayPainter &p, OverlayMode mode, const QImage &image) const +void ConverterFramebufMono::paintOverlay(OverlayPainter &p, OverlayMode mode, const QImage &image, const QVariantMap ¶meter) 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; + ConverterFramebuf::paintOverlay(p, mode, image, parameter); + monoPaintOverlay(p, mode, image, parameter); } diff --git a/ConverterFramebufMono.hpp b/ConverterFramebufMono.hpp index 54bd17b..442bde2 100644 --- a/ConverterFramebufMono.hpp +++ b/ConverterFramebufMono.hpp @@ -17,24 +17,14 @@ #pragma once +#include "MonoTools.hpp" #include "ConverterFramebuf.hpp" /// The base class of all mono converters /// -class ConverterFramebufMono : public ConverterFramebuf +class ConverterFramebufMono : public ConverterFramebuf, public MonoTools { -protected: - enum class UnitOrientation { - Horizontal, - Vertical - }; - - enum class BitDirection { - LSB, - MSB - }; - public: /// Create a new mono converter. /// @@ -49,30 +39,8 @@ public: int unitSize); public: // Converter interface - QSize generatedSize(const QSize &imageSize) const override; + QSize generatedSize(const QSize &imageSize, const QVariantMap ¶meter) 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. + void paintOverlay(OverlayPainter &p, OverlayMode mode, const QImage &image, const QVariantMap ¶meter) const override; }; diff --git a/ConverterFramebufMonoHLSB.cpp b/ConverterFramebufMonoHLSB.cpp index 779e881..814af7c 100644 --- a/ConverterFramebufMonoHLSB.cpp +++ b/ConverterFramebufMonoHLSB.cpp @@ -26,7 +26,7 @@ ConverterFramebufMonoHLSB::ConverterFramebufMonoHLSB() } -QString ConverterFramebufMonoHLSB::generateCode(const QImage &image, const QVariantMap&) const +QString ConverterFramebufMonoHLSB::generateCode(const QImage &image, const QVariantMap ¶meter) const { QByteArray data; for (int y = 0; y < image.height(); ++y) { @@ -34,5 +34,5 @@ QString ConverterFramebufMonoHLSB::generateCode(const QImage &image, const QVari data.append(static_cast(readUnit(x, y, 1, 0, 8, image))); } } - return createCode(data, generatedSize(image.size()), "MONO_HLSB"); + return createCode(data, generatedSize(image.size(), parameter), "MONO_HLSB"); } diff --git a/ConverterFramebufMonoHMSB.cpp b/ConverterFramebufMonoHMSB.cpp index fed9891..da7c7c8 100644 --- a/ConverterFramebufMonoHMSB.cpp +++ b/ConverterFramebufMonoHMSB.cpp @@ -23,7 +23,7 @@ ConverterFramebufMonoHMSB::ConverterFramebufMonoHMSB() } -QString ConverterFramebufMonoHMSB::generateCode(const QImage &image, const QVariantMap&) const +QString ConverterFramebufMonoHMSB::generateCode(const QImage &image, const QVariantMap ¶meter) const { QByteArray data; for (int y = 0; y < image.height(); ++y) { @@ -31,6 +31,6 @@ QString ConverterFramebufMonoHMSB::generateCode(const QImage &image, const QVari data.append(static_cast(readUnit(x+7, y, -1, 0, 8, image))); } } - return createCode(data, generatedSize(image.size()), "MONO_HMSB"); + return createCode(data, generatedSize(image.size(), parameter), "MONO_HMSB"); } diff --git a/ConverterFramebufMonoVLSB.cpp b/ConverterFramebufMonoVLSB.cpp index 868f6c1..2c2443c 100644 --- a/ConverterFramebufMonoVLSB.cpp +++ b/ConverterFramebufMonoVLSB.cpp @@ -23,7 +23,7 @@ ConverterFramebufMonoVLSB::ConverterFramebufMonoVLSB() } -QString ConverterFramebufMonoVLSB::generateCode(const QImage &image, const QVariantMap&) const +QString ConverterFramebufMonoVLSB::generateCode(const QImage &image, const QVariantMap ¶meter) const { QByteArray data; for (int y = 0; y < image.height(); y += 8) { @@ -31,6 +31,6 @@ QString ConverterFramebufMonoVLSB::generateCode(const QImage &image, const QVari data.append(static_cast(readUnit(x, y+7, 0, -1, 8, image))); } } - return createCode(data, generatedSize(image.size()), "MONO_VLSB"); + return createCode(data, generatedSize(image.size(), parameter), "MONO_VLSB"); } diff --git a/FontConverter.cpp b/FontConverter.cpp new file mode 100644 index 0000000..5ee49bf --- /dev/null +++ b/FontConverter.cpp @@ -0,0 +1,149 @@ +// +// (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 "FontConverter.hpp" + + +FontConverter::FontConverter(const QString &displayName) + : Converter(displayName) +{ +} + + +Converter::Mode FontConverter::mode() const +{ + return Mode::Font; +} + + +ParameterDefinitionPtr FontConverter::createParameterDefinition() const +{ + auto pd = Converter::createParameterDefinition(); + pd->addIntegerSize("maximumSize", QObject::tr("Maximum Size"), + QSize(12, 16), QSize(8, 8), QSize(256, 256)); + pd->addIntegerPosition("charOffset", QObject::tr("Char Offset"), + QPoint(0, 0), QPoint(-256, -256), QPoint(256, 256)); + pd->addCheckbox("convertMono", QObject::tr("Convert to Mono"), false); + pd->addInteger("convertMonoThreshold", QObject::tr("Conversion Threshold"), 128, 0, 255); + pd->addCheckbox("preferBitmapFont", QObject::tr("Prefer Bitmap Font"), false); + pd->addCheckbox("noAntialiasFont", QObject::tr("No Antialias Font"), false); + pd->addCheckbox("trimLeft", QObject::tr("Trim Left Side"), true); + pd->addCheckbox("trimRight", QObject::tr("Trim Right Side"), true); + pd->addCheckbox("invertFont", QObject::tr("Invert the Font"), false); + return pd; +} + + +QImage FontConverter::generateImage(const QFont &font, QChar c, const QVariantMap ¶meter) const +{ + auto maximumSize = parameter["maximumSize"].toSize(); + if (!maximumSize.isValid() || maximumSize.isNull()) { + maximumSize = QSize(8, 8); + } + const auto charOffset = parameter["charOffset"].toPoint(); + QImage result(maximumSize, QImage::Format_ARGB32_Premultiplied); + result.setDevicePixelRatio(1.0); + QPainter p; + p.begin(&result); + p.fillRect(result.rect(), Qt::white); + p.setRenderHint(QPainter::TextAntialiasing, false); + auto fontForDrawing = font; + int fontStyleStrategy = 0; + if (parameter["preferBitmapFont"].toBool()) { + fontStyleStrategy |= QFont::PreferBitmap; + } + if (parameter["noAntialiasFont"].toBool()) { + fontStyleStrategy |= QFont::NoAntialias; + } + fontStyleStrategy |= QFont::NoSubpixelAntialias; + fontForDrawing.setStyleStrategy(static_cast(fontStyleStrategy)); + QFontMetrics fontMetrics(fontForDrawing); + auto charBoundingRect = fontMetrics.boundingRect(c); + // Make sure the first pixel is in the bitmap, draw baseline at ascent. + QPoint targetPoint(-charBoundingRect.left(), fontMetrics.ascent()); + targetPoint += charOffset; + p.setFont(fontForDrawing); + p.drawText(targetPoint, QString(c)); + p.end(); + // Postprocess the font. + const auto threshold = static_cast(parameter["convertMonoThreshold"].toInt()); + if (parameter["convertMono"].toBool()) { + for (int x = 0; x < result.width(); ++x) { + for (int y = 0; y < result.height(); ++y) { + const auto pixel = result.pixel(x, y); + if ((pixel & 0xff) <= threshold) { + result.setPixel(x, y, qRgb(0, 0, 0)); + } else { + result.setPixel(x, y, qRgb(255, 255, 255)); + } + } + } + } + // Trim the font. + const auto trimLeft = parameter["trimLeft"].toBool(); + const auto trimRight = parameter["trimRight"].toBool(); + if (trimLeft || trimRight) { + auto resultRect = result.rect(); + int leftTrim = 0; + int rightTrim = 0; + if (trimLeft) { + for (int x = 0; x < result.width(); ++x) { + bool hasPixelSet = false; + for (int y = 0; y < result.height(); ++y) { + const auto pixel = result.pixel(x, y); + if ((pixel & 0xff) <= threshold) { + hasPixelSet = true; + break; + } + } + if (hasPixelSet) { + break; + } + leftTrim += 1; + } + } + if (trimRight) { + for (int x = result.width()-1; x >= 0; --x) { + bool hasPixelSet = false; + for (int y = 0; y < result.width(); ++y) { + const auto pixel = result.pixel(x, y); + if ((pixel & 0xff) <= threshold) { + hasPixelSet = true; + break; + } + } + if (hasPixelSet) { + break; + } + rightTrim += 1; + } + } + if ((resultRect.width() - leftTrim - rightTrim) <= 0) { + // The font image is empty, use one pixel line. + resultRect.setWidth(1); + } else { + resultRect = resultRect.marginsRemoved(QMargins(leftTrim, 0, rightTrim, 0)); + } + if (resultRect != result.rect()) { + result = result.copy(resultRect); + } + } + // Invert the font. + if (parameter["invertFont"].toBool()) { + result.invertPixels(); + } + return result; +} diff --git a/FontConverter.hpp b/FontConverter.hpp new file mode 100644 index 0000000..cf34d99 --- /dev/null +++ b/FontConverter.hpp @@ -0,0 +1,58 @@ +// +// (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" + + +/// The base class for all font converters. +/// +class FontConverter : public Converter +{ +public: + /// ctor + /// + FontConverter(const QString &displayName); + +public: // implement Converter + Mode mode() const override; + ParameterDefinitionPtr createParameterDefinition() const override; + +public: + /// Create a bitmap for a given character. + /// + /// This method should return the given character in exact the size and format it will + /// be converted as bitmap. + /// + /// @param font The font for the character. + /// @param c The character to convert. + /// @param parameter The current map of parameters. + /// + virtual QImage generateImage(const QFont &font, QChar c, const QVariantMap ¶meter) const; + + /// Generate the code from the given font. + /// + /// The string with all characters to convert is passed as parameter 'characters'. + /// + /// @param font The font to convert. + /// @param parameter A map with parameters passed to this converter. + /// @return The generated code. + /// + virtual QString generateCode(const QFont &font, const QVariantMap ¶meter) const = 0; +}; + diff --git a/FontConverterFramebufMono.cpp b/FontConverterFramebufMono.cpp new file mode 100644 index 0000000..c36232c --- /dev/null +++ b/FontConverterFramebufMono.cpp @@ -0,0 +1,52 @@ +// +// (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 "FontConverterFramebufMono.hpp" + + +FontConverterFramebufMono::FontConverterFramebufMono() +: + FontConverter("MicroPython Font Mono VLSB"), + MonoTools(UnitOrientation::Vertical, BitDirection::LSB, 8) +{ +} + + +QSize FontConverterFramebufMono::generatedSize(const QSize &imageSize, const QVariantMap ¶meter) const +{ + return monoGeneratedSize(imageSize, parameter); +} + + +QString FontConverterFramebufMono::generateCode(const QFont&, const QVariantMap&) const +{ + return "The generated format is not decided yet."; +} + + +LegendDataPtr FontConverterFramebufMono::legendData(OverlayMode mode) const +{ + auto data = FontConverter::legendData(mode); + addMonoLegendData(data, mode); + return data; +} + + +void FontConverterFramebufMono::paintOverlay(OverlayPainter &p, OverlayMode mode, const QImage &image, const QVariantMap ¶meter) const +{ + FontConverter::paintOverlay(p, mode, image, parameter); + monoPaintOverlay(p, mode, image, parameter); +} diff --git a/FontConverterFramebufMono.hpp b/FontConverterFramebufMono.hpp new file mode 100644 index 0000000..aaca602 --- /dev/null +++ b/FontConverterFramebufMono.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 "FontConverter.hpp" +#include "MonoTools.hpp" + + +/// A simple converter to create mono bitmap fonts. +/// +class FontConverterFramebufMono : public FontConverter, public MonoTools +{ +public: + /// ctor + /// + FontConverterFramebufMono(); + +public: // implement FontConverter + QSize generatedSize(const QSize &imageSize, const QVariantMap ¶meter) const override; + QString generateCode(const QFont &font, const QVariantMap ¶meter) const override; + LegendDataPtr legendData(OverlayMode mode) const override; + void paintOverlay(OverlayPainter &p, OverlayMode mode, const QImage &image, const QVariantMap ¶meter) const override; +}; + diff --git a/MainWindow.cpp b/MainWindow.cpp index 522e40c..5597f8a 100644 --- a/MainWindow.cpp +++ b/MainWindow.cpp @@ -17,10 +17,12 @@ #include "MainWindow.hpp" +#include "ApplicationController.hpp" #include "BitmapPanel.hpp" #include "ConverterFramebufMonoVLSB.hpp" #include "ConverterFramebufMonoHLSB.hpp" #include "ConverterFramebufMonoHMSB.hpp" +#include "FontConverterFramebufMono.hpp" #include #include @@ -37,6 +39,11 @@ #include #include #include +#include +#include +#include +#include +#include #include @@ -47,6 +54,9 @@ MainWindow::MainWindow(QWidget *parent) initializeUi(); initializeMenu(); loadSettings(); + onFormatChanged(); + onFontChanged(); + onCharactersChanged(); } @@ -60,13 +70,16 @@ void MainWindow::initializeConverterList() _converterList.append(new ConverterFramebufMonoVLSB()); _converterList.append(new ConverterFramebufMonoHMSB()); _converterList.append(new ConverterFramebufMonoHLSB()); + _converterList.append(new FontConverterFramebufMono()); } void MainWindow::initializeUi() { + const int cFixedWidth = 400; + setMinimumSize(800, 600); - setWindowTitle(tr("Micropython Bitmap Tool - V%1 - Lucky Resistor").arg(qApp->applicationVersion())); + setWindowTitle(tr("MicroPython Bitmap Tool - V%1 - Lucky Resistor").arg(qApp->applicationVersion())); auto centralWidget = new QWidget(); centralWidget->setObjectName("CentralWidget"); @@ -83,15 +96,16 @@ void MainWindow::initializeUi() settingsLayout->setSpacing(4); auto logo = new QLabel(); - logo->setFixedSize(300, 250); + logo->setFixedSize(cFixedWidth, 100); logo->setPixmap(QPixmap(":/images/AppLogo.png")); + logo->setAlignment(Qt::AlignCenter); 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:"))); + settingsLayout->addWidget(new QLabel(tr("Generated Format:"))); _formatSelector = new QComboBox(); for (auto converter : _converterList) { _formatSelector->addItem(converter->displayName()); @@ -99,20 +113,96 @@ void MainWindow::initializeUi() _formatSelector->setCurrentIndex(0); settingsLayout->addWidget(_formatSelector); - settingsLayout->addStretch(); + settingsLayout->addWidget(new QLabel(tr("Parameters:"))); - settingsLayout->addWidget(new QLabel(tr("Loaded Bitmap Info:"))); + auto parameterScroll = new QScrollArea(); + parameterScroll->setObjectName("ParameterScrollArea"); + _parameterFrame = new QFrame(); + _parameterFrame->setObjectName("ParameterFrame"); + _parameterFrame->setFixedWidth(cFixedWidth); + _parameterLayout = new QFormLayout(_parameterFrame); + _parameterLayout->setContentsMargins(8, 8, 8, 8); + parameterScroll->setWidget(_parameterFrame); + parameterScroll->setWidgetResizable(true); + parameterScroll->setFixedWidth(cFixedWidth); + parameterScroll->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); + //parameterScroll->setBackgroundRole(QPalette::Window); + settingsLayout->addWidget(parameterScroll); + + _bitmapConverterFrame = new QFrame(); + auto bitmapConverterLayout = new QVBoxLayout(_bitmapConverterFrame); + bitmapConverterLayout->setContentsMargins(0, 0, 0, 0); + bitmapConverterLayout->setSpacing(4); + settingsLayout->addWidget(_bitmapConverterFrame); + + bitmapConverterLayout->addWidget(new QLabel(tr("Loaded Bitmap Info:"))); _bitmapInfo = new QLabel(tr("No Bitmap Loaded")); _bitmapInfo->setObjectName("BitmapInfo"); - _bitmapInfo->setMinimumHeight(200); + _bitmapInfo->setMinimumHeight(100); _bitmapInfo->setAlignment(Qt::AlignTop|Qt::AlignLeft); - settingsLayout->addWidget(_bitmapInfo); + bitmapConverterLayout->addWidget(_bitmapInfo); auto loadButton = new QPushButton(); loadButton->setText(tr("Load Bitmap")); - settingsLayout->addWidget(loadButton); + bitmapConverterLayout->addWidget(loadButton); connect(loadButton, &QPushButton::clicked, this, &MainWindow::onLoadBitmap); + _fontConverterFrame = new QFrame(); + auto fontConverterLayout = new QVBoxLayout(_fontConverterFrame); + fontConverterLayout->setContentsMargins(0, 0, 0, 0); + fontConverterLayout->setSpacing(4); + settingsLayout->addWidget(_fontConverterFrame); + + fontConverterLayout->addWidget(new QLabel(tr("Selected Font Info:"))); + _fontInfo = new QLabel(tr("No Font Seleccted")); + _fontInfo->setObjectName("FontInfo"); + _fontInfo->setMinimumHeight(100); + _fontInfo->setAlignment(Qt::AlignTop|Qt::AlignLeft); + fontConverterLayout->addWidget(_fontInfo); + + fontConverterLayout->addWidget(new QLabel(tr("Selected Font:"))); + _fontSelector = new QFontComboBox(); + fontConverterLayout->addWidget(_fontSelector); + auto fontDetailsLayout = new QHBoxLayout(); + fontDetailsLayout->setContentsMargins(0, 0, 0, 0); + fontDetailsLayout->setSpacing(4); + fontConverterLayout->addLayout(fontDetailsLayout); + _fontWeightSelector = new QComboBox(); + _fontWeightSelector->addItem(tr("Thin"), static_cast(QFont::Thin)); + _fontWeightSelector->addItem(tr("Extra Light"), static_cast(QFont::ExtraLight)); + _fontWeightSelector->addItem(tr("Light"), static_cast(QFont::Light)); + _fontWeightSelector->addItem(tr("Normal"), static_cast(QFont::Normal)); + _fontWeightSelector->addItem(tr("Medium"), static_cast(QFont::Medium)); + _fontWeightSelector->addItem(tr("Demi Bold"), static_cast(QFont::DemiBold)); + _fontWeightSelector->addItem(tr("Bold"), static_cast(QFont::Bold)); + _fontWeightSelector->addItem(tr("Extra Bold"), static_cast(QFont::ExtraBold)); + _fontWeightSelector->addItem(tr("Black"), static_cast(QFont::Black)); + _fontWeightSelector->setCurrentIndex(3); + _fontSizeSelector = new QSpinBox(); + _fontSizeSelector->setRange(4, 200); + _fontSizeSelector->setValue(12); + _fontHinting = new QComboBox(); + _fontHinting->addItem(tr("Default"), static_cast(QFont::PreferDefaultHinting)); + _fontHinting->addItem(tr("None"), static_cast(QFont::PreferNoHinting)); + _fontHinting->addItem(tr("Vertical"), static_cast(QFont::PreferVerticalHinting)); + _fontHinting->addItem(tr("Full"), static_cast(QFont::PreferFullHinting)); + _fontHinting->setCurrentIndex(0); + fontDetailsLayout->addWidget(new QLabel(tr("Size:"))); + fontDetailsLayout->addWidget(_fontSizeSelector); + fontDetailsLayout->addWidget(new QLabel(tr("Weight:"))); + fontDetailsLayout->addWidget(_fontWeightSelector); + fontDetailsLayout->addWidget(new QLabel(tr("Hinting:"))); + fontDetailsLayout->addWidget(_fontHinting); + fontConverterLayout->addWidget(new QLabel(tr("Characters to convert:"))); + _fontCharacters = new QPlainTextEdit(); + _fontCharacters->setFixedHeight(100); + _fontCharacters->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); + _fontCharacters->setWordWrapMode(QTextOption::WrapAnywhere); + _fontCharacters->setPlainText("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.,:;!?()+-=\"'<>"); + _fontCharacters->setReadOnly(true); + fontConverterLayout->addWidget(_fontCharacters); + connect(_fontCharacters, &QPlainTextEdit::textChanged, this, &MainWindow::onCharactersChanged); + auto previewSplittter = new QSplitter(Qt::Vertical); previewSplittter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); previewSplittter->setChildrenCollapsible(false); @@ -133,15 +223,9 @@ void MainWindow::initializeUi() _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 + const auto monospaceFont = gApp()->monospaceFont(); + _codePreview->setFont(monospaceFont); + _fontCharacters->setFont(monospaceFont); auto codeActions = new QFrame(); auto codeActionsLayout = new QHBoxLayout(codeActions); @@ -161,11 +245,12 @@ void MainWindow::initializeUi() previewSplittter->setStretchFactor(0, 4); previewSplittter->setStretchFactor(1, 1); - connect(_formatSelector, &QComboBox::currentIndexChanged, [=]{ - _bitmapPanel->setConverter(selectedConverter()); - updateBitmapInfo(); - updateCode(); - }); + connect(_formatSelector, &QComboBox::currentIndexChanged, this, &MainWindow::onFormatChanged); + connect(_fontSelector, &QFontComboBox::currentFontChanged, this, &MainWindow::onFontChanged); + connect(_fontSizeSelector, &QSpinBox::valueChanged, this, &MainWindow::onFontChanged); + connect(_fontWeightSelector, &QComboBox::currentIndexChanged, this, &MainWindow::onFontChanged); + connect(_fontHinting, &QComboBox::currentIndexChanged, this, &MainWindow::onFontChanged); + connect(_bitmapPanel, &BitmapPanel::selectedCharacterChanged, this, &MainWindow::onSelectedCharacterChanged); } @@ -229,6 +314,18 @@ void MainWindow::loadSettings() restoreGeometry(settings.value("mainWindow.geometry").toByteArray()); restoreState(settings.value("mainWindow.state").toByteArray()); _formatSelector->setCurrentIndex(settings.value("format.index").toInt(0)); + _currentFont = QFont(settings.value("font.family", "Arial").toString()); + _currentFont.setPixelSize(settings.value("font.size", 12).toInt()); + _currentFont.setWeight(static_cast(settings.value("font.weight", 500).toInt())); + _fontSelector->setCurrentFont(_currentFont); + _fontSizeSelector->setValue(_currentFont.pixelSize()); + const int index = _fontWeightSelector->findData(static_cast(_currentFont.weight())); + if (index >= 0) { + _fontWeightSelector->setCurrentIndex(index); + } else { + _fontWeightSelector->setCurrentIndex(3); // normal + } + _fontHinting->setCurrentIndex(settings.value("font.hinting", 0).toInt()); } @@ -238,6 +335,10 @@ void MainWindow::saveSettings() settings.setValue("mainWindow.geometry", saveGeometry()); settings.setValue("mainWindow.state", saveState()); settings.setValue("format.index", _formatSelector->currentIndex()); + settings.setValue("font.family", _currentFont.family()); + settings.setValue("font.size", _currentFont.pixelSize()); + settings.setValue("font.weight", static_cast(_currentFont.weight())); + settings.setValue("font.hinting", _fontHinting->currentIndex()); } @@ -251,42 +352,120 @@ Converter *MainWindow::selectedConverter() const } -void MainWindow::updateBitmapInfo() +void MainWindow::updateInfoBox() { 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; + if (selectedConverter()->mode() == Converter::Mode::Bitmap) { + 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(), createParameterMap()); + ts << "Generated Size: " << gs.width() << "x" << gs.height() << "

"; + _bitmapInfo->setText(text); + } else { + ts << "

"; + ts << "Family: " << _currentFont.family() << "
"; + ts << "Weight: "; + switch (_currentFont.weight()) { + case QFont::Thin: ts << tr("Thin"); break; + case QFont::ExtraLight: ts << tr("Extra Light"); break; + case QFont::Light: ts << tr("Light"); break; + case QFont::Normal: ts << tr("Normal"); break; + case QFont::Medium: ts << tr("Medium"); break; + case QFont::DemiBold: ts << tr("Demi Bold"); break; + case QFont::Bold: ts << tr("Bold"); break; + case QFont::ExtraBold: ts << tr("Extra Bold"); break; + case QFont::Black: ts << tr("Black"); break; + } + ts << "
"; + ts << "Pixel Size: " << _currentFont.pixelSize() << "
"; + ts << "

"; + _fontInfo->setText(text); } - 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()); + if (selectedConverter()->mode() == Converter::Mode::Bitmap) { + auto bitmapConverter = static_cast(selectedConverter()); + if (!_currentImage.isNull()) { + code = bitmapConverter->generateCode(_currentImage, QVariantMap()); + } + } else { + auto fontConverter = static_cast(selectedConverter()); + code = fontConverter->generateCode(_currentFont, createParameterMap()); } _codePreview->setPlainText(code); } +void MainWindow::updateParameters() +{ + auto converter = selectedConverter(); + if (converter == nullptr) { + return; + } + + // Replace the current parameter definition. + _displayedParameters = converter->createParameterDefinition(); + // Remove the previous UI + while (_parameterLayout->count() > 0) { + auto item = _parameterLayout->takeAt(0); + delete item; + } + for (auto w : _parameterFrame->findChildren()) { + w->deleteLater(); + } + // Build the new parameter UI. + if (_displayedParameters->parameterList().isEmpty()) { + auto label = new QLabel(); + label->setText(tr("No Parameters")); + _parameterLayout->addWidget(label); + } else { + for (const auto ¶meter : _displayedParameters->parameterList()) { + auto pw = parameterFactory.createWidget(parameter); + pw->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + pw->setObjectName(parameter->identifier()); + _parameterLayout->addRow(parameter->label() + ":", pw); + connect(pw, &ParameterWidget::valueChanged, this, &MainWindow::onParameterChanged); + } + } + auto stretchItem = new QWidget(); + stretchItem->setFixedWidth(10); + stretchItem->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); + _parameterLayout->addRow(stretchItem); +} + + +QVariantMap MainWindow::createParameterMap() const +{ + QVariantMap result; + result["characters"] = _fontCharacters->toPlainText(); + result["font"] = _currentFont; + result["char"] = _bitmapPanel->selectedCharacter(); + for (auto pw : _parameterFrame->findChildren()) { + result[pw->objectName()] = pw->value(); + } + return result; +} + + void MainWindow::onLoadBitmap() { QSettings settings; @@ -320,11 +499,78 @@ void MainWindow::onLoadBitmap() _currentImage = newImage; _bitmapPanel->setImage(newImage); _bitmapPanel->setConverter(selectedConverter()); - updateBitmapInfo(); + updateInfoBox(); updateCode(); } +void MainWindow::onFormatChanged() +{ + const auto converter = selectedConverter(); + _bitmapPanel->setConverter(converter); + if (converter->mode() == Converter::Mode::Bitmap) { + _bitmapConverterFrame->setVisible(true); + _fontConverterFrame->setVisible(false); + } else { + _bitmapConverterFrame->setVisible(false); + _fontConverterFrame->setVisible(true); + } + updateParameters(); + updateInfoBox(); + updateCode(); + _bitmapPanel->setParameter(createParameterMap()); + onSelectedCharacterChanged(_bitmapPanel->selectedCharacter()); +} + + +void MainWindow::onFontChanged() +{ + _currentFont = _fontSelector->currentFont(); + _currentFont.setWeight(static_cast(_fontWeightSelector->currentData().toInt())); + _currentFont.setPixelSize(_fontSizeSelector->value()); + const auto hintingPreferences = static_cast(_fontHinting->currentData().toInt()); + _currentFont.setHintingPreference(hintingPreferences); + updateInfoBox(); + updateCode(); + _bitmapPanel->setParameter(createParameterMap()); + onSelectedCharacterChanged(_bitmapPanel->selectedCharacter()); +} + + +void MainWindow::onCharactersChanged() +{ + auto characters = _fontCharacters->toPlainText(); + _bitmapPanel->setCharacters(characters); + _bitmapPanel->setConverter(selectedConverter()); + _bitmapPanel->setParameter(createParameterMap()); + onSelectedCharacterChanged(_bitmapPanel->selectedCharacter()); +} + + +void MainWindow::onSelectedCharacterChanged(QChar c) +{ + auto converter = selectedConverter(); + if (converter == nullptr) { + return; + } + if (converter->mode() == Converter::Mode::Font) { + auto fontConverter = static_cast(converter); + auto newImage = fontConverter->generateImage(_currentFont, c, createParameterMap()); + _currentImage = newImage; + _bitmapPanel->setImage(newImage); + _bitmapPanel->setConverter(selectedConverter()); + _bitmapPanel->setParameter(createParameterMap()); + } +} + + +void MainWindow::onParameterChanged() +{ + _bitmapPanel->setParameter(createParameterMap()); + onSelectedCharacterChanged(_bitmapPanel->selectedCharacter()); +} + + void MainWindow::closeEvent(QCloseEvent *event) { saveSettings(); diff --git a/MainWindow.hpp b/MainWindow.hpp index 4107239..e9d7b84 100644 --- a/MainWindow.hpp +++ b/MainWindow.hpp @@ -17,6 +17,10 @@ #pragma once +#include "ParameterDefinition.hpp" +#include "ParameterFactory.hpp" + +#include #include @@ -25,6 +29,11 @@ class QPlainTextEdit; class QComboBox; class QLabel; class BitmapPanel; +class QFrame; +class QFontComboBox; +class QLineEdit; +class QSpinBox; +class QFormLayout; /// The main widnow @@ -64,26 +73,73 @@ private: /// Update the bitmap info. /// - void updateBitmapInfo(); + void updateInfoBox(); /// Update the code. /// void updateCode(); + /// Update the parameter frame for the current converter. + /// + void updateParameters(); + + /// Get the current parameters + /// + QVariantMap createParameterMap() const; + private Q_SLOTS: /// Load a bitmap. /// void onLoadBitmap(); + /// After a new converter is selected. + /// + void onFormatChanged(); + + /// After a new font is selected. + /// + void onFontChanged(); + + /// After the character list has changed + /// + void onCharactersChanged(); + + /// If the selected character changed + /// + void onSelectedCharacterChanged(QChar c); + + /// After a parameter has changed. + /// + void onParameterChanged(); + protected: // Implement QWidget void closeEvent(QCloseEvent *event) override; private: + ParameterFactory parameterFactory; ///< The parameter factory. + QComboBox *_formatSelector; ///< The format selector. QPlainTextEdit *_codePreview; ///< The generated code. BitmapPanel *_bitmapPanel; ///< The bitmap panel. - QImage _currentImage; ///< The current loaded image + + QFrame *_parameterFrame; ///< The frame with the parameters. + QFormLayout *_parameterLayout; ///< The parameter layout. + ParameterDefinitionPtr _displayedParameters; ///< The currently displayed parameters. + + QImage _currentImage; ///< The current loaded image for bitmap converters. + QFont _currentFont; ///< The currently selected font for font converters. + + QFrame *_bitmapConverterFrame; ///< The frame for bitmap converters. QLabel *_bitmapInfo; ///< The label with the image info. + + QFrame *_fontConverterFrame; ///< The frame for font converters. + QLabel *_fontInfo; ///< The label with the font info. + QFontComboBox *_fontSelector; ///< The combo box to select a new font. + QComboBox *_fontWeightSelector; ///< The selector for the font weight. + QSpinBox *_fontSizeSelector; ///< The selector for the used font size. + QComboBox *_fontHinting; ///< The hinting settings for the font. + QPlainTextEdit *_fontCharacters; ///< The characters of the font to be converted. + QList _converterList; ///< A list of available converters. }; diff --git a/MicropythonBitmapTool.pro b/MicropythonBitmapTool.pro index 29fb710..9d04c53 100644 --- a/MicropythonBitmapTool.pro +++ b/MicropythonBitmapTool.pro @@ -3,6 +3,7 @@ QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets CONFIG += c++17 +CONFIG += sdk_no_version_check TARGET = "MicroPythonBitmapTool" @@ -11,6 +12,8 @@ TARGET = "MicroPythonBitmapTool" #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 SOURCES += \ + ApplicationController.cpp \ + BitmapConverter.cpp \ BitmapPanel.cpp \ BitmapPreview.cpp \ Converter.cpp \ @@ -19,13 +22,22 @@ SOURCES += \ ConverterFramebufMonoHLSB.cpp \ ConverterFramebufMonoHMSB.cpp \ ConverterFramebufMonoVLSB.cpp \ + FontConverter.cpp \ + FontConverterFramebufMono.cpp \ LegendData.cpp \ LegendEntry.cpp \ + MonoTools.cpp \ OverlayPainter.cpp \ + ParameterDefinition.cpp \ + ParameterEntry.cpp \ + ParameterFactory.cpp \ + ParameterWidget.cpp \ main.cpp \ MainWindow.cpp HEADERS += \ + ApplicationController.hpp \ + BitmapConverter.hpp \ BitmapPanel.hpp \ BitmapPreview.hpp \ Converter.hpp \ @@ -34,11 +46,19 @@ HEADERS += \ ConverterFramebufMonoHLSB.hpp \ ConverterFramebufMonoHMSB.hpp \ ConverterFramebufMonoVLSB.hpp \ + FontConverter.hpp \ + FontConverterFramebufMono.hpp \ LegendData.hpp \ LegendEntry.hpp \ MainWindow.hpp \ + MonoTools.hpp \ OverlayMode.hpp \ - OverlayPainter.hpp + OverlayPainter.hpp \ + ParameterDefinition.hpp \ + ParameterEntry.hpp \ + ParameterFactory.hpp \ + ParameterType.hpp \ + ParameterWidget.hpp RESOURCES += \ data/data.qrc diff --git a/MonoTools.cpp b/MonoTools.cpp new file mode 100644 index 0000000..f48dbee --- /dev/null +++ b/MonoTools.cpp @@ -0,0 +1,134 @@ +// +// (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 "MonoTools.hpp" + + +MonoTools::MonoTools(MonoTools::UnitOrientation unitOrientation, MonoTools::BitDirection bitDirection, int unitSize) + : _unitOrientation(unitOrientation), _bitDirection(bitDirection), _unitSize(unitSize) +{ +} + + +void MonoTools::addMonoLegendData(LegendDataPtr data, OverlayMode mode) const +{ + if (mode == OverlayMode::BitAssigments) { + data->addEntry(colorBitAssignment1, colorBitAssignment2, QObject::tr("Bit Assignment")); + } else { + data->addEntry(colorPixelInterpretation, QObject::tr("Pixel Interpretation")); + } +} + + +QSize MonoTools::monoGeneratedSize(const QSize &imageSize, const QVariantMap&) 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; + } + } +} + + +void MonoTools::monoPaintOverlay(OverlayPainter &p, OverlayMode mode, const QImage &image, const QVariantMap&) const +{ + 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 MonoTools::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 MonoTools::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/MonoTools.hpp b/MonoTools.hpp new file mode 100644 index 0000000..f31b8cd --- /dev/null +++ b/MonoTools.hpp @@ -0,0 +1,83 @@ +// +// (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 "ParameterDefinition.hpp" + +#include +#include + + +class MonoTools +{ +public: + enum class UnitOrientation { + Horizontal, + Vertical + }; + + enum class BitDirection { + LSB, + MSB + }; + +public: + /// ctor + /// + MonoTools(UnitOrientation unitOrientation, + BitDirection bitDirection, + int unitSize); + + /// Legend data for mono conversion + /// + void addMonoLegendData(LegendDataPtr data, OverlayMode mode) const; + + /// The generated size for mono conversion. + /// + QSize monoGeneratedSize(const QSize &imageSize, const QVariantMap ¶meter) const; + + /// Paint overlay for mono data. + /// + void monoPaintOverlay(OverlayPainter &p, OverlayMode mode, const QImage &image, const QVariantMap ¶meter) const; + + /// 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); + +public: + /// 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/ParameterDefinition.cpp b/ParameterDefinition.cpp new file mode 100644 index 0000000..2d05662 --- /dev/null +++ b/ParameterDefinition.cpp @@ -0,0 +1,81 @@ +// +// (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 "ParameterDefinition.hpp" + + +ParameterDefinitionPtr ParameterDefinition::create() +{ + return ParameterDefinitionPtr(new ParameterDefinition()); +} + + +void ParameterDefinition::addCheckbox( + const QString &identifier, + const QString &label, + bool defaultValue) +{ + QVariantMap settings; + settings["default"] = defaultValue; + _parameterList.append(ParameterEntry::create( + ParameterType::Checkbox, identifier, label, settings)); +} + + +void ParameterDefinition::addInteger( + const QString &identifier, + const QString &label, + int defaultValue, + int minimum, + int maximum) +{ + QVariantMap settings; + settings["default"] = defaultValue; + settings["minimum"] = minimum; + settings["maximum"] = maximum; + _parameterList.append(ParameterEntry::create(ParameterType::IntegerValue, identifier, label, settings)); +} + + +void ParameterDefinition::addIntegerSize(const QString &identifier, const QString &label, QSize defaultValue, QSize minimum, QSize maximum) +{ + QVariantMap settings; + settings["default"] = defaultValue; + settings["minimum"] = minimum; + settings["maximum"] = maximum; + _parameterList.append(ParameterEntry::create(ParameterType::IntegerSize, identifier, label, settings)); +} + + +void ParameterDefinition::addIntegerPosition(const QString &identifier, const QString &label, QPoint defaultValue, QPoint minimum, QPoint maximum) +{ + QVariantMap settings; + settings["default"] = defaultValue; + settings["minimum"] = minimum; + settings["maximum"] = maximum; + _parameterList.append(ParameterEntry::create(ParameterType::IntegerPosition, identifier, label, settings)); +} + + +const ParameterDefinition::ParameterList &ParameterDefinition::parameterList() const +{ + return _parameterList; +} + + +ParameterDefinition::ParameterDefinition() +{ +} diff --git a/ParameterDefinition.hpp b/ParameterDefinition.hpp new file mode 100644 index 0000000..5e61b8a --- /dev/null +++ b/ParameterDefinition.hpp @@ -0,0 +1,73 @@ +// +// (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 "ParameterEntry.hpp" + +#include +#include +#include + + +class ParameterDefinition; +using ParameterDefinitionPtr = QSharedPointer; +using ParameterDefinitionPtrConst = QSharedPointer; + + +/// The definition of parameters, returned by a converter. +/// +class ParameterDefinition +{ +public: + /// A parameter list. + /// + using ParameterList = QList; + +public: + /// Create a new parameter definition. + /// + static ParameterDefinitionPtr create(); + +public: + /// Add a checkbox parameter. + /// + void addCheckbox(const QString &identifier, const QString &label, bool defaultValue); + + /// Add an integer parameter. + /// + void addInteger(const QString &identifier, const QString &label, int defaultValue, int minimum, int maximum); + + /// Add an integer size parameter. + /// + void addIntegerSize(const QString &identifier, const QString &label, QSize defaultValue, QSize minimum, QSize maximum); + + /// Add an integer position parameter. + /// + void addIntegerPosition(const QString &identifier, const QString &label, QPoint defaultValue, QPoint minimum, QPoint maximum); + + /// Get the parameter list. + /// + const ParameterList& parameterList() const; + +private: + ParameterDefinition(); + +private: + ParameterList _parameterList; ///< The list with all parameters. +}; + diff --git a/ParameterEntry.cpp b/ParameterEntry.cpp new file mode 100644 index 0000000..12c776b --- /dev/null +++ b/ParameterEntry.cpp @@ -0,0 +1,65 @@ +// +// (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 "ParameterEntry.hpp" + + +ParameterEntryPtr ParameterEntry::create( + ParameterType type, + const QString &identifier, + const QString &label, + const QVariantMap &settings) +{ + return ParameterEntryPtr(new ParameterEntry(type, identifier, label, settings)); +} + + +ParameterType ParameterEntry::type() const +{ + return _type; +} + + +QString ParameterEntry::identifier() const +{ + return _identifier; +} + + +QString ParameterEntry::label() const +{ + return _label; +} + + +QVariantMap ParameterEntry::settings() const +{ + return _settings; +} + + +ParameterEntry::ParameterEntry( + ParameterType type, + const QString &identifier, + const QString &label, + const QVariantMap &settings) +: + _type(type), + _identifier(identifier), + _label(label), + _settings(settings) +{ +} diff --git a/ParameterEntry.hpp b/ParameterEntry.hpp new file mode 100644 index 0000000..c68bc7c --- /dev/null +++ b/ParameterEntry.hpp @@ -0,0 +1,74 @@ +// +// (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 "ParameterType.hpp" + +#include +#include +#include + + +class ParameterEntry; +using ParameterEntryPtr = QSharedPointer; + + +/// One entry in the parameter definition. +/// +class ParameterEntry +{ +public: + static ParameterEntryPtr create( + ParameterType type, + const QString &identifier, + const QString &label, + const QVariantMap &settings); + +public: + /// The parameter type. + /// + ParameterType type() const; + + /// The identifier of the parameter. + /// + QString identifier() const; + + /// The label for the parameter. + /// + QString label() const; + + /// Settings for the parameter. + /// + QVariantMap settings() const; + +private: + /// Create a new raw paremeter entry. + /// + ParameterEntry( + ParameterType type, + const QString &identifier, + const QString &label, + const QVariantMap &settings); + +private: + ParameterType _type; + QString _identifier; + QString _label; + QVariantMap _settings; +}; + diff --git a/ParameterFactory.cpp b/ParameterFactory.cpp new file mode 100644 index 0000000..3c18a51 --- /dev/null +++ b/ParameterFactory.cpp @@ -0,0 +1,40 @@ +// +// (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 "ParameterFactory.hpp" + + +ParameterFactory::ParameterFactory() +{ +} + + +ParameterWidget *ParameterFactory::createWidget(const ParameterEntryPtr ¶meter) const +{ + switch (parameter->type()) { + case ParameterType::Checkbox: + return new CheckBoxParameter(parameter); + case ParameterType::IntegerValue: + return new IntegerParameter(parameter); + case ParameterType::IntegerSize: + return new IntegerSizeParameter(parameter); + case ParameterType::IntegerPosition: + return new IntegerPositionParameter(parameter); + default: + break; + } + return nullptr; +} diff --git a/ParameterFactory.hpp b/ParameterFactory.hpp new file mode 100644 index 0000000..ef767ea --- /dev/null +++ b/ParameterFactory.hpp @@ -0,0 +1,37 @@ +// +// (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 "ParameterWidget.hpp" + +#include + + +/// A factory to handle the generic parameters. +/// +class ParameterFactory +{ +public: + ParameterFactory(); + +public: + /// Create the widget for to edit a parameter. + /// + ParameterWidget *createWidget(const ParameterEntryPtr ¶meter) const; +}; + diff --git a/ParameterType.hpp b/ParameterType.hpp new file mode 100644 index 0000000..8ee36ff --- /dev/null +++ b/ParameterType.hpp @@ -0,0 +1,28 @@ +// +// (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 + + +/// The parameter type. +/// +enum class ParameterType { + Checkbox, ///< A boolean parameter + IntegerValue, ///< An integer value. + IntegerSize, ///< A size value + IntegerPosition, ///< A position value. +}; + diff --git a/ParameterWidget.cpp b/ParameterWidget.cpp new file mode 100644 index 0000000..c42536b --- /dev/null +++ b/ParameterWidget.cpp @@ -0,0 +1,173 @@ +// +// (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 "ParameterWidget.hpp" + + +#include +#include +#include +#include + + +ParameterWidget::ParameterWidget(const ParameterEntryPtr ¶meter, QWidget *parent) + : QWidget(parent), _currentValue(parameter->settings()["default"]) +{ +} + + +CheckBoxParameter::CheckBoxParameter(const ParameterEntryPtr ¶meter, QWidget *parent) + : ParameterWidget(parameter, parent) +{ + auto mainLayout = new QHBoxLayout(this); + mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->setSpacing(2); + _value = new QCheckBox(); + mainLayout->addWidget(_value); + _value->setChecked(_currentValue.toBool()); + connect(_value, &QCheckBox::toggled, this, &CheckBoxParameter::valueChanged); +} + + +QVariant CheckBoxParameter::value() const +{ + return static_cast(_value->isChecked()); +} + + +void CheckBoxParameter::setValue(const QVariant &value) +{ + if (value != _currentValue) { + _currentValue = value; + _value->setChecked(value.toBool()); + } +} + + +IntegerParameter::IntegerParameter(const ParameterEntryPtr ¶meter, QWidget *parent) + : ParameterWidget(parameter, parent) +{ + auto mainLayout = new QHBoxLayout(this); + mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->setSpacing(2); + _value = new QSpinBox(); + _value->setRange( + parameter->settings()["minimum"].toInt(), + parameter->settings()["maximum"].toInt() + ); + mainLayout->addWidget(_value); + _value->setValue(_currentValue.toInt()); + connect(_value, &QSpinBox::valueChanged, this, &CheckBoxParameter::valueChanged); +} + + +QVariant IntegerParameter::value() const +{ + return _value->value(); +} + + +void IntegerParameter::setValue(const QVariant &value) +{ + if (value != _currentValue) { + _value->setValue(value.toInt()); + } +} + + +IntegerSizeParameter::IntegerSizeParameter(const ParameterEntryPtr ¶meter, QWidget *parent) + : ParameterWidget(parameter, parent) +{ + auto mainLayout = new QHBoxLayout(this); + mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->setSpacing(2); + mainLayout->addWidget(new QLabel(tr("W:"))); + _a = new QSpinBox(); + _a->setRange( + parameter->settings()["minimum"].toSize().width(), + parameter->settings()["maximum"].toSize().width() + ); + mainLayout->addWidget(_a); + _a->setValue(_currentValue.toSize().width()); + connect(_a, &QSpinBox::valueChanged, this, &CheckBoxParameter::valueChanged); + mainLayout->addWidget(new QLabel(tr("H:"))); + _b = new QSpinBox(); + _b->setRange( + parameter->settings()["minimum"].toSize().height(), + parameter->settings()["maximum"].toSize().height() + ); + mainLayout->addWidget(_b); + _b->setValue(_currentValue.toSize().height()); + connect(_b, &QSpinBox::valueChanged, this, &CheckBoxParameter::valueChanged); +} + + +QVariant IntegerSizeParameter::value() const +{ + return QSize(_a->value(), _b->value()); +} + + +void IntegerSizeParameter::setValue(const QVariant &value) +{ + if (_currentValue != value) { + _a->setValue(value.toSize().width()); + _b->setValue(value.toSize().height()); + } +} + + +IntegerPositionParameter::IntegerPositionParameter(const ParameterEntryPtr ¶meter, QWidget *parent) + : ParameterWidget(parameter, parent) +{ + auto mainLayout = new QHBoxLayout(this); + mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->setSpacing(2); + mainLayout->addWidget(new QLabel(tr("X:"))); + _a = new QSpinBox(); + _a->setRange( + parameter->settings()["minimum"].toPoint().x(), + parameter->settings()["maximum"].toPoint().x() + ); + mainLayout->addWidget(_a); + _a->setValue(_currentValue.toPoint().x()); + connect(_a, &QSpinBox::valueChanged, this, &CheckBoxParameter::valueChanged); + mainLayout->addWidget(new QLabel(tr("Y:"))); + _b = new QSpinBox(); + _b->setRange( + parameter->settings()["minimum"].toPoint().y(), + parameter->settings()["maximum"].toPoint().y() + ); + mainLayout->addWidget(_b); + _b->setValue(_currentValue.toPoint().y()); + connect(_b, &QSpinBox::valueChanged, this, &CheckBoxParameter::valueChanged); +} + + +QVariant IntegerPositionParameter::value() const +{ + return QPoint(_a->value(), _b->value()); +} + + +void IntegerPositionParameter::setValue(const QVariant &value) +{ + if (_currentValue != value) { + _a->setValue(value.toPoint().x()); + _b->setValue(value.toPoint().y()); + } +} + diff --git a/ParameterWidget.hpp b/ParameterWidget.hpp new file mode 100644 index 0000000..99d72f2 --- /dev/null +++ b/ParameterWidget.hpp @@ -0,0 +1,114 @@ +// +// (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 "ParameterEntry.hpp" + +#include + + +class QCheckBox; +class QSpinBox; + + +/// The base class of all parameter widgets. +/// +class ParameterWidget : public QWidget +{ + Q_OBJECT + +public: + /// Create the widget. + /// + ParameterWidget(const ParameterEntryPtr ¶meter, QWidget *parent = nullptr); + + /// Get the current value for the widget. + /// + virtual QVariant value() const = 0; + + /// Set the value for this widget. + /// + virtual void setValue(const QVariant &value) = 0; + +signals: + /// Emitted if the value for this parameter changes. + /// + void valueChanged(); + +protected: + QVariant _currentValue; ///< Buffer to limit signals. +}; + + +class CheckBoxParameter : public ParameterWidget +{ + Q_OBJECT + +public: + CheckBoxParameter(const ParameterEntryPtr ¶meter, QWidget *parent = nullptr); + QVariant value() const override; + void setValue(const QVariant &value) override; + +private: + QCheckBox *_value; +}; + + +class IntegerParameter : public ParameterWidget +{ + Q_OBJECT + +public: + IntegerParameter(const ParameterEntryPtr ¶meter, QWidget *parent = nullptr); + QVariant value() const override; + void setValue(const QVariant &value) override; + +private: + QSpinBox *_value; +}; + + +class IntegerSizeParameter : public ParameterWidget +{ + Q_OBJECT + +public: + IntegerSizeParameter(const ParameterEntryPtr ¶meter, QWidget *parent = nullptr); + QVariant value() const override; + void setValue(const QVariant &value) override; + +private: + QSpinBox *_a; + QSpinBox *_b; +}; + + +class IntegerPositionParameter : public ParameterWidget +{ + Q_OBJECT + +public: + IntegerPositionParameter(const ParameterEntryPtr ¶meter, QWidget *parent = nullptr); + QVariant value() const override; + void setValue(const QVariant &value) override; + +private: + QSpinBox *_a; + QSpinBox *_b; +}; + diff --git a/data/AppLogo.png b/data/AppLogo.png index 159a917..c099c94 100644 Binary files a/data/AppLogo.png and b/data/AppLogo.png differ diff --git a/data/AppLogo@2x.png b/data/AppLogo@2x.png index a26784f..0cf0af6 100644 Binary files a/data/AppLogo@2x.png and b/data/AppLogo@2x.png differ diff --git a/data/AppLogo@3x.png b/data/AppLogo@3x.png index 3c1a363..bf245a2 100644 Binary files a/data/AppLogo@3x.png and b/data/AppLogo@3x.png differ diff --git a/data/application.css b/data/application.css index 53ee096..6ff17f5 100644 --- a/data/application.css +++ b/data/application.css @@ -14,8 +14,16 @@ color: #f8f8f8; } -#BitmapInfo { +#BitmapInfo, #FontInfo { border: 1px solid rgba(0,0,0,0.25); padding: 8px; margin: 0px 0px 16px 0px; } + +#ParameterScrollArea { + border: none; +} + +#ParameterFrame { + background-color: #616875; +} diff --git a/main.cpp b/main.cpp index 3cc1424..7b68f09 100644 --- a/main.cpp +++ b/main.cpp @@ -14,16 +14,19 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . // -#include "MainWindow.hpp" + + +#include "ApplicationController.hpp" #include #include + int main(int argc, char *argv[]) { QApplication a(argc, argv); a.setApplicationName("MicroPython Bitmap Tool"); - a.setApplicationVersion("1.0.1"); + a.setApplicationVersion("1.2.0"); a.setApplicationDisplayName("MicroPython Bitmap Tool"); a.setOrganizationDomain("luckyresistor.me"); a.setOrganizationName("Lucky Resistoor"); @@ -32,7 +35,6 @@ int main(int argc, char *argv[]) a.setStyleSheet(QString::fromUtf8(styleSheet.readAll())); styleSheet.close(); - MainWindow w; - w.show(); + ApplicationController app; return a.exec(); }