From 0018e4e9dbacdced0e54e24d270bdbe5f791cf47 Mon Sep 17 00:00:00 2001
From: Lucky Resistor
Date: Sun, 6 Jun 2021 13:29:22 +0200
Subject: [PATCH] Adding a framework to generate code for fonts.
- Work in progress to add a framework generating bitmap fonts.
---
ApplicationController.cpp | 67 +++++++
ApplicationController.hpp | 57 ++++++
BitmapConverter.cpp | 29 +++
BitmapConverter.hpp | 45 +++++
BitmapPanel.cpp | 77 +++++++-
BitmapPanel.hpp | 32 +++-
BitmapPreview.cpp | 31 +++-
BitmapPreview.hpp | 10 +
Converter.cpp | 10 +-
Converter.hpp | 25 ++-
ConverterFramebuf.cpp | 2 +-
ConverterFramebuf.hpp | 4 +-
ConverterFramebufMono.cpp | 109 +----------
ConverterFramebufMono.hpp | 40 +---
ConverterFramebufMonoHLSB.cpp | 4 +-
ConverterFramebufMonoHMSB.cpp | 4 +-
ConverterFramebufMonoVLSB.cpp | 4 +-
FontConverter.cpp | 149 +++++++++++++++
FontConverter.hpp | 58 ++++++
FontConverterFramebufMono.cpp | 52 ++++++
FontConverterFramebufMono.hpp | 39 ++++
MainWindow.cpp | 334 +++++++++++++++++++++++++++++-----
MainWindow.hpp | 60 +++++-
MicropythonBitmapTool.pro | 22 ++-
MonoTools.cpp | 134 ++++++++++++++
MonoTools.hpp | 83 +++++++++
ParameterDefinition.cpp | 81 +++++++++
ParameterDefinition.hpp | 73 ++++++++
ParameterEntry.cpp | 65 +++++++
ParameterEntry.hpp | 74 ++++++++
ParameterFactory.cpp | 40 ++++
ParameterFactory.hpp | 37 ++++
ParameterType.hpp | 28 +++
ParameterWidget.cpp | 173 ++++++++++++++++++
ParameterWidget.hpp | 114 ++++++++++++
data/AppLogo.png | Bin 8789 -> 4928 bytes
data/AppLogo@2x.png | Bin 19068 -> 9714 bytes
data/AppLogo@3x.png | Bin 30997 -> 14774 bytes
data/application.css | 10 +-
main.cpp | 10 +-
40 files changed, 1962 insertions(+), 224 deletions(-)
create mode 100644 ApplicationController.cpp
create mode 100644 ApplicationController.hpp
create mode 100644 BitmapConverter.cpp
create mode 100644 BitmapConverter.hpp
create mode 100644 FontConverter.cpp
create mode 100644 FontConverter.hpp
create mode 100644 FontConverterFramebufMono.cpp
create mode 100644 FontConverterFramebufMono.hpp
create mode 100644 MonoTools.cpp
create mode 100644 MonoTools.hpp
create mode 100644 ParameterDefinition.cpp
create mode 100644 ParameterDefinition.hpp
create mode 100644 ParameterEntry.cpp
create mode 100644 ParameterEntry.hpp
create mode 100644 ParameterFactory.cpp
create mode 100644 ParameterFactory.hpp
create mode 100644 ParameterType.hpp
create mode 100644 ParameterWidget.cpp
create mode 100644 ParameterWidget.hpp
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 159a917292d48ffe5a71639809269c09686390f4..c099c94f7951fba197375379de095a4b5e1fbc88 100644
GIT binary patch
literal 4928
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
HWF7u5)4_%B
literal 8789
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