KSeExpr 6.0.0.0
ExprColorCurve.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2011-2019 Disney Enterprises, Inc.
2// SPDX-License-Identifier: LicenseRef-Apache-2.0
3// SPDX-FileCopyrightText: 2020 L. E. Segovia <amy@amyspark.me>
4// SPDX-License-Identifier: GPL-3.0-or-later
5/*
6 * @file ExprColorCurveUI.cpp
7 * @brief Contains PyQt4 Ramp Widget to emulate Maya's ramp widget
8 * @author Arthur Shek
9 * @version ashek 05/04/09 Initial Version
10 */
11#include <algorithm>
12#include <iostream>
13
14
15#include <QColorDialog>
16#include <QDialogButtonBox>
17#include <QDoubleValidator>
18#include <QFormLayout>
19#include <QGraphicsSceneMouseEvent>
20#include <QHBoxLayout>
21#include <QLabel>
22#include <QMenu>
23#include <QPushButton>
24#include <QResizeEvent>
25#include <QToolButton>
26#include <QVBoxLayout>
27
28
30
31#include "ExprColorCurve.h"
32
34 : _curve(new T_CURVE)
35 , _width(320)
36 , _height(50)
37 , _color(KSeExpr::Vec3d(.5))
38 , _interp(T_CURVE::kMonotoneSpline)
39 , _selectedItem(-1)
40 , _pixmapDirty(true)
41 , _baseRectW(nullptr)
42 , _baseRect(nullptr)
43 , _lmb(false)
44{
47}
48
50{
51 delete _curve;
52}
53
54void CCurveScene::resize(const int width, const int height)
55{
56 // width and height already have the 8 px padding factored in
57 // MAKE SURE THIS NEVER UNDERFLOWS THE PIXMAPSIZE -- amyspark
58 _width = std::max(width - 16, 1);
59 _height = std::max(height - 16, 1);
60 setSceneRect(-9, -2, width, height);
61 drawRect();
62 drawPoints();
63 _pixmap = QPixmap(_width, _height);
64 _pixmapDirty = true;
65}
66
68{
69 delete _curve;
70 _curve = new T_CURVE;
71 for (const auto & _cv : _cvs)
72 _curve->addPoint(_cv._pos, _cv._val, _cv._interp);
73 _curve->preparePoints();
74}
75
76void CCurveScene::addPoint(double x, const KSeExpr::Vec3d y, const T_INTERP interp, const bool select)
77{
78 x = KSeExpr::clamp(x, 0, 1);
79
80 _cvs.emplace_back(x, y, T_INTERP(interp));
81 auto newIndex = _cvs.size() - 1;
82
84
85 if (select) {
86 _selectedItem = newIndex;
87 emit cvSelected(x, y, interp);
88 }
89 _pixmapDirty = true;
90 _baseRectW->update();
91 drawPoints();
92}
93
94void CCurveScene::removePoint(const int index)
95{
96 _cvs.erase(_cvs.begin() + index);
97 _selectedItem = -1;
99
100 _pixmapDirty = true;
101 _baseRectW->update();
102 drawPoints();
104}
105
107{
108 _cvs.clear();
109}
110
111void CCurveScene::keyPressEvent(QKeyEvent *event)
112{
113 if (((event->key() == Qt::Key_Backspace) || (event->key() == Qt::Key_Delete)) && (_selectedItem >= 0)) {
114 // user hit delete with cv selected
116 }
117}
118
119void CCurveScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent)
120{
121 _lmb = true;
122 QPointF pos = mouseEvent->scenePos();
123 // get items under mouse click
124 QList<QGraphicsItem *> itemList = items(pos);
125 if (itemList.empty()) {
126 _selectedItem = -1;
127 emit cvSelected(-1, KSeExpr::Vec3d(0.0), _interp);
128 drawPoints();
129 } else if (itemList[0]->zValue() == 2) {
130 // getting here means we've selected a current point
131 const int numCircle = _circleObjects.size();
132 for (int i = 0; i < numCircle; i++) {
133 QGraphicsItem *obj = _circleObjects[i];
134 if (obj == itemList[0]) {
135 _selectedItem = i;
136 _color = _cvs[i]._val;
137 _interp = _cvs[i]._interp;
138 emit cvSelected(_cvs[i]._pos, _cvs[i]._val, _cvs[i]._interp);
139 }
140 }
141 drawPoints();
142 } else {
143 if (mouseEvent->buttons() == Qt::LeftButton) {
144 // getting here means we want to create a new point
145 double myx = pos.x() / _width;
146 T_INTERP interpFromNearby = _curve->getLowerBoundCV(KSeExpr::clamp(myx, 0, 1))._interp;
147 if (interpFromNearby == T_CURVE::kNone)
148 interpFromNearby = T_CURVE::kMonotoneSpline;
149 addPoint(myx, _curve->getValue(myx), interpFromNearby);
151 } else {
152 _selectedItem = -1;
153 drawPoints();
154 }
155 }
156}
157
158void CCurveScene::mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent)
159{
160 if (_lmb) {
161 QPointF point = mouseEvent->scenePos();
162 if (_selectedItem >= 0) {
163 // clamp motion to inside curve area
164 double pos = KSeExpr::clamp(point.x() / _width, 0, 1);
165 _cvs[_selectedItem]._pos = pos;
166 rebuildCurve();
167 _pixmapDirty = true;
168 _baseRectW->update();
170 drawPoints();
172 }
173 }
174}
175
176void CCurveScene::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
177{
178 if (_selectedItem >= 0) {
179 auto *menu = new QMenu(event->widget());
180 QAction *deleteAction = menu->addAction(tr("Delete Point"));
181 QAction *action = menu->exec(event->screenPos());
182 if (action == deleteAction)
184 }
185}
186
187void CCurveScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent)
188{
189 Q_UNUSED(mouseEvent);
190 _lmb = false;
191}
192
193// user selected a different interpolation type, redraw
194void CCurveScene::interpChanged(const int interp)
195{
196 _interp = (T_INTERP)interp;
197 if (_selectedItem >= 0) {
198 _cvs[_selectedItem]._interp = _interp;
199 rebuildCurve();
200 _pixmapDirty = true;
201 _baseRectW->update();
203 }
204}
205
206// user entered a different point position, redraw
208{
209 if (_selectedItem >= 0) {
210 pos = KSeExpr::clamp(pos, 0, 1);
211 _cvs[_selectedItem]._pos = pos;
212 rebuildCurve();
213 _pixmapDirty = true;
214 _baseRectW->update();
215 drawPoints();
217 }
218}
219
220// user entered a different point value, redraw
222{
223 _color = val;
224 if (_selectedItem >= 0) {
225 _cvs[_selectedItem]._val = val;
226 rebuildCurve();
227 _pixmapDirty = true;
228 _baseRectW->update();
229 drawPoints();
231 }
232}
233
234// return points in reverse order in order to use same parsing in editor
236{
237 emit curveChanged();
238}
239
241{
242 if (_pixmapDirty) {
243 QByteArray buf;
244 buf.append(QLatin1String("P6\n%1 %2\n255\n").arg(QString::number(_width), QString::number(_height)).toUtf8());
245 buf.append(getCPixmap());
246 _pixmap.loadFromData(buf, "PPM");
247 _pixmapDirty = false;
248 }
249 return _pixmap;
250}
251
253{
254 // create pixmap, set to gray
255 const int len = 3 * _width * _height;
256 QByteArray pixmap(len, 127);
257
258 double paramInc = 1.0 / (_width - 2);
259 double param = 0.5 * paramInc; // start at pixel center
260 // add black lines to left
261 char *ptr = pixmap.data();
262 *ptr++ = 0;
263 *ptr++ = 0;
264 *ptr++ = 0;
265 for (int i = 1; i < _width - 1; i++) {
266 KSeExpr::Vec3d color = _curve->getValue(param);
267 *ptr++ = char(std::min(std::max(0.0, 255 * color[0]), 255.0) + 0.5);
268 *ptr++ = char(std::min(std::max(0.0, 255 * color[1]), 255.0) + 0.5);
269 *ptr++ = char(std::min(std::max(0.0, 255 * color[2]), 255.0) + 0.5);
270 param += paramInc;
271 }
272 // add black lines to right
273 *ptr++ = 0;
274 *ptr++ = 0;
275 *ptr++ = 0;
276
277 for (int i = 1; i < _height - 1; i++) {
278 memcpy(pixmap.data() + (i * _width * 3), pixmap.data() + ((i - 1) * _width * 3), _width * 3);
279 }
280
281 // add black lines to top and bottom
282 memset(pixmap.data(), 0, _width * 3);
283 memset(pixmap.data() + ((_height - 1) * _width * 3), 0, _width * 3);
284
285 return pixmap;
286}
287
288// draws the base gray outline rectangle
290{
291 if (_baseRectW == 0) {
292 _baseRectW = new ExprCBoxWidget(this);
293 // Disable the obtrusive grey background.
294 // It's noticeable with the sunken border of the CCurve. -amyspark
295 _baseRectW->setStyleSheet("background-color: transparent;");
296 }
297 if (_baseRect == nullptr) {
298 _baseRect = addWidget(_baseRectW);
299 }
300 _baseRectW->setMinimumWidth(_width);
301 _baseRect->widget()->update();
302 _baseRect->setZValue(0);
303}
304
305// draws the cv points
307{
308 while (!_circleObjects.empty()) {
309 delete _circleObjects[0];
310 _circleObjects.erase(_circleObjects.begin());
311 }
312 const int numCV = _cvs.size();
313 for (int i = 0; i < numCV; i++) {
314 const T_CURVE::CV &pt = _cvs[i];
315 QPen pen;
316 if (i == _selectedItem) {
317 pen = QPen(QColor(255, 170, 0), 1.0);
318 } else {
319 pen = QPen(Qt::black, 1.0);
320 }
321 _circleObjects.push_back(addEllipse(pt._pos * _width - 4, _height + 3, 8, 8, pen, QBrush(QColor(int(255 * pt._val[0] + 0.5), int(255 * pt._val[1] + 0.5), int(255 * pt._val[2] + 0.5)))));
322 QGraphicsEllipseItem *circle = _circleObjects.back();
323 circle->setFlag(QGraphicsItem::ItemIsMovable, true);
324 circle->setZValue(2);
325 }
326}
327
328void ExprCBoxWidget::paintEvent(QPaintEvent *event)
329{
330 Q_UNUSED(event);
331 QPainter p(this);
332 p.drawPixmap(0, 0, _curveScene->getPixmap());
333}
334
335void ExprCSwatchFrame::paintEvent(QPaintEvent *event)
336{
337 Q_UNUSED(event);
338 QPainter p(this);
339 p.fillRect(contentsRect(), _color);
340}
341
343 : QFrame(parent)
344 , _value(value)
345{
346 _color = QColor(int(255 * _value[0] + 0.5), int(255 * _value[1] + 0.5), int(255 * _value[2] + 0.5));
347}
348
350{
351 _color = QColor(int(255 * value[0] + 0.5), int(255 * value[1] + 0.5), int(255 * value[2] + 0.5));
352 // setPalette(QPalette(_color));
353 _value = value;
354 repaint();
355}
356
361
362void ExprCSwatchFrame::mousePressEvent(QMouseEvent *event)
363{
364 Q_UNUSED(event);
365 QColor color = QColorDialog::getColor(_color);
366 if (color.isValid()) {
367 _value[0] = color.red() / 255.0;
368 _value[1] = color.green() / 255.0;
369 _value[2] = color.blue() / 255.0;
370 setPalette(QPalette(color));
371 _color = color;
373 emit swatchChanged(color);
374 }
375}
376
377ExprColorCurve::ExprColorCurve(QWidget *parent, QString pLabel, QString vLabel, QString iLabel, bool expandable)
378 : QWidget(parent)
379 , _scene(nullptr)
380 , _selPosEdit(nullptr)
381 , _selValEdit(nullptr)
382 , _interpComboBox(nullptr)
383{
384 auto *mainLayout = new QHBoxLayout();
385 mainLayout->setContentsMargins({});
386
387 auto *edits = new QWidget;
388 auto *editsLayout = new QFormLayout;
389 editsLayout->setContentsMargins({});
390 edits->setLayout(editsLayout);
391
392 _selPosEdit = new QLineEdit;
393 auto *posValidator = new QDoubleValidator(0.0, 1.0, 6, _selPosEdit);
394 _selPosEdit->setValidator(posValidator);
395 QString posLabel;
396 if (pLabel.isEmpty()) {
397 posLabel = tr("Selected Position:");
398 } else {
399 posLabel = pLabel;
400 }
401 editsLayout->addRow(posLabel, _selPosEdit);
402
404 _selValEdit->setMinimumHeight(_selPosEdit->minimumSizeHint().height());
405 _selValEdit->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
406 QString valLabel;
407 if (vLabel.isEmpty()) {
408 valLabel = tr("Selected Color:");
409 } else {
410 valLabel = vLabel;
411 }
412 editsLayout->addRow(valLabel, _selValEdit);
413
414 QString interpLabel;
415 if (iLabel.isEmpty()) {
416 interpLabel = tr("Interp:");
417 } else {
418 interpLabel = iLabel;
419 }
420 _interpComboBox = new QComboBox;
421 _interpComboBox->addItem(tr("None"));
422 _interpComboBox->addItem(tr("Linear"));
423 _interpComboBox->addItem(tr("Smooth"));
424 _interpComboBox->addItem(tr("Spline"));
425 _interpComboBox->addItem(tr("MSpline"));
426 _interpComboBox->setCurrentIndex(4);
427 editsLayout->addRow(interpLabel, _interpComboBox);
428
429 auto *curveView = new CurveGraphicsView;
430 curveView->setFrameShape(QFrame::StyledPanel);
431 curveView->setFrameShadow(QFrame::Sunken);
432 curveView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
433 curveView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
434 _scene = new CCurveScene;
435 curveView->setScene(_scene);
436 curveView->setTransform(QTransform().scale(1, -1));
437 curveView->setRenderHints(QPainter::Antialiasing);
438
439 mainLayout->addWidget(edits);
440 mainLayout->addWidget(curveView);
441 if (expandable) {
442 auto *expandButton = new QToolButton(this);
443 expandButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
444 QIcon expandIcon = QIcon::fromTheme("arrow-right", QIcon::fromTheme("go-next"));
445 auto *detailAction = new QAction(expandIcon, tr("&Expand..."));
446 expandButton->setDefaultAction(detailAction);
447 mainLayout->addWidget(expandButton);
448 // open a the detail widget when clicked
449 connect(expandButton, SIGNAL(triggered(QAction *)), this, SLOT(openDetail()));
450 }
451 mainLayout->setStretchFactor(curveView, 100);
452 setLayout(mainLayout);
453
454 // SIGNALS
455
456 // when a user selects a cv, update the fields on left
457 connect(_scene, SIGNAL(cvSelected(double, KSeExpr::Vec3d, T_INTERP)), this, SLOT(cvSelectedSlot(double, KSeExpr::Vec3d, T_INTERP)));
458 // when a user selects a different interp, the curve has to redraw
459 connect(_interpComboBox, SIGNAL(activated(int)), _scene, SLOT(interpChanged(int)));
460 // when a user types a different position, the curve has to redraw
461 connect(_selPosEdit, SIGNAL(returnPressed()), this, SLOT(selPosChanged()));
462 connect(this, SIGNAL(selPosChangedSignal(double)), _scene, SLOT(selPosChanged(double)));
463 // when a user selects a different color, the ramp has to redraw
464 connect(_selValEdit, SIGNAL(selValChangedSignal(KSeExpr::Vec3d)), _scene, SLOT(selValChanged(KSeExpr::Vec3d)));
465 connect(_selValEdit, SIGNAL(swatchChanged(QColor)), this, SLOT(internalSwatchChanged(QColor)));
466 // when the widget is resized, resize the curve widget
467 connect(curveView, SIGNAL(resizeSignal(int, int)), _scene, SLOT(resize(int, int)));
468}
469
470// CV selected, update the user interface fields.
471void ExprColorCurve::cvSelectedSlot(const double pos, const KSeExpr::Vec3d val, const T_INTERP interp)
472{
473 QString posStr;
474 if (pos >= 0.0) {
475 posStr.setNum(pos, 'f', 3);
476 _selPosEdit->setText(posStr);
477 _selValEdit->setValue(val);
478 emit swatchChanged(QColor::fromRgbF(val[0], val[1], val[2], 1));
479 _interpComboBox->setCurrentIndex(interp);
480 }
481}
482
483// User entered new position, round and send signal to redraw curve.
485{
486 double pos = KSeExpr::clamp(QString(_selPosEdit->text()).toFloat(), 0, 1);
487 _selPosEdit->setText(QString(tr("%1")).arg(pos, 0, 'f', 3));
488 emit selPosChangedSignal(pos);
489}
490
491void ExprColorCurve::addPoint(const double x, const KSeExpr::Vec3d y, const T_INTERP interp, const bool select)
492{
493 _scene->addPoint(x, y, interp, select);
494}
495
497{
498 KSeExpr::Vec3d newColor(color.redF(), color.greenF(), color.blueF());
499 _scene->selValChanged(newColor);
500 _selValEdit->setValue(newColor);
501}
502
504{
506 return QColor::fromRgbF(val[0], val[1], val[2], 1);
507}
508
510{
511 emit swatchChanged(color);
512}
513
515{
516 auto *dialog = new QDialog();
517 dialog->setMinimumWidth(1024);
518 dialog->setMinimumHeight(400);
519 auto *curve = new ExprColorCurve(nullptr, QString(), QString(), QString(), false);
520
521 // copy points into new data
522 const auto &data = _scene->_cvs;
523 for (const auto& i : data)
524 curve->addPoint(i._pos, i._val, i._interp);
525
526 auto *layout = new QVBoxLayout();
527 dialog->setLayout(layout);
528 layout->addWidget(curve);
529
530 dialog->setLayout(layout);
531 layout->addWidget(curve);
532 auto *buttonbar = new QDialogButtonBox();
533 buttonbar->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok);
534 connect(buttonbar, SIGNAL(accepted()), dialog, SLOT(accept()));
535 connect(buttonbar, SIGNAL(rejected()), dialog, SLOT(reject()));
536 layout->addWidget(buttonbar);
537
538 if (dialog->exec() == QDialog::Accepted) {
539 // copy points back from child
540 _scene->removeAll();
541 const auto &dataNew = curve->_scene->_cvs;
542 for (const auto &i : dataNew)
543 addPoint(i._pos, i._val, i._interp);
545 }
546}
static constexpr std::array< int, 514 > p
Definition NoiseTables.h:10
void interpChanged(int interp)
QByteArray getCPixmap()
QPixmap _pixmap
void removePoint(int index)
QGraphicsProxyWidget * _baseRect
void curveChanged()
KSeExpr::Curve< KSeExpr::Vec3d > T_CURVE
QWidget * _baseRectW
void mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent) override
void mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) override
QPixmap & getPixmap()
KSeExpr::Vec3d _color
std::vector< QGraphicsEllipseItem * > _circleObjects
void emitCurveChanged()
void selPosChanged(double pos)
void selValChanged(const KSeExpr::Vec3d &val)
void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override
void keyPressEvent(QKeyEvent *event) override
void mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent) override
std::vector< T_CURVE::CV > _cvs
void addPoint(double x, KSeExpr::Vec3d y, T_INTERP interp, bool select=true)
~CCurveScene() override
T_CURVE::InterpType T_INTERP
T_CURVE * _curve
void cvSelected(double x, KSeExpr::Vec3d y, T_INTERP interp)
T_INTERP _interp
void resize(int width, int height)
void paintEvent(QPaintEvent *event) override
CCurveScene * _curveScene
ExprCSwatchFrame(KSeExpr::Vec3d value, QWidget *parent=nullptr)
KSeExpr::Vec3d getValue() const
void swatchChanged(QColor color)
void paintEvent(QPaintEvent *event) override
KSeExpr::Vec3d _value
void setValue(const KSeExpr::Vec3d &value)
void selValChangedSignal(KSeExpr::Vec3d value)
void mousePressEvent(QMouseEvent *event) override
void internalSwatchChanged(QColor color)
void addPoint(double x, KSeExpr::Vec3d y, T_INTERP interp, bool select=false)
void swatchChanged(QColor color)
void setSwatchColor(QColor color)
CCurveScene * _scene
QComboBox * _interpComboBox
ExprCSwatchFrame * _selValEdit
ExprColorCurve(QWidget *parent=nullptr, QString pLabel=QString(), QString vLabel=QString(), QString iLabel=QString(), bool expandable=true)
void cvSelectedSlot(double pos, KSeExpr::Vec3d val, T_INTERP interp)
QLineEdit * _selPosEdit
void selValChangedSignal(KSeExpr::Vec3d val)
void selPosChangedSignal(double pos)
InterpType
Supported interpolation types.
Definition Curve.h:32
double clamp(double x, double lo, double hi)