1 /*
2 * Copyright (C) Wolthera van Hovell tot Westerflier <griffinvalley@gmail.com>, (C) 2016
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 */
18 #include "KisVisualColorSelectorShape.h"
19
20 #include <QColor>
21 #include <QImage>
22 #include <QPainter>
23 #include <QRect>
24 #include <QVector>
25 #include <QVector4D>
26 #include <QVBoxLayout>
27 #include <QList>
28 #include <QtMath>
29
30 #include <KSharedConfig>
31 #include <KConfigGroup>
32
33 #include "KoColorConversions.h"
34 #include "KoColorDisplayRendererInterface.h"
35 #include "KoChannelInfo.h"
36 #include <KoColorModelStandardIds.h>
37 #include <QPointer>
38
39 #include "kis_debug.h"
40
41 struct KisVisualColorSelectorShape::Private
42 {
43 QImage gradient;
44 QImage alphaMask;
45 QImage fullSelector;
46 bool imagesNeedUpdate { true };
47 bool alphaNeedsUpdate { true };
48 bool acceptTabletEvents { false };
49 QPointF currentCoordinates; // somewhat redundant?
50 QPointF dragStart;
51 QVector4D currentChannelValues;
52 Dimensions dimension;
53 const KoColorSpace *colorSpace;
54 int channel1;
55 int channel2;
56 const KoColorDisplayRendererInterface *displayRenderer = 0;
57 };
58
KisVisualColorSelectorShape(QWidget * parent,KisVisualColorSelectorShape::Dimensions dimension,const KoColorSpace * cs,int channel1,int channel2,const KoColorDisplayRendererInterface * displayRenderer)59 KisVisualColorSelectorShape::KisVisualColorSelectorShape(QWidget *parent,
60 KisVisualColorSelectorShape::Dimensions dimension,
61 const KoColorSpace *cs,
62 int channel1,
63 int channel2,
64 const KoColorDisplayRendererInterface *displayRenderer): QWidget(parent), m_d(new Private)
65 {
66 m_d->dimension = dimension;
67 m_d->colorSpace = cs;
68 int maxchannel = m_d->colorSpace->colorChannelCount()-1;
69 m_d->channel1 = qBound(0, channel1, maxchannel);
70 m_d->channel2 = qBound(0, channel2, maxchannel);
71 this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
72 setDisplayRenderer(displayRenderer);
73 }
74
~KisVisualColorSelectorShape()75 KisVisualColorSelectorShape::~KisVisualColorSelectorShape()
76 {
77 }
78
getCursorPosition()79 QPointF KisVisualColorSelectorShape::getCursorPosition() {
80 return m_d->currentCoordinates;
81 }
82
setCursorPosition(QPointF position,bool signal)83 void KisVisualColorSelectorShape::setCursorPosition(QPointF position, bool signal)
84 {
85 QPointF newPos(qBound(0.0, position.x(), 1.0), qBound(0.0, position.y(), 1.0));
86 if (newPos != m_d->currentCoordinates)
87 {
88 m_d->currentCoordinates = newPos;
89 // for internal consistency, because we have a bit of redundancy here
90 m_d->currentChannelValues[m_d->channel1] = newPos.x();
91 if (m_d->dimension == Dimensions::twodimensional){
92 m_d->currentChannelValues[m_d->channel2] = newPos.y();
93 }
94 update();
95 if (signal){
96 emit sigCursorMoved(newPos);
97 }
98 }
99 }
100
setChannelValues(QVector4D channelValues,bool setCursor)101 void KisVisualColorSelectorShape::setChannelValues(QVector4D channelValues, bool setCursor)
102 {
103 //qDebug() << this << "setChannelValues";
104 m_d->currentChannelValues = channelValues;
105 if (setCursor) {
106 m_d->currentCoordinates = QPointF(qBound(0.f, channelValues[m_d->channel1], 1.f),
107 qBound(0.f, channelValues[m_d->channel2], 1.f));
108 }
109 else {
110 // for internal consistency, because we have a bit of redundancy here
111 m_d->currentChannelValues[m_d->channel1] = m_d->currentCoordinates.x();
112 if (m_d->dimension == Dimensions::twodimensional){
113 m_d->currentChannelValues[m_d->channel2] = m_d->currentCoordinates.y();
114 }
115 }
116 m_d->imagesNeedUpdate = true;
117 update();
118 }
119
setAcceptTabletEvents(bool on)120 void KisVisualColorSelectorShape::setAcceptTabletEvents(bool on)
121 {
122 m_d->acceptTabletEvents = on;
123 }
124
setDisplayRenderer(const KoColorDisplayRendererInterface * displayRenderer)125 void KisVisualColorSelectorShape::setDisplayRenderer (const KoColorDisplayRendererInterface *displayRenderer)
126 {
127 if (displayRenderer) {
128 m_d->displayRenderer = displayRenderer;
129 } else {
130 m_d->displayRenderer = KoDumbColorDisplayRenderer::instance();
131 }
132 }
133
forceImageUpdate()134 void KisVisualColorSelectorShape::forceImageUpdate()
135 {
136 //qDebug() << this << "forceImageUpdate";
137 m_d->alphaNeedsUpdate = true;
138 m_d->imagesNeedUpdate = true;
139 }
140
getColorFromConverter(KoColor c)141 QColor KisVisualColorSelectorShape::getColorFromConverter(KoColor c){
142 QColor col;
143 KoColor color = c;
144 if (m_d->displayRenderer) {
145 color.convertTo(m_d->displayRenderer->getPaintingColorSpace());
146 col = m_d->displayRenderer->toQColor(c);
147 } else {
148 col = c.toQColor();
149 }
150 return col;
151 }
152
153 // currently unused?
slotSetActiveChannels(int channel1,int channel2)154 void KisVisualColorSelectorShape::slotSetActiveChannels(int channel1, int channel2)
155 {
156 //qDebug() << this << "slotSetActiveChannels";
157 int maxchannel = m_d->colorSpace->colorChannelCount()-1;
158 m_d->channel1 = qBound(0, channel1, maxchannel);
159 m_d->channel2 = qBound(0, channel2, maxchannel);
160 m_d->imagesNeedUpdate = true;
161 update();
162 }
163
imagesNeedUpdate() const164 bool KisVisualColorSelectorShape::imagesNeedUpdate() const {
165 return m_d->imagesNeedUpdate;
166 }
167
getImageMap()168 QImage KisVisualColorSelectorShape::getImageMap()
169 {
170 //qDebug() << this << ">>>>>>>>> getImageMap()" << m_d->imagesNeedUpdate;
171
172 if (m_d->imagesNeedUpdate) {
173 // Fill a buffer with the right kocolors
174 m_d->gradient = renderBackground(m_d->currentChannelValues, m_d->colorSpace->pixelSize());
175 m_d->imagesNeedUpdate = false;
176 }
177 return m_d->gradient;
178 }
179
getAlphaMask() const180 const QImage KisVisualColorSelectorShape::getAlphaMask() const
181 {
182 if (m_d->alphaNeedsUpdate) {
183 m_d->alphaMask = renderAlphaMask();
184 m_d->alphaNeedsUpdate = false;
185 }
186 return m_d->alphaMask;
187 }
188
convertImageMap(const quint8 * rawColor,quint32 bufferSize,QSize imgSize) const189 QImage KisVisualColorSelectorShape::convertImageMap(const quint8 *rawColor, quint32 bufferSize, QSize imgSize) const
190 {
191 Q_ASSERT(bufferSize == imgSize.width() * imgSize.height() * m_d->colorSpace->pixelSize());
192 QImage image;
193 // Convert the buffer to a qimage
194 if (m_d->displayRenderer) {
195 image = m_d->displayRenderer->convertToQImage(m_d->colorSpace, rawColor, imgSize.width(), imgSize.height());
196 }
197 else {
198 image = m_d->colorSpace->convertToQImage(rawColor, imgSize.width(), imgSize.height(), 0,
199 KoColorConversionTransformation::internalRenderingIntent(),
200 KoColorConversionTransformation::internalConversionFlags());
201 }
202 // safeguard:
203 if (image.isNull())
204 {
205 image = QImage(width(), height(), QImage::Format_ARGB32);
206 image.fill(Qt::black);
207 }
208
209 return image;
210 }
211
renderBackground(const QVector4D & channelValues,quint32 pixelSize) const212 QImage KisVisualColorSelectorShape::renderBackground(const QVector4D &channelValues, quint32 pixelSize) const
213 {
214 const KisVisualColorSelector *selector = qobject_cast<KisVisualColorSelector*>(parent());
215 Q_ASSERT(selector);
216
217 // Hi-DPI aware rendering requires that we determine the device pixel dimension;
218 // actual widget size in device pixels is not accessible unfortunately, it might be 1px smaller...
219 const qreal deviceDivider = 1.0 / devicePixelRatioF();
220 const int deviceWidth = qCeil(width() * devicePixelRatioF());
221 const int deviceHeight = qCeil(height() * devicePixelRatioF());
222 quint32 imageSize = deviceWidth * deviceHeight * m_d->colorSpace->pixelSize();
223 QScopedArrayPointer<quint8> raw(new quint8[imageSize] {});
224 quint8 *dataPtr = raw.data();
225 QVector4D coordinates = channelValues;
226
227 QImage alpha = getAlphaMask();
228 bool checkAlpha = !alpha.isNull() && alpha.valid(deviceWidth - 1, deviceHeight - 1);
229 KIS_SAFE_ASSERT_RECOVER(!checkAlpha || alpha.format() == QImage::Format_Alpha8) {
230 checkAlpha = false;
231 }
232
233 KoColor filler(Qt::white, m_d->colorSpace);
234 for (int y = 0; y < deviceHeight; y++) {
235 const uchar *alphaLine = checkAlpha ? alpha.scanLine(y) : 0;
236 for (int x=0; x < deviceWidth; x++) {
237 if (!checkAlpha || alphaLine[x]) {
238 QPointF newcoordinate = convertWidgetCoordinateToShapeCoordinate(QPointF(x, y) * deviceDivider);
239 coordinates[m_d->channel1] = newcoordinate.x();
240 if (m_d->dimension == Dimensions::twodimensional) {
241 coordinates[m_d->channel2] = newcoordinate.y();
242 }
243 KoColor c = selector->convertShapeCoordsToKoColor(coordinates);
244 memcpy(dataPtr, c.data(), pixelSize);
245 }
246 else {
247 // need to write a color with non-zero alpha, otherwise the display converter
248 // will for some arcane reason crop the final QImage and screw rendering
249 memcpy(dataPtr, filler.data(), pixelSize);
250 }
251 dataPtr += pixelSize;
252 }
253 }
254 QImage image = convertImageMap(raw.data(), imageSize, QSize(deviceWidth, deviceHeight));
255 image.setDevicePixelRatio(devicePixelRatioF());
256
257 if (!alpha.isNull()) {
258 QPainter painter(&image);
259 // transfer alphaMask to Alpha channel
260 painter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
261 painter.drawImage(0, 0, alpha);
262 }
263
264 return image;
265 }
266
renderAlphaMask() const267 QImage KisVisualColorSelectorShape::renderAlphaMask() const
268 {
269 return QImage();
270 }
271
mousePositionToShapeCoordinate(const QPointF & pos,const QPointF & dragStart) const272 QPointF KisVisualColorSelectorShape::mousePositionToShapeCoordinate(const QPointF &pos, const QPointF &dragStart) const
273 {
274 Q_UNUSED(dragStart)
275 return convertWidgetCoordinateToShapeCoordinate(pos);
276 }
277
mousePressEvent(QMouseEvent * e)278 void KisVisualColorSelectorShape::mousePressEvent(QMouseEvent *e)
279 {
280 if (e->button() == Qt::LeftButton) {
281 m_d->dragStart = e->localPos();
282 QPointF coordinates = mousePositionToShapeCoordinate(e->localPos(), m_d->dragStart);
283 setCursorPosition(coordinates, true);
284 }
285 else {
286 e->ignore();
287 }
288 }
289
mouseMoveEvent(QMouseEvent * e)290 void KisVisualColorSelectorShape::mouseMoveEvent(QMouseEvent *e)
291 {
292 if (e->buttons() & Qt::LeftButton) {
293 QPointF coordinates = mousePositionToShapeCoordinate(e->localPos(), m_d->dragStart);
294 setCursorPosition(coordinates, true);
295 } else {
296 e->ignore();
297 }
298 }
299
mouseReleaseEvent(QMouseEvent * e)300 void KisVisualColorSelectorShape::mouseReleaseEvent(QMouseEvent *e)
301 {
302 if (e->button() != Qt::LeftButton) {
303 e->ignore();
304 }
305 }
306
tabletEvent(QTabletEvent * event)307 void KisVisualColorSelectorShape::tabletEvent(QTabletEvent* event)
308 {
309 // only accept tablet events that are associated to "left" button
310 // NOTE: QTabletEvent does not have a windowPos() equivalent, but we don't need it
311 if (m_d->acceptTabletEvents &&
312 (event->button() == Qt::LeftButton || (event->buttons() & Qt::LeftButton)))
313 {
314 event->accept();
315 switch (event->type()) {
316 case QEvent::TabletPress: {
317 QMouseEvent mouseEvent(QEvent::MouseButtonPress, event->posF(), event->posF(),
318 event->globalPosF(), event->button(), event->buttons(),
319 event->modifiers(), Qt::MouseEventSynthesizedByApplication);
320 mousePressEvent(&mouseEvent);
321 break;
322 }
323 case QEvent::TabletMove: {
324 QMouseEvent mouseEvent(QEvent::MouseMove, event->posF(), event->posF(),
325 event->globalPosF(), event->button(), event->buttons(),
326 event->modifiers(), Qt::MouseEventSynthesizedByApplication);
327 mouseMoveEvent(&mouseEvent);
328 break;
329 }
330 case QEvent::TabletRelease: {
331 QMouseEvent mouseEvent(QEvent::MouseButtonRelease, event->posF(), event->posF(),
332 event->globalPosF(), event->button(), event->buttons(),
333 event->modifiers(), Qt::MouseEventSynthesizedByApplication);
334 mouseReleaseEvent(&mouseEvent);
335 break;
336 }
337 default:
338 event->ignore();
339 }
340 }
341 }
342
paintEvent(QPaintEvent *)343 void KisVisualColorSelectorShape::paintEvent(QPaintEvent*)
344 {
345 QPainter painter(this);
346
347 drawCursor();
348 painter.drawImage(0,0,m_d->fullSelector);
349 }
350
resizeEvent(QResizeEvent *)351 void KisVisualColorSelectorShape::resizeEvent(QResizeEvent *)
352 {
353 forceImageUpdate();
354 setMask(getMaskMap());
355 }
356
getDimensions() const357 KisVisualColorSelectorShape::Dimensions KisVisualColorSelectorShape::getDimensions() const
358 {
359 return m_d->dimension;
360 }
361
setFullImage(QImage full)362 void KisVisualColorSelectorShape::setFullImage(QImage full)
363 {
364 m_d->fullSelector = full;
365 }
getCurrentColor()366 KoColor KisVisualColorSelectorShape::getCurrentColor()
367 {
368 const KisVisualColorSelector *selector = qobject_cast<KisVisualColorSelector*>(parent());
369 if (selector)
370 {
371 return selector->convertShapeCoordsToKoColor(m_d->currentChannelValues);
372 }
373 return KoColor(m_d->colorSpace);
374 }
375
getChannels() const376 QVector <int> KisVisualColorSelectorShape::getChannels() const
377 {
378 QVector <int> channels(2);
379 channels[0] = m_d->channel1;
380 channels[1] = m_d->channel2;
381 return channels;
382 }
383