File indexing completed on 2024-05-12 16:02:05
0001 /* 0002 * SPDX-FileCopyrightText: 2016 Wolthera van Hovell tot Westerflier <griffinvalley@gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 #include "KisVisualColorSelectorShape.h" 0007 0008 #include <QColor> 0009 #include <QImage> 0010 #include <QPainter> 0011 #include <QRect> 0012 #include <QVector> 0013 #include <QVector4D> 0014 #include <QVBoxLayout> 0015 #include <QList> 0016 #include <QtMath> 0017 0018 #include <KSharedConfig> 0019 #include <KConfigGroup> 0020 0021 #include "KoColorConversions.h" 0022 #include "KoColorDisplayRendererInterface.h" 0023 #include "KoChannelInfo.h" 0024 #include <KoColorModelStandardIds.h> 0025 #include <QPointer> 0026 0027 #include "kis_debug.h" 0028 0029 struct KisVisualColorSelectorShape::Private 0030 { 0031 QImage gradient; 0032 QImage alphaMask; 0033 QImage fullSelector; 0034 bool imagesNeedUpdate { true }; 0035 bool alphaNeedsUpdate { true }; 0036 bool acceptTabletEvents { false }; 0037 QPointF currentCoordinates; // somewhat redundant? 0038 QPointF dragStart; 0039 QVector4D currentChannelValues; 0040 Dimensions dimension; 0041 const KoColorSpace *colorSpace; 0042 int channel1; 0043 int channel2; 0044 const KoColorDisplayRendererInterface *displayRenderer = 0; 0045 }; 0046 0047 KisVisualColorSelectorShape::KisVisualColorSelectorShape(QWidget *parent, 0048 KisVisualColorSelectorShape::Dimensions dimension, 0049 const KoColorSpace *cs, 0050 int channel1, 0051 int channel2, 0052 const KoColorDisplayRendererInterface *displayRenderer): QWidget(parent), m_d(new Private) 0053 { 0054 m_d->dimension = dimension; 0055 m_d->colorSpace = cs; 0056 int maxchannel = m_d->colorSpace->colorChannelCount()-1; 0057 m_d->channel1 = qBound(0, channel1, maxchannel); 0058 m_d->channel2 = qBound(0, channel2, maxchannel); 0059 this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); 0060 setDisplayRenderer(displayRenderer); 0061 } 0062 0063 KisVisualColorSelectorShape::~KisVisualColorSelectorShape() 0064 { 0065 } 0066 0067 QPointF KisVisualColorSelectorShape::getCursorPosition() { 0068 return m_d->currentCoordinates; 0069 } 0070 0071 void KisVisualColorSelectorShape::setCursorPosition(QPointF position, bool signal) 0072 { 0073 QPointF newPos(qBound(0.0, position.x(), 1.0), qBound(0.0, position.y(), 1.0)); 0074 if (newPos != m_d->currentCoordinates) 0075 { 0076 m_d->currentCoordinates = newPos; 0077 // for internal consistency, because we have a bit of redundancy here 0078 m_d->currentChannelValues[m_d->channel1] = newPos.x(); 0079 if (m_d->dimension == Dimensions::twodimensional){ 0080 m_d->currentChannelValues[m_d->channel2] = newPos.y(); 0081 } 0082 update(); 0083 if (signal){ 0084 emit sigCursorMoved(newPos); 0085 } 0086 } 0087 } 0088 0089 void KisVisualColorSelectorShape::setChannelValues(QVector4D channelValues, bool setCursor) 0090 { 0091 //qDebug() << this << "setChannelValues"; 0092 m_d->currentChannelValues = channelValues; 0093 if (setCursor) { 0094 m_d->currentCoordinates = QPointF(qBound(0.f, channelValues[m_d->channel1], 1.f), 0095 qBound(0.f, channelValues[m_d->channel2], 1.f)); 0096 } 0097 else { 0098 // for internal consistency, because we have a bit of redundancy here 0099 m_d->currentChannelValues[m_d->channel1] = m_d->currentCoordinates.x(); 0100 if (m_d->dimension == Dimensions::twodimensional){ 0101 m_d->currentChannelValues[m_d->channel2] = m_d->currentCoordinates.y(); 0102 } 0103 } 0104 m_d->imagesNeedUpdate = true; 0105 update(); 0106 } 0107 0108 void KisVisualColorSelectorShape::setAcceptTabletEvents(bool on) 0109 { 0110 m_d->acceptTabletEvents = on; 0111 } 0112 0113 void KisVisualColorSelectorShape::setDisplayRenderer (const KoColorDisplayRendererInterface *displayRenderer) 0114 { 0115 if (displayRenderer) { 0116 m_d->displayRenderer = displayRenderer; 0117 } else { 0118 m_d->displayRenderer = KoDumbColorDisplayRenderer::instance(); 0119 } 0120 } 0121 0122 void KisVisualColorSelectorShape::forceImageUpdate() 0123 { 0124 //qDebug() << this << "forceImageUpdate"; 0125 m_d->alphaNeedsUpdate = true; 0126 m_d->imagesNeedUpdate = true; 0127 } 0128 0129 QColor KisVisualColorSelectorShape::getColorFromConverter(KoColor c){ 0130 QColor col; 0131 KoColor color = c; 0132 if (m_d->displayRenderer) { 0133 color.convertTo(m_d->displayRenderer->getPaintingColorSpace()); 0134 col = m_d->displayRenderer->toQColor(c); 0135 } else { 0136 col = c.toQColor(); 0137 } 0138 return col; 0139 } 0140 0141 // currently unused? 0142 void KisVisualColorSelectorShape::slotSetActiveChannels(int channel1, int channel2) 0143 { 0144 //qDebug() << this << "slotSetActiveChannels"; 0145 int maxchannel = m_d->colorSpace->colorChannelCount()-1; 0146 m_d->channel1 = qBound(0, channel1, maxchannel); 0147 m_d->channel2 = qBound(0, channel2, maxchannel); 0148 m_d->imagesNeedUpdate = true; 0149 update(); 0150 } 0151 0152 bool KisVisualColorSelectorShape::imagesNeedUpdate() const { 0153 return m_d->imagesNeedUpdate; 0154 } 0155 0156 QImage KisVisualColorSelectorShape::getImageMap() 0157 { 0158 //qDebug() << this << ">>>>>>>>> getImageMap()" << m_d->imagesNeedUpdate; 0159 0160 if (m_d->imagesNeedUpdate) { 0161 // Fill a buffer with the right kocolors 0162 m_d->gradient = renderBackground(m_d->currentChannelValues, m_d->colorSpace->pixelSize()); 0163 m_d->imagesNeedUpdate = false; 0164 } 0165 return m_d->gradient; 0166 } 0167 0168 const QImage KisVisualColorSelectorShape::getAlphaMask() const 0169 { 0170 if (m_d->alphaNeedsUpdate) { 0171 m_d->alphaMask = renderAlphaMask(); 0172 m_d->alphaNeedsUpdate = false; 0173 } 0174 return m_d->alphaMask; 0175 } 0176 0177 QImage KisVisualColorSelectorShape::convertImageMap(const quint8 *rawColor, quint32 bufferSize, QSize imgSize) const 0178 { 0179 Q_ASSERT(bufferSize == imgSize.width() * imgSize.height() * m_d->colorSpace->pixelSize()); 0180 QImage image; 0181 // Convert the buffer to a qimage 0182 if (m_d->displayRenderer) { 0183 image = m_d->displayRenderer->convertToQImage(m_d->colorSpace, rawColor, imgSize.width(), imgSize.height()); 0184 } 0185 else { 0186 image = m_d->colorSpace->convertToQImage(rawColor, imgSize.width(), imgSize.height(), 0, 0187 KoColorConversionTransformation::internalRenderingIntent(), 0188 KoColorConversionTransformation::internalConversionFlags()); 0189 } 0190 // safeguard: 0191 if (image.isNull()) 0192 { 0193 image = QImage(width(), height(), QImage::Format_ARGB32); 0194 image.fill(Qt::black); 0195 } 0196 0197 return image; 0198 } 0199 0200 QImage KisVisualColorSelectorShape::renderBackground(const QVector4D &channelValues, quint32 pixelSize) const 0201 { 0202 const KisVisualColorSelector *selector = qobject_cast<KisVisualColorSelector*>(parent()); 0203 Q_ASSERT(selector); 0204 0205 // Hi-DPI aware rendering requires that we determine the device pixel dimension; 0206 // actual widget size in device pixels is not accessible unfortunately, it might be 1px smaller... 0207 const qreal deviceDivider = 1.0 / devicePixelRatioF(); 0208 const int deviceWidth = qCeil(width() * devicePixelRatioF()); 0209 const int deviceHeight = qCeil(height() * devicePixelRatioF()); 0210 quint32 imageSize = deviceWidth * deviceHeight * m_d->colorSpace->pixelSize(); 0211 QScopedArrayPointer<quint8> raw(new quint8[imageSize] {}); 0212 quint8 *dataPtr = raw.data(); 0213 QVector4D coordinates = channelValues; 0214 0215 QImage alpha = getAlphaMask(); 0216 bool checkAlpha = !alpha.isNull() && alpha.valid(deviceWidth - 1, deviceHeight - 1); 0217 KIS_SAFE_ASSERT_RECOVER(!checkAlpha || alpha.format() == QImage::Format_Alpha8) { 0218 checkAlpha = false; 0219 } 0220 0221 KoColor filler(Qt::white, m_d->colorSpace); 0222 for (int y = 0; y < deviceHeight; y++) { 0223 const uchar *alphaLine = checkAlpha ? alpha.scanLine(y) : 0; 0224 for (int x=0; x < deviceWidth; x++) { 0225 if (!checkAlpha || alphaLine[x]) { 0226 QPointF newcoordinate = convertWidgetCoordinateToShapeCoordinate(QPointF(x, y) * deviceDivider); 0227 coordinates[m_d->channel1] = newcoordinate.x(); 0228 if (m_d->dimension == Dimensions::twodimensional) { 0229 coordinates[m_d->channel2] = newcoordinate.y(); 0230 } 0231 KoColor c = selector->convertShapeCoordsToKoColor(coordinates); 0232 memcpy(dataPtr, c.data(), pixelSize); 0233 } 0234 else { 0235 // need to write a color with non-zero alpha, otherwise the display converter 0236 // will for some arcane reason crop the final QImage and screw rendering 0237 memcpy(dataPtr, filler.data(), pixelSize); 0238 } 0239 dataPtr += pixelSize; 0240 } 0241 } 0242 QImage image = convertImageMap(raw.data(), imageSize, QSize(deviceWidth, deviceHeight)); 0243 image.setDevicePixelRatio(devicePixelRatioF()); 0244 0245 if (!alpha.isNull()) { 0246 QPainter painter(&image); 0247 // transfer alphaMask to Alpha channel 0248 painter.setCompositionMode(QPainter::CompositionMode_DestinationIn); 0249 painter.drawImage(0, 0, alpha); 0250 } 0251 0252 return image; 0253 } 0254 0255 QImage KisVisualColorSelectorShape::renderAlphaMask() const 0256 { 0257 return QImage(); 0258 } 0259 0260 QPointF KisVisualColorSelectorShape::mousePositionToShapeCoordinate(const QPointF &pos, const QPointF &dragStart) const 0261 { 0262 Q_UNUSED(dragStart); 0263 return convertWidgetCoordinateToShapeCoordinate(pos); 0264 } 0265 0266 void KisVisualColorSelectorShape::mousePressEvent(QMouseEvent *e) 0267 { 0268 if (e->button() == Qt::LeftButton) { 0269 m_d->dragStart = e->localPos(); 0270 QPointF coordinates = mousePositionToShapeCoordinate(e->localPos(), m_d->dragStart); 0271 setCursorPosition(coordinates, true); 0272 } 0273 else { 0274 e->ignore(); 0275 } 0276 } 0277 0278 void KisVisualColorSelectorShape::mouseMoveEvent(QMouseEvent *e) 0279 { 0280 if (e->buttons() & Qt::LeftButton) { 0281 QPointF coordinates = mousePositionToShapeCoordinate(e->localPos(), m_d->dragStart); 0282 setCursorPosition(coordinates, true); 0283 } else { 0284 e->ignore(); 0285 } 0286 } 0287 0288 void KisVisualColorSelectorShape::mouseReleaseEvent(QMouseEvent *e) 0289 { 0290 if (e->button() != Qt::LeftButton) { 0291 e->ignore(); 0292 } 0293 } 0294 0295 void KisVisualColorSelectorShape::tabletEvent(QTabletEvent* event) 0296 { 0297 // only accept tablet events that are associated to "left" button 0298 // NOTE: QTabletEvent does not have a windowPos() equivalent, but we don't need it 0299 if (m_d->acceptTabletEvents && 0300 (event->button() == Qt::LeftButton || (event->buttons() & Qt::LeftButton))) 0301 { 0302 event->accept(); 0303 switch (event->type()) { 0304 case QEvent::TabletPress: { 0305 QMouseEvent mouseEvent(QEvent::MouseButtonPress, event->posF(), event->posF(), 0306 event->globalPosF(), event->button(), event->buttons(), 0307 event->modifiers(), Qt::MouseEventSynthesizedByApplication); 0308 mousePressEvent(&mouseEvent); 0309 break; 0310 } 0311 case QEvent::TabletMove: { 0312 QMouseEvent mouseEvent(QEvent::MouseMove, event->posF(), event->posF(), 0313 event->globalPosF(), event->button(), event->buttons(), 0314 event->modifiers(), Qt::MouseEventSynthesizedByApplication); 0315 mouseMoveEvent(&mouseEvent); 0316 break; 0317 } 0318 case QEvent::TabletRelease: { 0319 QMouseEvent mouseEvent(QEvent::MouseButtonRelease, event->posF(), event->posF(), 0320 event->globalPosF(), event->button(), event->buttons(), 0321 event->modifiers(), Qt::MouseEventSynthesizedByApplication); 0322 mouseReleaseEvent(&mouseEvent); 0323 break; 0324 } 0325 default: 0326 event->ignore(); 0327 } 0328 } 0329 } 0330 0331 void KisVisualColorSelectorShape::paintEvent(QPaintEvent*) 0332 { 0333 QPainter painter(this); 0334 0335 drawCursor(); 0336 painter.drawImage(0,0,m_d->fullSelector); 0337 } 0338 0339 void KisVisualColorSelectorShape::resizeEvent(QResizeEvent *) 0340 { 0341 forceImageUpdate(); 0342 setMask(getMaskMap()); 0343 } 0344 0345 KisVisualColorSelectorShape::Dimensions KisVisualColorSelectorShape::getDimensions() const 0346 { 0347 return m_d->dimension; 0348 } 0349 0350 void KisVisualColorSelectorShape::setFullImage(QImage full) 0351 { 0352 m_d->fullSelector = full; 0353 } 0354 KoColor KisVisualColorSelectorShape::getCurrentColor() 0355 { 0356 const KisVisualColorSelector *selector = qobject_cast<KisVisualColorSelector*>(parent()); 0357 if (selector) 0358 { 0359 return selector->convertShapeCoordsToKoColor(m_d->currentChannelValues); 0360 } 0361 return KoColor(m_d->colorSpace); 0362 } 0363 0364 QVector <int> KisVisualColorSelectorShape::getChannels() const 0365 { 0366 QVector <int> channels(2); 0367 channels[0] = m_d->channel1; 0368 channels[1] = m_d->channel2; 0369 return channels; 0370 }