Krita Source Code Documentation
Loading...
Searching...
No Matches
WGShadeSlider.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2020 Mathias Wein <lynx.mw+kde@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-3.0-or-later
5 */
6
7#include "WGShadeSlider.h"
8
10
11#include <QImage>
12#include <QMouseEvent>
13#include <QPainter>
14#include <QVector4D>
15#include <QtMath>
16
18{
20 QImage background;
21 QVector4D range;
22 QVector4D offset;
23 QVector4D baseValues;
24 qreal handleValue {0};
25 qreal leftStart {-1};
26 qreal leftEnd {0};
27 qreal rightStart {0};
28 qreal rightEnd {-1};
31 int cursorWidth {11};
32 int lineWidth {1};
33 int numPatches {9};
34 bool widgetSizeOk {false};
35 bool sliderMode {true};
36 bool imageNeedsUpdate {true};
37};
38
40 : QWidget(parent)
41 , m_d(new Private)
42{
43 m_d->selectorModel = model;
44 m_d->displayConfig = config;
48}
49
52
53void WGShadeSlider::setGradient(const QVector4D &range, const QVector4D &offset)
54{
55 m_d->range = range;
56 m_d->offset = offset;
57 m_d->imageNeedsUpdate = true;
59}
60
61void WGShadeSlider::setDisplayMode(bool slider, int numPatches)
62{
63 if (slider != m_d->sliderMode ||
64 (!slider && numPatches != m_d->numPatches)) {
65 m_d->sliderMode = slider;
66 if (!slider && numPatches > 2) {
67 m_d->numPatches = numPatches;
68 }
69 m_d->widgetSizeOk = sizeRequirementsMet();
70 m_d->imageNeedsUpdate = true;
72 }
73}
74
76{
77 m_d->selectorModel = model;
78 m_d->imageNeedsUpdate = true;
79 update();
80}
81
83{
84 return calculateChannelValues(m_d->handleValue);
85}
86
88{
89 if (m_d->imageNeedsUpdate) {
90 m_d->background = renderBackground();
91 m_d->imageNeedsUpdate = false;
92 }
93 return &m_d->background;
94}
95
97{
98 return QSize(50, 8);
99}
100
101void WGShadeSlider::mousePressEvent(QMouseEvent *event)
102{
103 if (event->button() == Qt::LeftButton) {
104 Q_EMIT sigInteraction(true);
105 if (adjustHandleValue(event->localPos())) {
107 update();
108 }
109 } else {
110 event->ignore();
111 }
112}
113
114void WGShadeSlider::mouseMoveEvent(QMouseEvent *event)
115{
116 if (event->buttons() & Qt::LeftButton) {
117 if (adjustHandleValue(event->localPos())) {
119 update();
120 }
121 } else {
122 event->ignore();
123 }
124}
125
126void WGShadeSlider::mouseReleaseEvent(QMouseEvent *event)
127{
128 if (event->button() == Qt::LeftButton) {
129 Q_EMIT sigInteraction(false);
130 } else {
131 event->ignore();
132 }
133}
134
136{
137 if (m_d->imageNeedsUpdate) {
138 m_d->background = renderBackground();
139 m_d->imageNeedsUpdate = false;
140 }
141 QPainter painter(this);
142 painter.drawImage(0, 0, m_d->background);
143 painter.scale(1.0/devicePixelRatioF(), 1.0/devicePixelRatioF());
144 QRectF handleRect;
145 if (m_d->sliderMode) {
146 QPointF sliderPos = convertSliderValueToWidgetCoordinate(m_d->handleValue);
147 int sliderX = qRound(sliderPos.x());
148 handleRect = QRectF(sliderX - m_d->cursorWidth/2, 0, m_d->cursorWidth, height());
149 } else if (m_d->handleValue >= 0) {
150 handleRect = patchRect(m_d->handleValue);
151 }
152 if (handleRect.isValid()) {
153 QPen pen(QColor(175,175,175), m_d->lineWidth, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin);
154 painter.setPen(pen);
155 strokeRect(painter, handleRect, devicePixelRatioF(), 0);
156 pen.setColor(QColor(75,75,75));
157 painter.setPen(pen);
158 strokeRect(painter, handleRect, devicePixelRatioF(), 1);
159 }
160}
161
162void WGShadeSlider::resizeEvent(QResizeEvent *)
163{
165}
166
167void WGShadeSlider::slotSetChannelValues(const QVector4D &values)
168{
169 m_d->baseValues = values;
170 m_d->imageNeedsUpdate = true;
171 resetHandle();
172}
173
175{
176 m_d->handleValue = m_d->sliderMode ? 0 : -1;
177 update();
178}
179
181{
182 m_d->imageNeedsUpdate = true;
183 update();
184}
185
186bool WGShadeSlider::adjustHandleValue(const QPointF &widgetPos)
187{
188 if (!m_d->widgetSizeOk) {
189 return false;
190 }
191
192 if (m_d->sliderMode) {
193 qreal sliderPos = convertWidgetCoordinateToSliderValue(widgetPos);
194 if (!qFuzzyIsNull(m_d->handleValue - sliderPos)) {
195 m_d->handleValue = sliderPos;
196 return true;
197 }
198 } else {
199 int patchNum = getPatch(widgetPos);
200 if (patchNum >= 0 && patchNum != (int)m_d->handleValue) {
201 m_d->handleValue = patchNum;
202 return true;
203 }
204 }
205 return false;
206}
207
209{
210 QPointF pos(0.0, 0.0);
211 if (value < 0) {
212 pos.setX(m_d->leftStart - value * (m_d->leftEnd - m_d->leftStart));
213 }
214 else if (value > 0) {
215 pos.setX(m_d->rightStart + value * (m_d->rightEnd - m_d->rightStart));
216 }
217 else {
218 pos.setX((width() - 1) / 2);
219 }
220 return pos;
221}
222
224{
225 qreal x = coordinate.x();
226 if (x < m_d->leftEnd) {
227 return -1.0;
228 }
229 else if (x < m_d->leftStart) {
230 return (m_d->leftStart - x) / (m_d->leftEnd - m_d->leftStart);
231 }
232 else if (x < m_d->rightStart) {
233 return 0.0;
234 }
235 else if (x < m_d->rightEnd) {
236 return (x - m_d->rightStart) / (m_d->rightEnd - m_d->rightStart);
237 }
238 return 1.0;
239}
240
241QVector4D WGShadeSlider::calculateChannelValues(qreal sliderPos) const
242{
243 float delta = 0.0f;
244 if (m_d->sliderMode) {
245 delta = (float)sliderPos;
246 } else if (sliderPos >= 0 || m_d->numPatches > 1) {
247 delta = 2.0f * float(sliderPos)/(m_d->numPatches - 1.0f) - 1.0f;
248 }
249
250 QVector4D channelVec = m_d->baseValues + m_d->offset + delta * m_d->range;
251 // Hue wraps around
252 if (m_d->selectorModel->isHSXModel()) {
253 channelVec[0] = (float)fmod(channelVec[0], 1.0);
254 if (channelVec[0] < 0) {
255 channelVec[0] += 1.f;
256 }
257 }
258 else {
259 channelVec[0] = qBound(0.f, channelVec[0], 1.f);
260 }
261
262 for (int i = 1; i < 3; i++) {
263 channelVec[i] = qBound(0.f, channelVec[i], 1.f);
264 }
265 return channelVec;
266}
267
268int WGShadeSlider::getPatch(const QPointF pos) const
269{
270 int patch = m_d->numPatches * pos.x() / width();
271 if (patch >= 0 && patch < m_d->numPatches) {
272 return patch;
273 }
274 return -1;
275}
276
277QRectF WGShadeSlider::patchRect(int index) const
278{
279 qreal patchWidth = width() / qreal(m_d->numPatches);
280 qreal margin = 1.5;
281 QPointF topLeft(index * patchWidth + margin, 0);
282 QPointF bottomRight((index+1) * patchWidth - margin, height());
283 return QRectF(topLeft, bottomRight);
284}
285
287{
288 int center = (width() - 1) / 2;
289 int halfCursor = m_d->cursorWidth / 2;
290
291 m_d->leftEnd = halfCursor;
292 m_d->leftStart = center - halfCursor;
293
294 m_d->rightStart = center + halfCursor;
295 m_d->rightEnd = 2 * center - halfCursor;
296
297 m_d->lineWidth = qRound(devicePixelRatioF() - 0.1);
298 m_d->widgetSizeOk = sizeRequirementsMet();
299 m_d->imageNeedsUpdate = true;
300}
301
303{
304 if (m_d->sliderMode) {
305 return m_d->leftStart - m_d->leftEnd > 0 && m_d->rightEnd - m_d->rightStart > 0;
306 } else {
307 return width() > m_d->numPatches;
308 }
309}
310
312{
313 if (!m_d->widgetSizeOk || !m_d->selectorModel || !m_d->selectorModel->colorSpace()) {
314 return QImage();
315 }
316
317 // Hi-DPI aware rendering requires that we determine the device pixel dimension;
318 // actual widget size in device pixels is not accessible unfortunately, it might be 1px smaller...
319 const qreal deviceDivider = 1.0 / devicePixelRatioF();
320 const int deviceWidth = qCeil(width() * devicePixelRatioF());
321 const int deviceHeight = qCeil(height() * devicePixelRatioF());
322 if (m_d->sliderMode) {
323 const KoColorSpace *currentCS = m_d->selectorModel->colorSpace();
324 const quint32 pixelSize = currentCS->pixelSize();
325 quint32 imageSize = deviceWidth * pixelSize;
326 QScopedArrayPointer<quint8> raw(new quint8[imageSize] {});
327 quint8 *dataPtr = raw.data();
328
329 for (int x = 0; x < deviceWidth; x++, dataPtr += pixelSize) {
330 qreal sliderVal = convertWidgetCoordinateToSliderValue(QPointF(x, 0) * deviceDivider);
331 QVector4D coordinates = calculateChannelValues(sliderVal);
332 KoColor c = m_d->selectorModel->convertChannelValuesToKoColor(coordinates);
333 memcpy(dataPtr, c.data(), pixelSize);
334 }
335
336 QImage image = m_d->displayConfig->displayConverter()->toQImage(currentCS, raw.data(), {deviceWidth, 1},
337 m_d->displayConfig->previewInPaintingCS());
338 image = image.scaled(QSize(deviceWidth, deviceHeight));
339
340 QPainter painter(&image);
341 QPen pen(QColor(175,175,175), m_d->lineWidth, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin);
342 painter.setPen(pen);
343 strokeRect(painter, QRectF(m_d->leftStart, 0, m_d->cursorWidth, height()), devicePixelRatioF(), 0);
344 pen.setColor(QColor(75,75,75));
345 painter.setPen(pen);
346 strokeRect(painter, QRectF(m_d->leftStart, 0, m_d->cursorWidth, height()), devicePixelRatioF(), 1);
347
348 image.setDevicePixelRatio(devicePixelRatioF());
349 return image;
350 } else {
351 QImage image(deviceWidth, deviceHeight, QImage::Format_ARGB32);
352 image.fill(Qt::transparent);
353 image.setDevicePixelRatio(devicePixelRatioF());
354 QPainter painter(&image);
355 painter.setPen(Qt::NoPen);
356
357 for (int i = 0; i < m_d->numPatches; i++) {
358 QVector4D values = calculateChannelValues(i);
359 KoColor col = m_d->selectorModel->convertChannelValuesToKoColor(values);
360 QColor qCol = m_d->displayConfig->displayConverter()->toQColor(col, m_d->displayConfig->previewInPaintingCS());
361 painter.setBrush(qCol);
362 painter.drawRect(patchRect(i));
363 }
364 return image;
365 }
366}
367
368void WGShadeSlider::strokeRect(QPainter &painter, const QRectF &rect, qreal pixelSize, qreal shrinkX)
369{
370 qreal lineWidth = painter.pen().widthF();
371 QPointF topLeft(qRound(rect.left() * pixelSize) + (shrinkX + 0.5) * lineWidth,
372 qRound(rect.top() * pixelSize) + 0.5 * lineWidth);
373 QPointF bottomRight(qRound(rect.right() * pixelSize) - (shrinkX + 0.5) * lineWidth,
374 qRound(rect.bottom() * pixelSize) - 0.5 * lineWidth);
375 painter.drawRect(QRectF(topLeft, bottomRight));
376}
float value(const T *src, size_t ch)
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
virtual quint32 pixelSize() const =0
quint8 * data()
Definition KoColor.h:144
void toQColor(QColor *c) const
a convenience method for the above.
Definition KoColor.cpp:198
void sigDisplayConfigurationChanged()
void mousePressEvent(QMouseEvent *event) override
QPointF convertSliderValueToWidgetCoordinate(qreal value)
void sigInteraction(bool active)
WGShadeSlider(WGSelectorDisplayConfigSP config, QWidget *parent=nullptr, KisVisualColorModelSP model=nullptr)
bool adjustHandleValue(const QPointF &widgetPos)
~WGShadeSlider() override
QVector4D calculateChannelValues(qreal sliderPos) const
QVector4D channelValues() const
const QImage * background()
void setModel(KisVisualColorModelSP model)
void resizeEvent(QResizeEvent *) override
void setDisplayMode(bool slider, int numPatches=-1)
QRectF patchRect(int index) const
void slotDisplayConfigurationChanged()
QImage renderBackground()
int getPatch(const QPointF pos) const
void slotSetChannelValues(const QVector4D &values)
void strokeRect(QPainter &painter, const QRectF &rect, qreal pixelSize, qreal shrinkX)
strokeRect
void mouseMoveEvent(QMouseEvent *event) override
void paintEvent(QPaintEvent *) override
qreal convertWidgetCoordinateToSliderValue(QPointF coordinate)
void sigChannelValuesChanged(const QVector4D &values)
void recalculateParameters()
void setGradient(const QVector4D &range, const QVector4D &offset)
QSize minimumSizeHint() const override
bool sizeRequirementsMet() const
const QScopedPointer< Private > m_d
void mouseReleaseEvent(QMouseEvent *event) override
static bool qFuzzyIsNull(half h)
WGSelectorDisplayConfigSP displayConfig
KisVisualColorModelSP selectorModel