Krita Source Code Documentation
Loading...
Searching...
No Matches
KisVisualColorSelectorShape.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2016 Wolthera van Hovell tot Westerflier <griffinvalley@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
7
8#include <QColor>
9#include <QImage>
10#include <QPainter>
11#include <QVector>
12#include <QVector4D>
13#include <QtMath>
14
15#include "KoColorConversions.h"
16#include "KoColorSpace.h"
18#include "KoChannelInfo.h"
20
21#include "kis_debug.h"
22
24{
25 QImage gradient;
26 QImage alphaMask;
28 bool imagesNeedUpdate { true };
29 bool alphaNeedsUpdate { true };
30 bool acceptTabletEvents { false };
31 QPointF currentCoordinates; // somewhat redundant?
32 QPointF dragStart;
37 quint32 channelMask;
38};
39
42 int channel1,
43 int channel2): QWidget(parent), m_d(new Private)
44{
45 m_d->dimension = dimension;
46 int maxchannel = parent->selectorModel()->colorSpace()->colorChannelCount()-1;
47 m_d->channel1 = qBound(0, channel1, maxchannel);
48 m_d->channel2 = qBound(0, channel2, maxchannel);
49 m_d->channelMask = 1 << channel1;
50 if (dimension == Dimensions::twodimensional) {
51 m_d->channelMask |= 1 << channel2;
52 }
53 this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
54 // Color selectors don't have context menus. Setting this prevents any
55 // long-presses from delaying inputs, see KisLongPressEventFilter.cpp.
56 setContextMenuPolicy(Qt::PreventContextMenu);
57}
58
62
64 return m_d->currentCoordinates;
65}
66
67void KisVisualColorSelectorShape::setCursorPosition(QPointF position, bool signal)
68{
69 QPointF newPos(qBound(0.0, position.x(), 1.0), qBound(0.0, position.y(), 1.0));
70 if (newPos != m_d->currentCoordinates)
71 {
72 m_d->currentCoordinates = newPos;
73 // for internal consistency, because we have a bit of redundancy here
74 m_d->currentChannelValues[m_d->channel1] = newPos.x();
75 if (m_d->dimension == Dimensions::twodimensional){
76 m_d->currentChannelValues[m_d->channel2] = newPos.y();
77 }
78 update();
79 if (signal){
80 Q_EMIT sigCursorMoved(newPos);
81 }
82 }
83}
84
85void KisVisualColorSelectorShape::setChannelValues(QVector4D channelValues, quint32 channelFlags)
86{
87 //qDebug() << this << "setChannelValues";
88 m_d->currentChannelValues = channelValues;
89 bool setCursor = channelFlags & m_d->channelMask;
90 if (setCursor) {
91 m_d->currentCoordinates = QPointF(qBound(0.f, channelValues[m_d->channel1], 1.f),
92 qBound(0.f, channelValues[m_d->channel2], 1.f));
93 }
94 else {
95 // for internal consistency, because we have a bit of redundancy here
96 m_d->currentChannelValues[m_d->channel1] = m_d->currentCoordinates.x();
97 if (m_d->dimension == Dimensions::twodimensional){
98 m_d->currentChannelValues[m_d->channel2] = m_d->currentCoordinates.y();
99 }
100 }
101 m_d->imagesNeedUpdate = m_d->imagesNeedUpdate || channelFlags & ~m_d->channelMask;
102 update();
103}
104
106{
107 m_d->acceptTabletEvents = on;
108}
109
116
118{
119 return false;
120}
121
123{
124 //qDebug() << this << "forceImageUpdate";
125 m_d->alphaNeedsUpdate = true;
126 m_d->imagesNeedUpdate = true;
127}
128
130{
131 // Nothing to do if gamut masks not supported
132}
133
135{
137 return renderer->toQColor(c, colorSelector()->proofColors());
138}
139
141{
142 KisVisualColorSelector* selectorWidget = qobject_cast<KisVisualColorSelector*>(parent());
143 KIS_ASSERT(selectorWidget);
144 return selectorWidget;
145}
146
148{
149 KisVisualColorSelector* selectorWidget = qobject_cast<KisVisualColorSelector*>(parent());
150 KIS_ASSERT(selectorWidget);
151 return selectorWidget->selectorModel().data();
152}
153
155{
156 //qDebug() << this << ">>>>>>>>> getImageMap()" << m_d->imagesNeedUpdate;
157
158 if (m_d->imagesNeedUpdate) {
159 // NOTE: pure static backgrounds are currently somewhat implicitly handled,
160 // it would be nicer to avoid re-checking and overwriting m_d->gradient.
161 // But QImage's implicit data sharing allows all this mindless by-value stuff...
162 m_d->gradient = compositeBackground();
163 m_d->imagesNeedUpdate = false;
164 }
165 return m_d->gradient;
166}
167
168QImage KisVisualColorSelectorShape::convertImageMap(const quint8 *rawColor, quint32 bufferSize, QSize imgSize) const
169{
170 const KoColorSpace *colorSpace = selectorModel()->colorSpace();
171 Q_ASSERT(bufferSize == imgSize.width() * imgSize.height() * colorSpace->pixelSize());
173
174 // Convert the buffer to a qimage
175 QImage image = renderer->toQImage(colorSpace, rawColor, imgSize, colorSelector()->proofColors());
176
177 // safeguard:
178 if (image.isNull())
179 {
180 image = QImage(width(), height(), QImage::Format_ARGB32);
181 image.fill(Qt::black);
182 }
183
184 return image;
185}
186
187QImage KisVisualColorSelectorShape::renderBackground(const QVector4D &channelValues, const QImage &alpha) const
188{
189 const KisVisualColorModel *selector = selectorModel();
190 Q_ASSERT(selector);
191
192 // Hi-DPI aware rendering requires that we determine the device pixel dimension;
193 // actual widget size in device pixels is not accessible unfortunately, it might be 1px smaller...
194 const qreal deviceDivider = 1.0 / devicePixelRatioF();
195 const int deviceWidth = qCeil(width() * devicePixelRatioF());
196 const int deviceHeight = qCeil(height() * devicePixelRatioF());
197 quint32 imageSize = deviceWidth * deviceHeight * selector->colorSpace()->pixelSize();
198 QScopedArrayPointer<quint8> raw(new quint8[imageSize] {});
199 quint8 *dataPtr = raw.data();
200 QVector4D coordinates = channelValues;
201 const qsizetype pixelSize = selector->colorSpace()->pixelSize();
202
203 bool checkAlpha = !alpha.isNull() && alpha.valid(deviceWidth - 1, deviceHeight - 1);
204 KIS_SAFE_ASSERT_RECOVER(!checkAlpha || alpha.format() == QImage::Format_Alpha8) {
205 checkAlpha = false;
206 }
207
208 KoColor filler(Qt::white, selector->colorSpace());
209 for (int y = 0; y < deviceHeight; y++) {
210 const uchar *alphaLine = checkAlpha ? alpha.scanLine(y) : 0;
211 for (int x=0; x < deviceWidth; x++) {
212 if (!checkAlpha || alphaLine[x]) {
213 QPointF newcoordinate = convertWidgetCoordinateToShapeCoordinate(QPointF(x, y) * deviceDivider);
214 coordinates[m_d->channel1] = newcoordinate.x();
215 if (m_d->dimension == Dimensions::twodimensional) {
216 coordinates[m_d->channel2] = newcoordinate.y();
217 }
218 KoColor c = selector->convertChannelValuesToKoColor(coordinates);
219 memcpy(dataPtr, c.data(), pixelSize);
220 }
221 else {
222 // need to write a color with non-zero alpha, otherwise the display converter
223 // will for some arcane reason crop the final QImage and screw rendering
224 memcpy(dataPtr, filler.data(), pixelSize);
225 }
226 dataPtr += pixelSize;
227 }
228 }
229 QImage image = convertImageMap(raw.data(), imageSize, QSize(deviceWidth, deviceHeight));
230 image.setDevicePixelRatio(devicePixelRatioF());
231
232 if (!alpha.isNull()) {
233 QPainter painter(&image);
234 // transfer alphaMask to Alpha channel
235 painter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
236 painter.drawImage(0, 0, alpha);
237 }
238
239 return image;
240}
241
243{
244 // Shapes are expect to return a valid alpha mask or a valid
245 // static alpha mask. If they provide both, the rendered backgrounds
246 // get composited.
247 if (m_d->alphaNeedsUpdate) {
248 QImage staticAlpha = renderStaticAlphaMask();
249 if (!staticAlpha.isNull()) {
250 QVector4D neutralValues(1, 1, 1, 1);
251 switch (selectorModel()->colorModel()) {
255 neutralValues.setZ(0.5f);
256 default:
257 break;
258 }
259
260 m_d->staticBackground = renderBackground(neutralValues, staticAlpha);
261 }
262 m_d->alphaMask = renderAlphaMask();
263 m_d->alphaNeedsUpdate = false;
264 }
265 if (m_d->alphaMask.isNull()) {
266 return m_d->staticBackground;
267 }
268
269 QImage bgImage = renderBackground(m_d->currentChannelValues, m_d->alphaMask);
270 if (!m_d->staticBackground.isNull()) {
271 QPainter painter(&bgImage);
272 // composite static and dynamic background parts
273 painter.setCompositionMode(QPainter::CompositionMode_DestinationOver);
274 painter.drawImage(0, 0, m_d->staticBackground);
275 }
276 return bgImage;
277}
278
280{
281 return QImage();
282}
283
285{
286 return QImage();
287}
288
289QPointF KisVisualColorSelectorShape::mousePositionToShapeCoordinate(const QPointF &pos, const QPointF &dragStart) const
290{
291 Q_UNUSED(dragStart);
293}
294
296{
297 if (e->button() == Qt::LeftButton) {
298 m_d->dragStart = e->localPos();
299 Q_EMIT colorSelector()->sigInteraction(true);
300 QPointF coordinates = mousePositionToShapeCoordinate(e->localPos(), m_d->dragStart);
301 setCursorPosition(coordinates, true);
302 }
303 else {
304 e->ignore();
305 }
306}
307
309{
310 if (e->buttons() & Qt::LeftButton) {
311 QPointF coordinates = mousePositionToShapeCoordinate(e->localPos(), m_d->dragStart);
312 setCursorPosition(coordinates, true);
313 } else {
314 e->ignore();
315 }
316}
317
319{
320 if (e->button() == Qt::LeftButton) {
321 Q_EMIT colorSelector()->sigInteraction(false);
322 } else {
323 e->ignore();
324 }
325}
326
328{
329 // only accept tablet events that are associated to "left" button
330 // NOTE: QTabletEvent does not have a windowPos() equivalent, but we don't need it
331 if (m_d->acceptTabletEvents &&
332 (event->button() == Qt::LeftButton || (event->buttons() & Qt::LeftButton)))
333 {
334 event->accept();
335 switch (event->type()) {
336 case QEvent::TabletPress: {
337 QMouseEvent mouseEvent(QEvent::MouseButtonPress, event->posF(), event->posF(),
338 event->globalPosF(), event->button(), event->buttons(),
339 event->modifiers(), Qt::MouseEventSynthesizedByApplication);
340 mousePressEvent(&mouseEvent);
341 break;
342 }
343 case QEvent::TabletMove: {
344 QMouseEvent mouseEvent(QEvent::MouseMove, event->posF(), event->posF(),
345 event->globalPosF(), event->button(), event->buttons(),
346 event->modifiers(), Qt::MouseEventSynthesizedByApplication);
347 mouseMoveEvent(&mouseEvent);
348 break;
349 }
350 case QEvent::TabletRelease: {
351 QMouseEvent mouseEvent(QEvent::MouseButtonRelease, event->posF(), event->posF(),
352 event->globalPosF(), event->button(), event->buttons(),
353 event->modifiers(), Qt::MouseEventSynthesizedByApplication);
354 mouseReleaseEvent(&mouseEvent);
355 break;
356 }
357 default:
358 event->ignore();
359 }
360 }
361}
362
364{
365 QPainter painter(this);
366
367 const QImage &fullSelector = getImageMap();
368 if (!fullSelector.isNull()) {
369 painter.drawImage(0, 0, fullSelector);
370 }
371
372 drawGamutMask(painter);
373
374 if (isEnabled()) {
375 painter.setRenderHint(QPainter::Antialiasing);
376 drawCursor(painter);
377 }
378}
379
381{
384 setMask(getMaskMap());
385}
386
388{
389 // Nothing to do if gamut masks not supported
390 Q_UNUSED(painter);
391}
392
397
399{
400 const KisVisualColorModel *selector = selectorModel();
401 if (selector)
402 {
403 return selector->convertChannelValuesToKoColor(m_d->currentChannelValues);
404 }
405 return KoColor();
406}
407
409{
410 if (dimension == 0) {
411 return m_d->channel1;
412 }
413 if (dimension == 1 && getDimensions() == twodimensional) {
414 return m_d->channel2;
415 }
416 return -1;
417}
418
420{
421 return m_d->channelMask;
422}
The KisVisualColorModel class allows manipulating a KoColor using various color models.
const KoColorSpace * colorSpace() const
KoColor convertChannelValuesToKoColor(const QVector4D &values) const
KisVisualColorModel * selectorModel() const
void setCursorPosition(QPointF position, bool signal=false)
setCursorPosition Set the cursor to normalized shape coordinates. This will only repaint the cursor.
void setChannelValues(QVector4D channelValues, quint32 channelFlags)
setChannelValues Set the current channel values; Note that channel values controlled by the shape its...
void mouseReleaseEvent(QMouseEvent *e) override
KisVisualColorSelector * colorSelector() const
void mouseMoveEvent(QMouseEvent *e) override
void forceImageUpdate()
forceImageUpdate force the image to recache.
virtual QPointF convertWidgetCoordinateToShapeCoordinate(QPointF coordinate) const =0
convertWidgetCoordinateToShapeCoordinate Convert a coordinate in the widget's height/width to a shape...
QPointF getCursorPosition() const
getCursorPosition
virtual void drawCursor(QPainter &painter)=0
int channel(int dimension) const
channel Get the channel index associated with a selector shape dimension
virtual QImage renderBackground(const QVector4D &channelValues, const QImage &alpha) const
renderBackground Render the widget background visible inside the widget's mask in current color space...
virtual QImage renderAlphaMask() const
render the alpha mask for the widget background the returned image is expected to be QImage::Format_A...
void mousePressEvent(QMouseEvent *e) override
virtual void updateGamutMask()
Notify shape that the gamut mask changed.
QColor getColorFromConverter(KoColor c)
getColorFromConverter
virtual void drawGamutMask(QPainter &painter)
void tabletEvent(QTabletEvent *event) override
virtual QRegion getMaskMap()=0
getPixmap
void sigCursorMoved(QPointF pos)
const QScopedPointer< Private > m_d
void paintEvent(QPaintEvent *) override
KisVisualColorSelectorShape(KisVisualColorSelector *parent, KisVisualColorSelectorShape::Dimensions dimension, int channel1, int channel2)
Dimensions getDimensions() const
getDimensions
virtual QPointF mousePositionToShapeCoordinate(const QPointF &pos, const QPointF &dragStart) const
default implementation just calls convertWidgetCoordinateToShapeCoordinate(pos)
QImage convertImageMap(const quint8 *rawColor, quint32 bufferSize, QSize imgSize) const
convertImageMap convert image data containing raw KoColor data into a QImage
Dimensions
The Dimensions enum Whether or not the shape is single or two dimensional.
const QImage & getImageMap()
getImageMap returns the updated base image
void resizeEvent(QResizeEvent *) override
The KisVisualColorSelector class.
void sigInteraction(bool active)
sigInteraction is emitted whenever mouse interaction status changes
const KoColorDisplayRendererInterface * displayRenderer() const
KisVisualColorModelSP selectorModel() const
virtual QColor toQColor(const KoColor &c, bool proofToPaintColors=false) const =0
virtual QImage toQImage(const KoColorSpace *srcColorSpace, const quint8 *data, QSize size, bool proofPaintColors=false) const =0
Convert a consecutive block of pixel data to an ARGB32 QImage.
virtual quint32 pixelSize() const =0
quint8 * data()
Definition KoColor.h:144
#define KIS_SAFE_ASSERT_RECOVER(cond)
Definition kis_assert.h:126
#define KIS_ASSERT(cond)
Definition kis_assert.h:33