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