File indexing completed on 2024-12-22 04:15:04
0001 /* 0002 * SPDX-FileCopyrightText: 2020 Mathias Wein <lynx.mw+kde@gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-3.0-or-later 0005 */ 0006 0007 #include "WGShadeSlider.h" 0008 0009 #include "KoColorDisplayRendererInterface.h" 0010 0011 #include <QImage> 0012 #include <QMouseEvent> 0013 #include <QPainter> 0014 #include <QVector4D> 0015 #include <QtMath> 0016 0017 struct WGShadeSlider::Private 0018 { 0019 Private() {} 0020 QImage background; 0021 QVector4D range; 0022 QVector4D offset; 0023 QVector4D baseValues; 0024 qreal handleValue {0}; 0025 qreal leftStart {-1}; 0026 qreal leftEnd {0}; 0027 qreal rightStart {0}; 0028 qreal rightEnd {-1}; 0029 KisVisualColorModelSP selectorModel; 0030 WGSelectorDisplayConfigSP displayConfig; 0031 int cursorWidth {11}; 0032 int lineWidth {1}; 0033 int numPatches {9}; 0034 bool widgetSizeOk {false}; 0035 bool sliderMode {true}; 0036 bool imageNeedsUpdate {true}; 0037 }; 0038 0039 WGShadeSlider::WGShadeSlider(WGSelectorDisplayConfigSP config, QWidget *parent, KisVisualColorModelSP model) 0040 : QWidget(parent) 0041 , m_d(new Private) 0042 { 0043 m_d->selectorModel = model; 0044 m_d->displayConfig = config; 0045 recalculateParameters(); 0046 connect(config.data(), &WGSelectorDisplayConfig::sigDisplayConfigurationChanged, 0047 this, &WGShadeSlider::slotDisplayConfigurationChanged); 0048 } 0049 0050 WGShadeSlider::~WGShadeSlider() 0051 {} 0052 0053 void WGShadeSlider::setGradient(const QVector4D &range, const QVector4D &offset) 0054 { 0055 m_d->range = range; 0056 m_d->offset = offset; 0057 m_d->imageNeedsUpdate = true; 0058 resetHandle(); 0059 } 0060 0061 void WGShadeSlider::setDisplayMode(bool slider, int numPatches) 0062 { 0063 if (slider != m_d->sliderMode || 0064 (!slider && numPatches != m_d->numPatches)) { 0065 m_d->sliderMode = slider; 0066 if (!slider && numPatches > 2) { 0067 m_d->numPatches = numPatches; 0068 } 0069 m_d->widgetSizeOk = sizeRequirementsMet(); 0070 m_d->imageNeedsUpdate = true; 0071 resetHandle(); 0072 } 0073 } 0074 0075 void WGShadeSlider::setModel(KisVisualColorModelSP model) 0076 { 0077 m_d->selectorModel = model; 0078 m_d->imageNeedsUpdate = true; 0079 update(); 0080 } 0081 0082 QVector4D WGShadeSlider::channelValues() const 0083 { 0084 return calculateChannelValues(m_d->handleValue); 0085 } 0086 0087 const QImage *WGShadeSlider::background() 0088 { 0089 if (m_d->imageNeedsUpdate) { 0090 m_d->background = renderBackground(); 0091 m_d->imageNeedsUpdate = false; 0092 } 0093 return &m_d->background; 0094 } 0095 0096 QSize WGShadeSlider::minimumSizeHint() const 0097 { 0098 return QSize(50, 8); 0099 } 0100 0101 void WGShadeSlider::mousePressEvent(QMouseEvent *event) 0102 { 0103 if (event->button() == Qt::LeftButton) { 0104 emit sigInteraction(true); 0105 if (adjustHandleValue(event->localPos())) { 0106 emit sigChannelValuesChanged(channelValues()); 0107 update(); 0108 } 0109 } else { 0110 event->ignore(); 0111 } 0112 } 0113 0114 void WGShadeSlider::mouseMoveEvent(QMouseEvent *event) 0115 { 0116 if (event->buttons() & Qt::LeftButton) { 0117 if (adjustHandleValue(event->localPos())) { 0118 emit sigChannelValuesChanged(channelValues()); 0119 update(); 0120 } 0121 } else { 0122 event->ignore(); 0123 } 0124 } 0125 0126 void WGShadeSlider::mouseReleaseEvent(QMouseEvent *event) 0127 { 0128 if (event->button() == Qt::LeftButton) { 0129 emit sigInteraction(false); 0130 } else { 0131 event->ignore(); 0132 } 0133 } 0134 0135 void WGShadeSlider::paintEvent(QPaintEvent*) 0136 { 0137 if (m_d->imageNeedsUpdate) { 0138 m_d->background = renderBackground(); 0139 m_d->imageNeedsUpdate = false; 0140 } 0141 QPainter painter(this); 0142 painter.drawImage(0, 0, m_d->background); 0143 painter.scale(1.0/devicePixelRatioF(), 1.0/devicePixelRatioF()); 0144 QRectF handleRect; 0145 if (m_d->sliderMode) { 0146 QPointF sliderPos = convertSliderValueToWidgetCoordinate(m_d->handleValue); 0147 int sliderX = qRound(sliderPos.x()); 0148 handleRect = QRectF(sliderX - m_d->cursorWidth/2, 0, m_d->cursorWidth, height()); 0149 } else if (m_d->handleValue >= 0) { 0150 handleRect = patchRect(m_d->handleValue); 0151 } 0152 if (handleRect.isValid()) { 0153 QPen pen(QColor(175,175,175), m_d->lineWidth, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin); 0154 painter.setPen(pen); 0155 strokeRect(painter, handleRect, devicePixelRatioF(), 0); 0156 pen.setColor(QColor(75,75,75)); 0157 painter.setPen(pen); 0158 strokeRect(painter, handleRect, devicePixelRatioF(), 1); 0159 } 0160 } 0161 0162 void WGShadeSlider::resizeEvent(QResizeEvent *) 0163 { 0164 recalculateParameters(); 0165 } 0166 0167 void WGShadeSlider::slotSetChannelValues(const QVector4D &values) 0168 { 0169 m_d->baseValues = values; 0170 m_d->imageNeedsUpdate = true; 0171 resetHandle(); 0172 } 0173 0174 void WGShadeSlider::resetHandle() 0175 { 0176 m_d->handleValue = m_d->sliderMode ? 0 : -1; 0177 update(); 0178 } 0179 0180 void WGShadeSlider::slotDisplayConfigurationChanged() 0181 { 0182 m_d->imageNeedsUpdate = true; 0183 update(); 0184 } 0185 0186 bool WGShadeSlider::adjustHandleValue(const QPointF &widgetPos) 0187 { 0188 if (!m_d->widgetSizeOk) { 0189 return false; 0190 } 0191 0192 if (m_d->sliderMode) { 0193 qreal sliderPos = convertWidgetCoordinateToSliderValue(widgetPos); 0194 if (!qFuzzyIsNull(m_d->handleValue - sliderPos)) { 0195 m_d->handleValue = sliderPos; 0196 return true; 0197 } 0198 } else { 0199 int patchNum = getPatch(widgetPos); 0200 if (patchNum >= 0 && patchNum != (int)m_d->handleValue) { 0201 m_d->handleValue = patchNum; 0202 return true; 0203 } 0204 } 0205 return false; 0206 } 0207 0208 QPointF WGShadeSlider::convertSliderValueToWidgetCoordinate(qreal value) 0209 { 0210 QPointF pos(0.0, 0.0); 0211 if (value < 0) { 0212 pos.setX(m_d->leftStart - value * (m_d->leftEnd - m_d->leftStart)); 0213 } 0214 else if (value > 0) { 0215 pos.setX(m_d->rightStart + value * (m_d->rightEnd - m_d->rightStart)); 0216 } 0217 else { 0218 pos.setX((width() - 1) / 2); 0219 } 0220 return pos; 0221 } 0222 0223 qreal WGShadeSlider::convertWidgetCoordinateToSliderValue(QPointF coordinate) 0224 { 0225 qreal x = coordinate.x(); 0226 if (x < m_d->leftEnd) { 0227 return -1.0; 0228 } 0229 else if (x < m_d->leftStart) { 0230 return (m_d->leftStart - x) / (m_d->leftEnd - m_d->leftStart); 0231 } 0232 else if (x < m_d->rightStart) { 0233 return 0.0; 0234 } 0235 else if (x < m_d->rightEnd) { 0236 return (x - m_d->rightStart) / (m_d->rightEnd - m_d->rightStart); 0237 } 0238 return 1.0; 0239 } 0240 0241 QVector4D WGShadeSlider::calculateChannelValues(qreal sliderPos) const 0242 { 0243 float delta = 0.0f; 0244 if (m_d->sliderMode) { 0245 delta = (float)sliderPos; 0246 } else if (sliderPos >= 0 || m_d->numPatches > 1) { 0247 delta = 2.0f * float(sliderPos)/(m_d->numPatches - 1.0f) - 1.0f; 0248 } 0249 0250 QVector4D channelVec = m_d->baseValues + m_d->offset + delta * m_d->range; 0251 // Hue wraps around 0252 if (m_d->selectorModel->isHSXModel()) { 0253 channelVec[0] = (float)fmod(channelVec[0], 1.0); 0254 if (channelVec[0] < 0) { 0255 channelVec[0] += 1.f; 0256 } 0257 } 0258 else { 0259 channelVec[0] = qBound(0.f, channelVec[0], 1.f); 0260 } 0261 0262 for (int i = 1; i < 3; i++) { 0263 channelVec[i] = qBound(0.f, channelVec[i], 1.f); 0264 } 0265 return channelVec; 0266 } 0267 0268 int WGShadeSlider::getPatch(const QPointF pos) const 0269 { 0270 int patch = m_d->numPatches * pos.x() / width(); 0271 if (patch >= 0 && patch < m_d->numPatches) { 0272 return patch; 0273 } 0274 return -1; 0275 } 0276 0277 QRectF WGShadeSlider::patchRect(int index) const 0278 { 0279 qreal patchWidth = width() / qreal(m_d->numPatches); 0280 qreal margin = 1.5; 0281 QPointF topLeft(index * patchWidth + margin, 0); 0282 QPointF bottomRight((index+1) * patchWidth - margin, height()); 0283 return QRectF(topLeft, bottomRight); 0284 } 0285 0286 void WGShadeSlider::recalculateParameters() 0287 { 0288 int center = (width() - 1) / 2; 0289 int halfCursor = m_d->cursorWidth / 2; 0290 0291 m_d->leftEnd = halfCursor; 0292 m_d->leftStart = center - halfCursor; 0293 0294 m_d->rightStart = center + halfCursor; 0295 m_d->rightEnd = 2 * center - halfCursor; 0296 0297 m_d->lineWidth = qRound(devicePixelRatioF() - 0.1); 0298 m_d->widgetSizeOk = sizeRequirementsMet(); 0299 m_d->imageNeedsUpdate = true; 0300 } 0301 0302 bool WGShadeSlider::sizeRequirementsMet() const 0303 { 0304 if (m_d->sliderMode) { 0305 return m_d->leftStart - m_d->leftEnd > 0 && m_d->rightEnd - m_d->rightStart > 0; 0306 } else { 0307 return width() > m_d->numPatches; 0308 } 0309 } 0310 0311 QImage WGShadeSlider::renderBackground() 0312 { 0313 if (!m_d->widgetSizeOk || !m_d->selectorModel || !m_d->selectorModel->colorSpace()) { 0314 return QImage(); 0315 } 0316 0317 // Hi-DPI aware rendering requires that we determine the device pixel dimension; 0318 // actual widget size in device pixels is not accessible unfortunately, it might be 1px smaller... 0319 const qreal deviceDivider = 1.0 / devicePixelRatioF(); 0320 const int deviceWidth = qCeil(width() * devicePixelRatioF()); 0321 const int deviceHeight = qCeil(height() * devicePixelRatioF()); 0322 if (m_d->sliderMode) { 0323 const KoColorSpace *currentCS = m_d->selectorModel->colorSpace(); 0324 const quint32 pixelSize = currentCS->pixelSize(); 0325 quint32 imageSize = deviceWidth * pixelSize; 0326 QScopedArrayPointer<quint8> raw(new quint8[imageSize] {}); 0327 quint8 *dataPtr = raw.data(); 0328 0329 for (int x = 0; x < deviceWidth; x++, dataPtr += pixelSize) { 0330 qreal sliderVal = convertWidgetCoordinateToSliderValue(QPointF(x, 0) * deviceDivider); 0331 QVector4D coordinates = calculateChannelValues(sliderVal); 0332 KoColor c = m_d->selectorModel->convertChannelValuesToKoColor(coordinates); 0333 memcpy(dataPtr, c.data(), pixelSize); 0334 } 0335 0336 QImage image = m_d->displayConfig->displayConverter()->toQImage(currentCS, raw.data(), {deviceWidth, 1}, 0337 m_d->displayConfig->previewInPaintingCS()); 0338 image = image.scaled(QSize(deviceWidth, deviceHeight)); 0339 0340 QPainter painter(&image); 0341 QPen pen(QColor(175,175,175), m_d->lineWidth, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin); 0342 painter.setPen(pen); 0343 strokeRect(painter, QRectF(m_d->leftStart, 0, m_d->cursorWidth, height()), devicePixelRatioF(), 0); 0344 pen.setColor(QColor(75,75,75)); 0345 painter.setPen(pen); 0346 strokeRect(painter, QRectF(m_d->leftStart, 0, m_d->cursorWidth, height()), devicePixelRatioF(), 1); 0347 0348 image.setDevicePixelRatio(devicePixelRatioF()); 0349 return image; 0350 } else { 0351 QImage image(deviceWidth, deviceHeight, QImage::Format_ARGB32); 0352 image.fill(Qt::transparent); 0353 image.setDevicePixelRatio(devicePixelRatioF()); 0354 QPainter painter(&image); 0355 painter.setPen(Qt::NoPen); 0356 0357 for (int i = 0; i < m_d->numPatches; i++) { 0358 QVector4D values = calculateChannelValues(i); 0359 KoColor col = m_d->selectorModel->convertChannelValuesToKoColor(values); 0360 QColor qCol = m_d->displayConfig->displayConverter()->toQColor(col, m_d->displayConfig->previewInPaintingCS()); 0361 painter.setBrush(qCol); 0362 painter.drawRect(patchRect(i)); 0363 } 0364 return image; 0365 } 0366 } 0367 0368 void WGShadeSlider::strokeRect(QPainter &painter, const QRectF &rect, qreal pixelSize, qreal shrinkX) 0369 { 0370 qreal lineWidth = painter.pen().widthF(); 0371 QPointF topLeft(qRound(rect.left() * pixelSize) + (shrinkX + 0.5) * lineWidth, 0372 qRound(rect.top() * pixelSize) + 0.5 * lineWidth); 0373 QPointF bottomRight(qRound(rect.right() * pixelSize) - (shrinkX + 0.5) * lineWidth, 0374 qRound(rect.bottom() * pixelSize) - 0.5 * lineWidth); 0375 painter.drawRect(QRectF(topLeft, bottomRight)); 0376 }