KSeExpr  4.0.4.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 
29 #include <KSeExpr/ExprBuiltins.h>
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 {
45  rebuildCurve();
47 }
48 
50 {
51  delete _curve;
52 }
53 
54 void 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);
74 }
75 
76 void 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 
83  rebuildCurve();
84 
85  if (select) {
86  _selectedItem = newIndex;
87  emit cvSelected(x, y, interp);
88  }
89  _pixmapDirty = true;
90  _baseRectW->update();
91  drawPoints();
92 }
93 
94 void CCurveScene::removePoint(const int index)
95 {
96  _cvs.erase(_cvs.begin() + index);
97  _selectedItem = -1;
98  rebuildCurve();
99 
100  _pixmapDirty = true;
101  _baseRectW->update();
102  drawPoints();
104 }
105 
107 {
108  _cvs.clear();
109 }
110 
111 void 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 
119 void 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 
158 void 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 
176 void 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 
187 void CCurveScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent)
188 {
189  Q_UNUSED(mouseEvent);
190  _lmb = false;
191 }
192 
193 // user selected a different interpolation type, redraw
194 void 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(QString::fromLatin1("P6\n%1 %2\n255\n").arg(_width).arg(_height));
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 
328 void ExprCBoxWidget::paintEvent(QPaintEvent *event)
329 {
330  Q_UNUSED(event);
331  QPainter p(this);
332  p.drawPixmap(0, 0, _curveScene->getPixmap());
333 }
334 
335 void 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 
358 {
359  return _value;
360 }
361 
362 void 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 
377 ExprColorCurve::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->setMargin(0);
386 
387  auto *edits = new QWidget;
388  auto *editsLayout = new QFormLayout;
389  editsLayout->setMargin(0);
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.
471 void 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 
491 void 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 rebuildCurve()
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)
QColor getSwatchColor()
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)
Interpolation curve class for double->double and double->Vec3D.
Definition: Curve.h:27
InterpType
Supported interpolation types.
Definition: Curve.h:32
@ kMonotoneSpline
Definition: Curve.h:32
void preparePoints()
Prepares points for evaluation (sorts and computes boundaries, clamps extrema)
Definition: Curve.cpp:46
T getValue(double param) const
Evaluates curve and returns full value.
Definition: Curve.cpp:94
void addPoint(double position, const T &val, InterpType type)
Adds a point to the curve.
Definition: Curve.cpp:40
CV getLowerBoundCV(double param) const
Definition: Curve.cpp:186
KSeExpr::CurveFuncX curve
double max(double x, double y)
Definition: ExprBuiltins.h:74
double min(double x, double y)
Definition: ExprBuiltins.h:78
double clamp(double x, double lo, double hi)
Definition: ExprBuiltins.h:66
Vec< double, 3, false > Vec3d
Definition: Vec.h:352
double _pos
Definition: Curve.h:41
InterpType _interp
Definition: Curve.h:43