Implemented coarse color picker

This commit is contained in:
2025-11-23 21:39:29 +01:00
parent b6a6084ba8
commit 42108b2c65
16 changed files with 291 additions and 97 deletions
+4
View File
@@ -38,8 +38,10 @@ HEADERS += \
src/editor/editorguiutils.h \
src/editor/roomscene.h \
src/editor/widgets/actiondragwidget.h \
src/editor/widgets/coarsecolorwidget.h \
src/editor/widgets/colorwidget.h \
src/editor/widgets/draggabletreeview.h \
src/editor/widgets/finecolorwidget.h \
src/editor/widgets/objectparentselectorwidget.h \
src/editor/widgets/objectselectorwidget.h \
src/editor/widgets/qlineeditwithmenu.h \
@@ -109,8 +111,10 @@ SOURCES += \
src/editor/editorguiutils.cpp \
src/editor/roomscene.cpp \
src/editor/widgets/actiondragwidget.cpp \
src/editor/widgets/coarsecolorwidget.cpp \
src/editor/widgets/colorwidget.cpp \
src/editor/widgets/draggabletreeview.cpp \
src/editor/widgets/finecolorwidget.cpp \
src/editor/widgets/objectparentselectorwidget.cpp \
src/editor/widgets/objectselectorwidget.cpp \
src/editor/widgets/qlineeditwithmenu.cpp \
+3 -5
View File
@@ -94,7 +94,7 @@ void EditSpriteDialog::newSprite()
m_model->beginResetModel();
m_pixmaps = std::vector<QPixmap> { std::move(pixmap) };
m_model->endResetModel();
qDebug() << m_model->rowCount({});
changed();
}
}
@@ -110,11 +110,9 @@ void EditSpriteDialog::doubleClicked(const QModelIndex &index)
ImageEditorDialog dialog{m_model->pixmap(index), tr("Image Editor: %0").arg(m_spriteName), this};
if (dialog.exec() == QDialog::Accepted)
{
qDebug() << "apply pixmap?!";
emit m_model->beginResetModel();
m_pixmaps[index.row()] = dialog.pixmap();
emit m_model->endResetModel();
//emit m_model->dataChanged(index, index, {Qt::DecorationRole});
emit m_model->dataChanged(index, index, {Qt::DecorationRole});
changed();
}
}
+10
View File
@@ -34,6 +34,16 @@ ImageEditorDialog::ImageEditorDialog(const QPixmap &pixmap, const QString &title
connect(m_ui->pushButtonRightButtonColor, &QAbstractButton::pressed,
this, &ImageEditorDialog::selectRightButtonColor);
connect(m_ui->frameColorCoarse, &ColorWidget::colorLeftClicked,
m_ui->canvas, &DrawingCanvasWidget::setLeftButtonColor);
connect(m_ui->frameColorCoarse, &ColorWidget::colorRightClicked,
m_ui->canvas, &DrawingCanvasWidget::setRightButtonColor);
connect(m_ui->frameColorFine, &ColorWidget::colorLeftClicked,
m_ui->canvas, &DrawingCanvasWidget::setLeftButtonColor);
connect(m_ui->frameColorFine, &ColorWidget::colorRightClicked,
m_ui->canvas, &DrawingCanvasWidget::setRightButtonColor);
connect(m_ui->canvas, &DrawingCanvasWidget::changed,
this, &ImageEditorDialog::changed);
connect(m_ui->canvas, &DrawingCanvasWidget::leftButtonColorChanged,
+31 -12
View File
@@ -725,12 +725,20 @@
<string>Colors</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
<number>10</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="0,0">
<item>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="labelLeft">
<property name="font">
<font>
<pointsize>8</pointsize>
</font>
</property>
<property name="text">
<string>Left</string>
</property>
@@ -752,6 +760,11 @@
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QLabel" name="labelRight">
<property name="font">
<font>
<pointsize>8</pointsize>
</font>
</property>
<property name="text">
<string>Right</string>
</property>
@@ -772,34 +785,34 @@
</layout>
</item>
<item>
<widget class="ColorWidget" name="frameColorCoarse">
<widget class="CoarseColorWidget" name="frameColorCoarse">
<property name="minimumSize">
<size>
<width>75</width>
<height>100</height>
<width>0</width>
<height>125</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::Shape::StyledPanel</enum>
<enum>QFrame::Shape::Panel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum>
<enum>QFrame::Shadow::Sunken</enum>
</property>
</widget>
</item>
<item>
<widget class="ColorWidget" name="frameColorFine">
<widget class="FineColorWidget" name="frameColorFine">
<property name="minimumSize">
<size>
<width>75</width>
<height>100</height>
<width>0</width>
<height>125</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::Shape::StyledPanel</enum>
<enum>QFrame::Shape::Panel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum>
<enum>QFrame::Shadow::Sunken</enum>
</property>
</widget>
</item>
@@ -1112,9 +1125,15 @@
<header>widgets/drawingcanvaswidget.h</header>
</customwidget>
<customwidget>
<class>ColorWidget</class>
<class>CoarseColorWidget</class>
<extends>QFrame</extends>
<header>widgets/colorwidget.h</header>
<header>widgets/coarsecolorwidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>FineColorWidget</class>
<extends>QFrame</extends>
<header>widgets/finecolorwidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
-5
View File
@@ -8,11 +8,6 @@
#include "projectserialization.h"
#include "stdserialization.h"
ActionDragWidget::ActionDragWidget(QWidget *parent) :
QToolButton{parent}
{
}
void ActionDragWidget::mousePressEvent(QMouseEvent *event)
{
QToolButton::mousePressEvent(event);
+1 -1
View File
@@ -10,7 +10,7 @@ class ActionDragWidget : public QToolButton
Q_OBJECT
public:
explicit ActionDragWidget(QWidget *parent = nullptr);
using QToolButton::QToolButton;
const Action &action() const { return m_action; }
void setAction(Action &&action) { m_action = std::move(action); }
+102
View File
@@ -0,0 +1,102 @@
#include "coarsecolorwidget.h"
#include <QPainter>
void CoarseColorWidget::resizeEvent(QResizeEvent *event)
{
ColorWidget::resizeEvent(event);
int w = width() - frameWidth() * 2;
int h = height() - frameWidth() * 2;
// dynamic columns
constexpr int MIN_COL_WIDTH = 16;
constexpr int MAX_COL_WIDTH = 32;
int minCols = std::max(1, w / MAX_COL_WIDTH);
int maxCols = std::max(1, w / MIN_COL_WIDTH);
int columns = std::clamp(maxCols, minCols, maxCols);
// dynamic rows
constexpr int MIN_ROW_HEIGHT = 16;
constexpr int MAX_ROW_HEIGHT = 32;
int minRows = std::max(1, h / MAX_ROW_HEIGHT);
int maxRows = std::max(1, h / MIN_ROW_HEIGHT);
int rows = std::clamp(maxRows, minRows, maxRows);
m_image = QImage(w, h, QImage::Format_RGB32);
{
QPainter p(&m_image);
p.setPen(Qt::NoPen);
for (int r = 0; r < rows; ++r)
for (int c = 0; c < columns; ++c)
{
// fractional geometry (smooth scaling)
int x0 = (c * w) / columns;
int x1 = ((c+1) * w) / columns;
int y0 = (r * h) / rows;
int y1 = ((r+1) * h) / rows;
int cw = x1 - x0;
int rh = y1 - y0;
// color logic
QColor color;
float hue = float(c) * 360.f / float(columns);
// bottom row = grayscale, but now rows are dynamic
if (r == rows - 1)
{
int v = int(c * 255.f / std::max(1, columns - 1));
color = QColor(v, v, v);
}
else
{
// spread the "classic5 rows" over whatever number of rows we have
// We interpolate S and V vertically.
float t = float(r) / float(rows - 1); // 0..1, except grayscale row
// Windows-like palette interpolation:
// r=0 (top): S=255 V=140
// r=1: S=255 V=200
// r=2: S=255 V=255
// r=3: S=100 V=255
// r=4: S= 40 V=255
// r=5 bottom: grayscale row (handled above)
// Predefined control points
constexpr struct { int s, v; } ctrl[] = {
{255, 140},
{255, 200},
{255, 255},
{100, 255},
{ 40, 255}
};
int ctrlRows = 5;
float pos = t * (ctrlRows - 1);
int i = int(pos);
float f = pos - i;
if (i >= ctrlRows - 1) {
i = ctrlRows - 2;
f = 1.f;
}
int S = int(ctrl[i].s * (1.f - f) + ctrl[i + 1].s * f);
int V = int(ctrl[i].v * (1.f - f) + ctrl[i + 1].v * f);
color.setHsv(int(hue), S, V);
}
p.setBrush(color);
p.drawRect(x0, y0, cw, rh);
}
}
m_pixmap = QPixmap::fromImage(m_image);
}
+14
View File
@@ -0,0 +1,14 @@
#pragma once
#include "colorwidget.h"
class CoarseColorWidget : public ColorWidget
{
Q_OBJECT
public:
using ColorWidget::ColorWidget;
protected:
void resizeEvent(QResizeEvent *event) override;
};
+46 -51
View File
@@ -2,33 +2,17 @@
#include <QPaintEvent>
#include <QResizeEvent>
#include <QMouseEvent>
#include <QPainter>
ColorWidget::ColorWidget(QWidget *parent)
: QFrame{parent}
ColorWidget::ColorWidget(QWidget *parent) :
QFrame{parent}
{
setCol(150, 255);
setAttribute(Qt::WA_NoSystemBackground);
setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed) );
}
ColorWidget::~ColorWidget() = default;
void ColorWidget::setCol(int h, int s)
{
int nhue = qMin(qMax(0,h), 359);
int nsat = qMin(qMax(0,s), 255);
if (nhue == m_hue && nsat == m_sat)
return;
QRect r(colPt(), QSize(20,20));
m_hue = nhue;
m_sat = nsat;
r = r.united(QRect(colPt(), QSize(20,20)));
r.translate(contentsRect().x()-9, contentsRect().y()-9);
// update(r);
repaint(r);
}
void ColorWidget::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event)
@@ -39,50 +23,61 @@ void ColorWidget::paintEvent(QPaintEvent *event)
p.drawPixmap(r.topLeft(), m_pixmap);
}
void ColorWidget::resizeEvent(QResizeEvent *event)
void ColorWidget::mousePressEvent(QMouseEvent *event)
{
Q_UNUSED(event)
QFrame::mousePressEvent(event);
int w = width() - frameWidth() * 2;
int h = height() - frameWidth() * 2;
QImage img(w, h, QImage::Format_RGB32);
int x, y;
uint *pixel = (uint *) img.scanLine(0);
for (y = 0; y < h; y++)
switch (event->button())
{
const uint *end = pixel + w;
x = 0;
while (pixel < end) {
QPoint p(x, y);
QColor c;
c.setHsv(huePt(p), satPt(p), 200);
*pixel = c.rgb();
++pixel;
++x;
}
case Qt::LeftButton:
case Qt::RightButton:
{
m_currentlyPicking = event->button() == Qt::LeftButton;
setMouseTracking(true);
pickPixel(event->pos(), event->button() == Qt::LeftButton);
break;
}
default:;
}
m_pixmap = QPixmap::fromImage(img);
}
QPoint ColorWidget::colPt()
void ColorWidget::mouseReleaseEvent(QMouseEvent *event)
{
QRect r = contentsRect();
return QPoint((360 - m_hue) * (r.width() - 1) / 360, (255 - m_sat) * (r.height() - 1) / 255);
QFrame::mouseReleaseEvent(event);
switch (event->button())
{
case Qt::LeftButton:
case Qt::RightButton:
{
m_currentlyPicking = std::nullopt;
setMouseTracking(false);
break;
}
default:;
}
}
int ColorWidget::huePt(const QPoint &pt)
void ColorWidget::mouseMoveEvent(QMouseEvent *event)
{
QRect r = contentsRect();
return 360 - pt.x() * 360 / (r.width() - 1);
QFrame::mouseMoveEvent(event);
if (m_currentlyPicking)
pickPixel(event->pos(), *m_currentlyPicking);
}
int ColorWidget::satPt(const QPoint &pt)
void ColorWidget::pickPixel(const QPoint &pos, bool left)
{
QRect r = contentsRect();
return 255 - pt.y() * 255 / (r.height() - 1);
}
if (!m_image.rect().contains(pos))
return;
void ColorWidget::setCol(const QPoint &pt)
{
setCol(huePt(pt), satPt(pt));
if (left)
emit colorLeftClicked(m_image.pixelColor(pos));
else
emit colorRightClicked(m_image.pixelColor(pos));
}
+15 -11
View File
@@ -1,8 +1,11 @@
#pragma once
#include <QFrame>
#include <QImage>
#include <QPixmap>
#include <optional>
class ColorWidget : public QFrame
{
Q_OBJECT
@@ -11,21 +14,22 @@ public:
explicit ColorWidget(QWidget *parent = nullptr);
~ColorWidget() override;
public slots:
void setCol(int h, int s);
signals:
void colorLeftClicked(const QColor &color);
void colorRightClicked(const QColor &color);
protected:
void paintEvent(QPaintEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
QImage m_image;
QPixmap m_pixmap;
private:
QPoint colPt();
int huePt(const QPoint &pt);
int satPt(const QPoint &pt);
void setCol(const QPoint &pt);
void pickPixel(const QPoint &pos, bool left);
int m_hue{};
int m_sat{};
QPixmap m_pixmap;
std::optional<bool> m_currentlyPicking;
};
-5
View File
@@ -1,10 +1,5 @@
#include "draggabletreeview.h"
DraggableTreeView::DraggableTreeView(QWidget *parent) :
QTreeView{parent}
{
}
void DraggableTreeView::dragMoveEvent(QDragMoveEvent *event)
{
setDropIndicatorShown(true);
+1 -1
View File
@@ -7,7 +7,7 @@ class DraggableTreeView : public QTreeView
Q_OBJECT
public:
explicit DraggableTreeView(QWidget *parent = nullptr);
using QTreeView::QTreeView;
void dragMoveEvent(QDragMoveEvent *event) override;
void dropEvent(QDropEvent *event) override;
@@ -4,11 +4,6 @@
#include <QPainter>
#include <QMouseEvent>
DrawingCanvasWidget::DrawingCanvasWidget(QWidget *parent) :
QWidget{parent}
{
}
void DrawingCanvasWidget::setPixmap(QPixmap &pixmap)
{
m_pixmap = &pixmap;
+1 -1
View File
@@ -14,7 +14,7 @@ class DrawingCanvasWidget : public QWidget
Q_PROPERTY(QColor rightButtonColor READ rightButtonColor WRITE setRightButtonColor NOTIFY rightButtonColorChanged FINAL)
public:
explicit DrawingCanvasWidget(QWidget *parent = nullptr);
using QWidget::QWidget;
QPixmap *pixmap() { return m_pixmap; }
const QPixmap *pixmap() const { return m_pixmap; }
+49
View File
@@ -0,0 +1,49 @@
#include "finecolorwidget.h"
void FineColorWidget::resizeEvent(QResizeEvent *event)
{
ColorWidget::resizeEvent(event);
int w = width() - frameWidth() * 2;
int h = height() - frameWidth() * 2;
m_image = QImage{w, h, QImage::Format_RGB32};
for (int y = 0; y < h; y++)
{
uint *pixel = (uint *)m_image.scanLine(y);
float hue = float(360 - y * 360 / (h - 1));
QColor pure;
pure.setHsv(hue, 255, 255);
for (int x = 0; x < w; x++, pixel++)
{
float t = float(x) / float(w - 1); // 0..1 across width
QColor c;
if (t < 0.5f)
{
float k = t / 0.5f; // 0..1 across halve
c.setRgb(
pure.red() * k,
pure.green() * k,
pure.blue() * k
);
}
else
{
float k = (t - 0.5f) / 0.5f; // 0..1 across halve
c.setRgb(
pure.red() + (255 - pure.red()) * k,
pure.green() + (255 - pure.green()) * k,
pure.blue() + (255 - pure.blue()) * k
);
}
*pixel = c.rgb();
}
}
m_pixmap = QPixmap::fromImage(m_image);
}
+14
View File
@@ -0,0 +1,14 @@
#pragma once
#include "colorwidget.h"
class FineColorWidget : public ColorWidget
{
Q_OBJECT
public:
using ColorWidget::ColorWidget;
protected:
void resizeEvent(QResizeEvent *event) override;
};