KSeExpr  4.0.4.0
imageEditor.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 
10 #include <iostream>
11 #include <string>
12 
13 #include <QApplication>
14 #include <QDialog>
15 #include <QDir>
16 #include <QImage>
17 #include <QLabel>
18 #include <QMessageBox>
19 #include <QPushButton>
20 #include <QScrollArea>
21 #include <QVBoxLayout>
22 
23 #include <KSeExpr/Expression.h>
25 #include <KSeExprUI/ExprBrowser.h>
27 #include <KSeExprUI/ExprEditor.h>
28 
29 #include "ImageEditorDialog.h"
30 
31 //-- IMAGE SYNTHESIZER CLASSES AND METHODS --//
32 
33 constexpr double clamp(double x)
34 {
35  return std::max(0., std::min(255., x)); // NOLINT readability-magic-numbers
36 }
37 
38 // Simple image synthesizer expression class to support demo image editor
39 class ImageSynthExpression : public KSeExpr::Expression
40 {
41 public:
42  // Constructor that takes the expression to parse
43  ImageSynthExpression(const std::string &expr)
44  : KSeExpr::Expression(expr)
45  {
46  }
47 
48  // Simple variable that just returns its internal value
49  struct Var : public KSeExpr::ExprVarRef {
50  Var(const double val)
51  : KSeExpr::ExprVarRef(KSeExpr::ExprType().FP(1).Varying())
52  , val(val)
53  {
54  }
55  Var()
56  : KSeExpr::ExprVarRef(KSeExpr::ExprType().FP(1).Varying())
57  {
58  }
59  double val {0.0}; // independent variable
60  void eval(double *result) override
61  {
62  result[0] = val;
63  }
64  void eval(const char **) override
65  {
66  assert(false);
67  }
68  };
69  // variable map
70  mutable std::map<std::string, Var> vars;
71 
72  // resolve function that only supports one external variable 'x'
73  KSeExpr::ExprVarRef *resolveVar(const std::string &name) const override
74  {
75  auto i = vars.find(name);
76  if (i != vars.end())
77  return &i->second;
78  return nullptr;
79  }
80 };
81 
82 class ImageSynthesizer
83 {
84 public:
85  using ImageData = std::shared_ptr<std::vector<unsigned char>>;
86  ImageSynthesizer() = default;
87  ImageData evaluateExpression(const std::string &exprStr) const
88  {
89  ImageSynthExpression expr(exprStr);
90 
91  // make variables
92  expr.vars["u"] = ImageSynthExpression::Var(0.);
93  expr.vars["v"] = ImageSynthExpression::Var(0.);
94  expr.vars["w"] = ImageSynthExpression::Var(_width);
95  expr.vars["h"] = ImageSynthExpression::Var(_height);
96 
97  // check if expression is valid
98  bool valid = expr.isValid();
99  if (!valid) {
100  std::cerr << "Invalid expression " << std::endl;
101  auto message = ErrorMessages::message(expr.parseError());
102  for (const auto &arg : expr.parseErrorArgs()) {
103  message = message.arg(QString::fromStdString(arg));
104  }
105  std::cerr << "Parse error: " << message.toStdString() << std::endl;
106  for (const auto &occurrence : expr.getErrors()) {
107  QString message = ErrorMessages::message(occurrence.error);
108  for (const auto &arg : occurrence.ids) {
109  message = message.arg(QString::fromStdString(arg));
110  }
111  std::cerr << "Prep error: " << message.toStdString() << std::endl;
112  }
113  return nullptr;
114  }
115 
116  // evaluate expression
117  std::cerr << "Evaluating expression..." << std::endl;
118  std::vector<unsigned char> image(_width * _height * 4);
119  double one_over_width = 1. / _width;
120  double one_over_height = 1. / _height;
121  double &u = expr.vars["u"].val;
122  double &v = expr.vars["v"].val;
123  for (size_t row {}; row < _height; row++) {
124  for (size_t col {}; col < _width; col++) {
125  auto i = (row * _width + col) * 4;
126  u = one_over_width * (col + .5); // NOLINT readability-magic-constants
127  v = one_over_height * (row + .5); // NOLINT readability-magic-constants
128  KSeExpr::Vec3d result = KSeExpr::Vec3dConstRef(expr.evalFP());
129  image[i] = clamp(result[2] * 256.); // NOLINT readability-magic-numbers
130  image[i + 1] = clamp(result[1] * 256.); // NOLINT readability-magic-numbers
131  image[i + 2] = clamp(result[0] * 256.); // NOLINT readability-magic-numbers
132  image[i + 3] = 255; // NOLINT readability-magic-numbers
133  }
134  }
135 
136  return std::make_shared<std::vector<unsigned char>>(image);
137  }
138 
139 private:
140  size_t _width {256}; // NOLINT readability-magic-numbers
141  size_t _height {256}; // NOLINT readability-magic-numbers
142 };
143 
144 //-- IMAGE EDITOR DIALOG METHODS --//
145 
146 ImageEditorDialog::ImageEditorDialog(QWidget *parent)
147  : QDialog(parent)
148 {
149  _imageSynthesizer = new ImageSynthesizer();
150 
151  this->setWindowTitle("Image Synthesis Editor");
152 
153  // Image Previewer
154  _imageLabel = new QLabel();
155  _imageLabel->setFixedSize(256, 256); // NOLINT readability-magic-numbers
156  _imageLabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
157 
158  auto *imagePreviewWidget = new QWidget();
159  auto *imagePreviewLayout = new QHBoxLayout(imagePreviewWidget);
160  imagePreviewLayout->addStretch();
161  imagePreviewLayout->addWidget(_imageLabel);
162  imagePreviewLayout->addStretch();
163 
164  // Expression controls
165  auto *controls = new ExprControlCollection();
166  auto *scrollArea = new QScrollArea();
167  scrollArea->setMinimumHeight(100); // NOLINT readability-magic-numbers
168  scrollArea->setFixedWidth(450); // NOLINT readability-magic-numbers
169  scrollArea->setWidgetResizable(true);
170  scrollArea->setWidget(controls);
171 
172  // Expression editor
173  _editor = new ExprEditor(this);
174  _editor->setControlCollectionWidget(controls);
175 
176  // Expression browser
177  auto *browser = new ExprBrowser(nullptr, _editor);
178 
179  // Add user expressions, example expressions to browser list.
180  browser->addUserExpressionPath("imageEditor");
181 #ifdef IMAGE_EDITOR_ROOT
182  browser->addPath("Examples", QDir::toNativeSeparators(QString("%1/share/KSeExpr/expressions").arg(IMAGE_EDITOR_ROOT)).toStdString());
183 #else
184  browser->addPath("Examples", "./src/demos/imageEditor");
185 #endif
186  browser->update();
187 
188  // Create apply button and connect to image preview.
189  auto *applyButton = new QPushButton("Apply");
190  connect(applyButton, SIGNAL(clicked()), (ImageEditorDialog *)this, SLOT(applyExpression()));
191 
192  // Layout widgets: Top section contains left side with previewer and
193  // controls, right side with browser. Bottom section contains editor
194  // and apply button.
195  auto *rootLayout = new QVBoxLayout();
196  this->setLayout(rootLayout);
197 
198  auto *topWidget = new QWidget();
199  auto *topLayout = new QHBoxLayout();
200  topLayout->setContentsMargins(0, 0, 0, 0);
201  topWidget->setLayout(topLayout);
202 
203  auto *leftWidget = new QWidget();
204  auto *leftLayout = new QVBoxLayout();
205  leftLayout->setContentsMargins(0, 0, 0, 0);
206  leftWidget->setLayout(leftLayout);
207  leftLayout->addWidget(imagePreviewWidget);
208  leftLayout->addWidget(scrollArea, 1);
209 
210  auto *bottomWidget = new QWidget();
211  auto *bottomLayout = new QVBoxLayout();
212  bottomLayout->setContentsMargins(0, 0, 0, 0);
213  bottomWidget->setLayout(bottomLayout);
214 
215  auto *buttonWidget = new QWidget();
216  auto *buttonLayout = new QHBoxLayout(nullptr);
217  buttonWidget->setLayout(buttonLayout);
218  buttonLayout->addWidget(applyButton);
219 
220  topLayout->addWidget(leftWidget);
221  topLayout->addWidget(browser, 1);
222 
223  bottomLayout->addWidget(_editor);
224  bottomLayout->addWidget(buttonWidget);
225 
226  rootLayout->addWidget(topWidget);
227  rootLayout->addWidget(bottomWidget);
228 }
229 
230 // Apply expression, if any, from the editor contents to the preview image
231 void ImageEditorDialog::applyExpression()
232 {
233  std::string exprStr = _editor->getExpr().toStdString();
234  if (exprStr.empty()) {
235  QMessageBox::information(this, this->windowTitle(), "No expression entered in the editor.");
236  } else {
237  auto data = _imageSynthesizer->evaluateExpression(exprStr);
238  if (!data) {
239  QMessageBox::critical(this, this->windowTitle(), "Error evaluating expression to create preview image.");
240  } else {
241  QImage image(data->data(), 256, 256, QImage::Format_RGB32); // NOLINT readability-magic-numbers
242  QPixmap imagePixmap = QPixmap::fromImage(image);
243  _imageLabel->setPixmap(imagePixmap);
244  // amyspark: ensure the old image gets dropped only after it was replaced
245  imageData = data;
246  }
247  }
248 }
249 
250 //-- MAIN --//
251 
252 int main(int argc, char *argv[])
253 {
254  auto app = std::make_unique<QApplication>(argc, argv);
255  auto dialog = std::make_unique<ImageEditorDialog>(nullptr);
256  dialog->show();
257  app->exec();
258  return 0;
259 }
void eval(ArgHandle args) override
static QString message(KSeExpr::ErrorCode code)
abstract class for implementing variable references
Definition: Expression.h:36
main expression class
Definition: Expression.h:67
virtual ExprVarRef * resolveVar(const std::string &) const
Definition: Expression.h:201
Expression(EvaluationStrategy be=Expression::defaultEvaluationStrategy)
Definition: Expression.cpp:54
int main(int argc, char *argv[])
constexpr double clamp(double x)
Definition: imageEditor.cpp:33
Vec< const double, 3, true > Vec3dConstRef
Definition: Vec.h:368
double max(double x, double y)
Definition: ExprBuiltins.h:74
double min(double x, double y)
Definition: ExprBuiltins.h:78