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}
55
59
61 return m_d->currentCoordinates;
62}
63
64void KisVisualColorSelectorShape::setCursorPosition(QPointF position, bool signal)
65{
66 QPointF newPos(qBound(0.0, position.x(), 1.0), qBound(0.0, position.y(), 1.0));
67 if (newPos != m_d->currentCoordinates)
68 {
69 m_d->currentCoordinates = newPos;
70 // for internal consistency, because we have a bit of redundancy here
71 m_d->currentChannelValues[m_d->channel1] = newPos.x();
72 if (m_d->dimension == Dimensions::twodimensional){
73 m_d->currentChannelValues[m_d->channel2] = newPos.y();
74 }
75 update();
76 if (signal){
77 Q_EMIT sigCursorMoved(newPos);
78 }
79 }
80}
81
82void KisVisualColorSelectorShape::setChannelValues(QVector4D channelValues, quint32 channelFlags)
83{
84 //qDebug() << this << "setChannelValues";
85 m_d->currentChannelValues = channelValues;
86 bool setCursor = channelFlags & m_d->channelMask;
87 if (setCursor) {
88 m_d->currentCoordinates = QPointF(qBound(0.f, channelValues[m_d->channel1], 1.f),
89 qBound(0.f, channelValues[m_d->channel2], 1.f));
90 }
91 else {
92 // for internal consistency, because we have a bit of redundancy here
93 m_d->currentChannelValues[m_d->channel1] = m_d->currentCoordinates.x();
94 if (m_d->dimension == Dimensions::twodimensional){
95 m_d->currentChannelValues[m_d->channel2] = m_d->currentCoordinates.y();
96 }
97 }
98 m_d->imagesNeedUpdate = m_d->imagesNeedUpdate || channelFlags & ~m_d->channelMask;
99 update();
100}
101
103{
104 m_d->acceptTabletEvents = on;
105}
106
113
115{
116 return false;
117}
118
120{
121 //qDebug() << this << "forceImageUpdate";
122 m_d->alphaNeedsUpdate = true;
123 m_d->imagesNeedUpdate = true;
124}
125
127{
128 // Nothing to do if gamut masks not supported
129}
130
132{
134 return renderer->toQColor(c, colorSelector()->proofColors());
135}
136
138{
139 KisVisualColorSelector* selectorWidget = qobject_cast<KisVisualColorSelector*>(parent());
140 KIS_ASSERT(selectorWidget);
141 return selectorWidget;
142}
143
145{
146 KisVisualColorSelector* selectorWidget = qobject_cast<KisVisualColorSelector*>(parent());
147 KIS_ASSERT(selectorWidget);
148 return selectorWidget->selectorModel().data();
149}
150
152{
153 //qDebug() << this << ">>>>>>>>> getImageMap()" << m_d->imagesNeedUpdate;
154
155 if (m_d->imagesNeedUpdate) {
156 // NOTE: pure static backgrounds are currently somewhat implicitly handled,
157 // it would be nicer to avoid re-checking and overwriting m_d->gradient.
158 // But QImage's implicit data sharing allows all this mindless by-value stuff...
159 m_d->gradient = compositeBackground();
160 m_d->imagesNeedUpdate = false;
161 }
162 return m_d->gradient;
163}
164
165QImage KisVisualColorSelectorShape::convertImageMap(const quint8 *rawColor, quint32 bufferSize, QSize imgSize) const
166{
167 const KoColorSpace *colorSpace = selectorModel()->colorSpace();
168 Q_ASSERT(bufferSize == imgSize.width() * imgSize.height() * colorSpace->pixelSize());
170
171 // Convert the buffer to a qimage
172 QImage image = renderer->toQImage(colorSpace, rawColor, imgSize, colorSelector()->proofColors());
173
174 // safeguard:
175 if (image.isNull())
176 {
177 image = QImage(width(), height(), QImage::Format_ARGB32);
178 image.fill(Qt::black);
179 }
180
181 return image;
182}
183
184QImage KisVisualColorSelectorShape::renderBackground(const QVector4D &channelValues, const QImage &alpha) const
185{
186 const KisVisualColorModel *selector = selectorModel();
187 Q_ASSERT(selector);
188
189 // Hi-DPI aware rendering requires that we determine the device pixel dimension;
190 // actual widget size in device pixels is not accessible unfortunately, it might be 1px smaller...
191 const qreal deviceDivider = 1.0 / devicePixelRatioF();
192 const int deviceWidth = qCeil(width() * devicePixelRatioF());
193 const int deviceHeight = qCeil(height() * devicePixelRatioF());
194 quint32 imageSize = deviceWidth * deviceHeight * selector->colorSpace()->pixelSize();
195 QScopedArrayPointer<quint8> raw(new quint8[imageSize] {});
196 quint8 *dataPtr = raw.data();
197 QVector4D coordinates = channelValues;
198 const qsizetype pixelSize = selector->colorSpace()->pixelSize();
199
200 bool checkAlpha = !alpha.isNull() && alpha.valid(deviceWidth - 1, deviceHeight - 1);
201 KIS_SAFE_ASSERT_RECOVER(!checkAlpha || alpha.format() == QImage::Format_Alpha8) {
202 checkAlpha = false;
203 }
204
205 KoColor filler(Qt::white, selector->colorSpace());
206 for (int y = 0; y < deviceHeight; y++) {
207 const uchar *alphaLine = checkAlpha ? alpha.scanLine(y) : 0;
208 for (int x=0; x < deviceWidth; x++) {
209 if (!checkAlpha || alphaLine[x]) {
210 QPointF newcoordinate = convertWidgetCoordinateToShapeCoordinate(QPointF(x, y) * deviceDivider);
211 coordinates[m_d->channel1] = newcoordinate.x();
212 if (m_d->dimension == Dimensions::twodimensional) {
213 coordinates[m_d->channel2] = newcoordinate.y();
214 }
215 KoColor c = selector->convertChannelValuesToKoColor(coordinates);
216 memcpy(dataPtr, c.data(), pixelSize);
217 }
218 else {
219 // need to write a color with non-zero alpha, otherwise the display converter
220 // will for some arcane reason crop the final QImage and screw rendering
221 memcpy(dataPtr, filler.data(), pixelSize);
222 }
223 dataPtr += pixelSize;
224 }
225 }
226 QImage image = convertImageMap(raw.data(), imageSize, QSize(deviceWidth, deviceHeight));
227 image.setDevicePixelRatio(devicePixelRatioF());
228
229 if (!alpha.isNull()) {
230 QPainter painter(&image);
231 // transfer alphaMask to Alpha channel
232 painter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
233 painter.drawImage(0, 0, alpha);
234 }
235
236 return image;
237}
238
240{
241 // Shapes are expect to return a valid alpha mask or a valid
242 // static alpha mask. If they provide both, the rendered backgrounds
243 // get composited.
244 if (m_d->alphaNeedsUpdate) {
245 QImage staticAlpha = renderStaticAlphaMask();
246 if (!staticAlpha.isNull()) {
247 QVector4D neutralValues(1, 1, 1, 1);
248 switch (selectorModel()->colorModel()) {
252 neutralValues.setZ(0.5f);
253 default:
254 break;
255 }
256
257 m_d->staticBackground = renderBackground(neutralValues, staticAlpha);
258 }
259 m_d->alphaMask = renderAlphaMask();
260 m_d->alphaNeedsUpdate = false;
261 }
262 if (m_d->alphaMask.isNull()) {
263 return m_d->staticBackground;
264 }
265
266 QImage bgImage = renderBackground(m_d->currentChannelValues, m_d->alphaMask);
267 if (!m_d->staticBackground.isNull()) {
268 QPainter painter(&bgImage);
269 // composite static and dynamic background parts
270 painter.setCompositionMode(QPainter::CompositionMode_DestinationOver);
271 painter.drawImage(0, 0, m_d->staticBackground);
272 }
273 return bgImage;
274}
275
277{
278 return QImage();
279}
280
282{
283 return QImage();
284}
285
286QPointF KisVisualColorSelectorShape::mousePositionToShapeCoordinate(const QPointF &pos, const QPointF &dragStart) const
287{
288 Q_UNUSED(dragStart);
290}
291
293{
294 if (e->button() == Qt::LeftButton) {
295 m_d->dragStart = e->localPos();
296 Q_EMIT colorSelector()->sigInteraction(true);
297 QPointF coordinates = mousePositionToShapeCoordinate(e->localPos(), m_d->dragStart);
298 setCursorPosition(coordinates, true);
299 }
300 else {
301 e->ignore();
302 }
303}
304
306{
307 if (e->buttons() & Qt::LeftButton) {
308 QPointF coordinates = mousePositionToShapeCoordinate(e->localPos(), m_d->dragStart);
309 setCursorPosition(coordinates, true);
310 } else {
311 e->ignore();
312 }
313}
314
316{
317 if (e->button() == Qt::LeftButton) {
318 Q_EMIT colorSelector()->sigInteraction(false);
319 } else {
320 e->ignore();
321 }
322}
323
325{
326 // only accept tablet events that are associated to "left" button
327 // NOTE: QTabletEvent does not have a windowPos() equivalent, but we don't need it
328 if (m_d->acceptTabletEvents &&
329 (event->button() == Qt::LeftButton || (event->buttons() & Qt::LeftButton)))
330 {
331 event->accept();
332 switch (event->type()) {
333 case QEvent::TabletPress: {
334 QMouseEvent mouseEvent(QEvent::MouseButtonPress, event->posF(), event->posF(),
335 event->globalPosF(), event->button(), event->buttons(),
336 event->modifiers(), Qt::MouseEventSynthesizedByApplication);
337 mousePressEvent(&mouseEvent);
338 break;
339 }
340 case QEvent::TabletMove: {
341 QMouseEvent mouseEvent(QEvent::MouseMove, event->posF(), event->posF(),
342 event->globalPosF(), event->button(), event->buttons(),
343 event->modifiers(), Qt::MouseEventSynthesizedByApplication);
344 mouseMoveEvent(&mouseEvent);
345 break;
346 }
347 case QEvent::TabletRelease: {
348 QMouseEvent mouseEvent(QEvent::MouseButtonRelease, event->posF(), event->posF(),
349 event->globalPosF(), event->button(), event->buttons(),
350 event->modifiers(), Qt::MouseEventSynthesizedByApplication);
351 mouseReleaseEvent(&mouseEvent);
352 break;
353 }
354 default:
355 event->ignore();
356 }
357 }
358}
359
361{
362 QPainter painter(this);
363
364 const QImage &fullSelector = getImageMap();
365 if (!fullSelector.isNull()) {
366 painter.drawImage(0, 0, fullSelector);
367 }
368
369 drawGamutMask(painter);
370
371 if (isEnabled()) {
372 painter.setRenderHint(QPainter::Antialiasing);
373 drawCursor(painter);
374 }
375}
376
378{
381 setMask(getMaskMap());
382}
383
385{
386 // Nothing to do if gamut masks not supported
387 Q_UNUSED(painter);
388}
389
394
396{
397 const KisVisualColorModel *selector = selectorModel();
398 if (selector)
399 {
400 return selector->convertChannelValuesToKoColor(m_d->currentChannelValues);
401 }
402 return KoColor();
403}
404
406{
407 if (dimension == 0) {
408 return m_d->channel1;
409 }
410 if (dimension == 1 && getDimensions() == twodimensional) {
411 return m_d->channel2;
412 }
413 return -1;
414}
415
417{
418 return m_d->channelMask;
419}
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