Krita Source Code Documentation
Loading...
Searching...
No Matches
WGMyPaintShadeSelector.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2021 Mathias Wein <lynx.mw+kde@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-3.0-or-later
5 *
6 * Code based on kis_my_paint_shade_selector.cpp from Advanced Color Selector,
7 * which in turn is based on "lib/colorchanger_crossed_bowl.hpp" from MyPaint (mypaint.org),
8 *
9 * SPDX-FileCopyrightText: 2010 Adam Celarek <kdedev at xibo dot at>
10 * SPDX-FileCopyrightText: 2008 Martin Renold <martinxyz@gmx.ch>
11 * SPDX-FileCopyrightText: 2009 Ilya Portnov <nomail>
12 */
13
15
17#include <kis_paint_device.h>
18#include <kis_painter.h>
20
21#include <QMouseEvent>
22#include <QPainter>
23#include <QVector4D>
24
25#include <cmath>
26
27template<class Iterator>
28void setColorWithIterator(Iterator &it, const KoColor &color, const int pixelSize) {
29 memcpy(it.rawData(), color.data(), pixelSize);
30}
31
32inline int sqr(int x) {
33 return x*x;
34}
35
36inline qreal sqr2(qreal x) {
37 return (x*x + x)*0.5;
38}
39
40inline int signedSqr(int x) {
41 return (x > 0) ? x*x : -(x*x);
42}
43
45 : WGSelectorWidgetBase(displayConfig, parent, mode)
46{
48}
49
54
56{
57 if (m_model) {
58 disconnect(m_model.data());
59 m_model->disconnect(this);
60 }
61 m_model = model;
62 connect(this, SIGNAL(sigChannelValuesChanged(QVector4D)),
63 m_model.data(), SLOT(slotSetChannelValues(QVector4D)));
64 connect(m_model.data(), SIGNAL(sigChannelValuesChanged(QVector4D,quint32)),
65 this, SLOT(slotSetChannelValues(QVector4D)));
66 if (m_model->isHSXModel()) {
67 slotSetChannelValues(m_model->channelValues());
68 }
69}
70
72{
73 if (event->button() == Qt::LeftButton) {
74 Q_EMIT sigColorInteraction(true);
75 pickColorAt(event->localPos());
76 } else {
77 event->ignore();
78 }
79}
80
82{
83 if (event->buttons() & Qt::LeftButton) {
84 if (rect().contains(event->pos())) {
85 pickColorAt(event->localPos());
86 }
87 } else {
88 event->ignore();
89 }
90}
91
93{
94 if (event->button() == Qt::LeftButton) {
95 Q_EMIT sigColorInteraction(false);
96 } else {
97 event->ignore();
98 }
99}
100
102{
103 // Hint to the casual reader: some of the calculation here do not
104 // what Martin Renold originally intended. Not everything here will make sense.
105 // It does not matter in the end, as long as the result looks good.
106 if (!m_model || !m_model->isHSXModel()) {
107 return;
108 }
109
110 // This selector was ported from MyPaint in 2010
111 if (!m_realPixelCache || m_realPixelCache->colorSpace() != m_model->colorSpace()) {
112 m_realPixelCache = new KisPaintDevice(m_model->colorSpace());
113 m_realCircleBorder = new KisPaintDevice(m_model->colorSpace());
114// m_cachedColorSpace = colorSpace();
115 }
116 else {
119 }
120
121 const int pixelSize = m_model->colorSpace()->pixelSize();
122
123 QRect pickRectHighDPI = QRect(QPoint(0, 0), size()*devicePixelRatioF());
124 KisSequentialIterator it(m_realPixelCache, pickRectHighDPI);
125 KisSequentialIterator borderIt(m_realCircleBorder, pickRectHighDPI);
126 QVector4D values;
127 QVector4D values2;
128
129 while (it.nextPixel() && borderIt.nextPixel()) {
130 const int x = it.x();
131 const int y = it.y();
132
133 bool needsBlending = getChannelValues(QPoint(x, y), values, values2);
134
135 if (needsBlending) {
136 const qreal aaFactor = static_cast<qreal>(values2[3]);
137 KoColor color = m_model->convertChannelValuesToKoColor(values2);
138 color.setOpacity(aaFactor);
139 setColorWithIterator(borderIt, color, pixelSize);
140 }
141
142 KoColor color = m_model->convertChannelValuesToKoColor(values);
143 setColorWithIterator(it, color, pixelSize);
144 }
145
147 gc.bitBlt(QPoint(0,0), m_realCircleBorder, QRect(rect().topLeft(), rect().size()*devicePixelRatioF()));
148
149 QPainter painter(this);
150 QImage renderedImage = displayConverter()->toQImage(m_realPixelCache, displayConfiguration()->previewInPaintingCS());
151 renderedImage.setDevicePixelRatio(devicePixelRatioF());
152
153 painter.drawImage(0, 0, renderedImage);
154}
155
156void WGMyPaintShadeSelector::resizeEvent(QResizeEvent *event)
157{
158 WGSelectorWidgetBase::resizeEvent(event);
160}
161
162bool WGMyPaintShadeSelector::getChannelValues(QPoint pos, QVector4D &values, QVector4D &blendValues)
163{
164 bool needsBlending = false;
165
166 const float v_factor = 0.6f;
167 const float s_factor = 0.6f;
168 const float v_factor2 = 0.013f;
169 const float s_factor2 = 0.013f;
170
171 const int stripe_width = (15 * m_sizeHD)/255;
172 int s_radiusHD = m_sizeHD/2.6;
173
174 float h = 0;
175 float s = 0;
176 float v = 0;
177
178 int dx = pos.x() - m_widthHD/2;
179 int dy = pos.y() - m_heightHD/2;
180 int diag = sqrt(2.0) * m_sizeHD/2;
181
182 int dxs, dys;
183 if (dx > 0)
184 dxs = dx - stripe_width;
185 else
186 dxs = dx + stripe_width;
187 if (dy > 0)
188 dys = dy - stripe_width;
189 else
190 dys = dy + stripe_width;
191
192 qreal r = std::sqrt(qreal(sqr(dxs)+sqr(dys)));
193
194 if (qMin(abs(dx), abs(dy)) < stripe_width) {
195 // horizontal and vertical lines
196 bool horizontal = std::abs(dx) > std::abs(dy);
197 dx = (dx/qreal(m_sizeHD))*255;
198 dy = (dy/qreal(m_sizeHD))*255;
199
200 h = 0;
201 // x-axis = value, y-axis = saturation
202 v = dx*v_factor + signedSqr(dx)*v_factor2;
203 s = - (dy*s_factor + signedSqr(dy)*s_factor2);
204 // but not both at once
205 if (horizontal) {
206 // horizontal stripe
207 s = 0.0;
208 } else {
209 // vertical stripe
210 v = 0.0;
211 }
212 }
213 else if (std::min(std::abs(dx - dy), std::abs(dx + dy)) < stripe_width) {
214
215 dx = (dx/qreal(m_sizeHD))*255;
216 dy = (dy/qreal(m_sizeHD))*255;
217
218 h = 0;
219 // x-axis = value, y-axis = saturation
220 v = dx*v_factor + signedSqr(dx)*v_factor2;
221 s = - (dy*s_factor + signedSqr(dy)*s_factor2);
222 // both at once
223 }
224 else if (r < s_radiusHD+1) {
225
226 // hue
227 if (dx > 0)
228 h = 90*sqr2(r/s_radiusHD);
229 else
230 h = 360 - 90*sqr2(r/s_radiusHD);
231 s = 256*(atan2f(std::abs(dxs),dys)/M_PI) - 128;
232
233 if (r > s_radiusHD) {
234 needsBlending = true;
235 // antialiasing boarder
236 qreal aaFactor = r-floor(r); // part after the decimal point
237 aaFactor = 1-aaFactor;
238
239 qreal fh = m_colorH + h/360.0;
240 qreal fs = m_colorS + s/255.0;
241 qreal fv = m_colorV + v/255.0;
242
243 fh -= floor(fh);
244 fs = qBound(qreal(0.0), fs, qreal(1.0));
245 fv = qBound(qreal(0.01), fv, qreal(1.0));
246 blendValues = QVector4D(fh, fs, fv, aaFactor);
247
248 h = 180 + 180*atan2f(dys,-dxs)/M_PI;
249 v = 255*(r-s_radiusHD)/(diag-s_radiusHD) - 128;
250 s = 0; // overwrite the s value that was meant for the inside of the circle
251 // here we already have drawn the inside, and the value left should be just the background value
252 }
253 }
254 else {
255 // background (hue+darkness gradient)
256 h = 180 + 180*atan2f(dys,-dxs)/M_PI;
257 v = 255*(r-s_radiusHD)/(diag-s_radiusHD) - 128;
258 }
259
260 qreal fh = m_colorH + h/360.0;
261 qreal fs = m_colorS + s/255.0;
262 qreal fv = m_colorV + v/255.0;
263
264 fh -= floor(fh);
265 fs = qBound(qreal(0.0), fs, qreal(1.0));
266 fv = qBound(qreal(0.01), fv, qreal(1.0));
267 values = QVector4D(fh, fs, fv, 0);
268
269 return needsBlending;
270}
271
272void WGMyPaintShadeSelector::pickColorAt(const QPointF &posF)
273{
274 QPoint pos = (posF * devicePixelRatioF()).toPoint();
275 QVector4D values, dummy;
276 getChannelValues(pos, values, dummy);
277 m_allowUpdates = false;
278 Q_EMIT sigChannelValuesChanged(values);
279 m_allowUpdates = true;
280}
281
283{
284 m_widthHD = qMax(1, width()) * devicePixelRatioF();
285 m_heightHD = qMax(1, height()) *devicePixelRatioF();
287}
288
290{
291 if (m_allowUpdates) {
292 m_colorH = values.x();
293 m_colorS = values.y();
294 m_colorV = values.z();
295 update();
296 }
297}
qreal v
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
int sqr(int x)
int signedSqr(int x)
qreal sqr2(qreal x)
void setColorWithIterator(Iterator &it, const KoColor &color, const int pixelSize)
QImage toQImage(KisPaintDeviceSP srcDevice, bool proofPaintColors=false) const
virtual void clear()
const KoColorSpace * colorSpace() const
void bitBlt(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight)
ALWAYS_INLINE int x() const
ALWAYS_INLINE int y() const
void setOpacity(quint8 alpha)
Definition KoColor.cpp:333
quint8 * data()
Definition KoColor.h:144
bool getChannelValues(QPoint pos, QVector4D &values, QVector4D &blendValues)
KisVisualColorModelSP m_model
void paintEvent(QPaintEvent *) override
void mousePressEvent(QMouseEvent *event) override
void setModel(KisVisualColorModelSP model) override
KisPaintDeviceSP m_realCircleBorder
void pickColorAt(const QPointF &posF)
void slotSetChannelValues(const QVector4D &values)
void mouseReleaseEvent(QMouseEvent *event) override
void mouseMoveEvent(QMouseEvent *event) override
WGMyPaintShadeSelector(WGSelectorDisplayConfigSP displayConfig, QWidget *parent, UiMode mode)
void resizeEvent(QResizeEvent *event) override
const KisDisplayColorConverter * displayConverter() const
void sigChannelValuesChanged(const QVector4D &values)
WGSelectorDisplayConfigSP displayConfiguration() const
void sigColorInteraction(bool active)
#define M_PI
Definition kis_global.h:111
int signedSqr(int x)
qreal sqr2(qreal x)