File indexing completed on 2024-05-26 04:32:50
0001 /* 0002 * SPDX-FileCopyrightText: 2021 Mathias Wein <lynx.mw+kde@gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-3.0-or-later 0005 * 0006 * Code based on kis_my_paint_shade_selector.cpp from Advanced Color Selector, 0007 * which in turn is based on "lib/colorchanger_crossed_bowl.hpp" from MyPaint (mypaint.org), 0008 * 0009 * SPDX-FileCopyrightText: 2010 Adam Celarek <kdedev at xibo dot at> 0010 * SPDX-FileCopyrightText: 2008 Martin Renold <martinxyz@gmx.ch> 0011 * SPDX-FileCopyrightText: 2009 Ilya Portnov <nomail> 0012 */ 0013 0014 #include "WGMyPaintShadeSelector.h" 0015 0016 #include <kis_display_color_converter.h> 0017 #include <kis_paint_device.h> 0018 #include <kis_painter.h> 0019 #include <kis_sequential_iterator.h> 0020 0021 #include <QMouseEvent> 0022 #include <QPainter> 0023 #include <QVector4D> 0024 0025 #include <cmath> 0026 0027 template<class Iterator> 0028 void setColorWithIterator(Iterator &it, const KoColor &color, const int pixelSize) { 0029 memcpy(it.rawData(), color.data(), pixelSize); 0030 } 0031 0032 inline int sqr(int x) { 0033 return x*x; 0034 } 0035 0036 inline qreal sqr2(qreal x) { 0037 return (x*x + x)*0.5; 0038 } 0039 0040 inline int signedSqr(int x) { 0041 return (x > 0) ? x*x : -(x*x); 0042 } 0043 0044 WGMyPaintShadeSelector::WGMyPaintShadeSelector(WGSelectorDisplayConfigSP displayConfig, QWidget *parent, UiMode mode) 0045 : WGSelectorWidgetBase(displayConfig, parent, mode) 0046 { 0047 recalculateSizeHD(); 0048 } 0049 0050 WGMyPaintShadeSelector::~WGMyPaintShadeSelector() 0051 { 0052 0053 } 0054 0055 void WGMyPaintShadeSelector::setModel(KisVisualColorModelSP model) 0056 { 0057 if (m_model) { 0058 disconnect(m_model.data()); 0059 m_model->disconnect(this); 0060 } 0061 m_model = model; 0062 connect(this, SIGNAL(sigChannelValuesChanged(QVector4D)), 0063 m_model.data(), SLOT(slotSetChannelValues(QVector4D))); 0064 connect(m_model.data(), SIGNAL(sigChannelValuesChanged(QVector4D,quint32)), 0065 this, SLOT(slotSetChannelValues(QVector4D))); 0066 if (m_model->isHSXModel()) { 0067 slotSetChannelValues(m_model->channelValues()); 0068 } 0069 } 0070 0071 void WGMyPaintShadeSelector::mousePressEvent(QMouseEvent *event) 0072 { 0073 if (event->button() == Qt::LeftButton) { 0074 emit sigColorInteraction(true); 0075 pickColorAt(event->localPos()); 0076 } else { 0077 event->ignore(); 0078 } 0079 } 0080 0081 void WGMyPaintShadeSelector::mouseMoveEvent(QMouseEvent *event) 0082 { 0083 if (event->buttons() & Qt::LeftButton) { 0084 if (rect().contains(event->pos())) { 0085 pickColorAt(event->localPos()); 0086 } 0087 } else { 0088 event->ignore(); 0089 } 0090 } 0091 0092 void WGMyPaintShadeSelector::mouseReleaseEvent(QMouseEvent *event) 0093 { 0094 if (event->button() == Qt::LeftButton) { 0095 emit sigColorInteraction(false); 0096 } else { 0097 event->ignore(); 0098 } 0099 } 0100 0101 void WGMyPaintShadeSelector::paintEvent(QPaintEvent *) 0102 { 0103 // Hint to the casual reader: some of the calculation here do not 0104 // what Martin Renold originally intended. Not everything here will make sense. 0105 // It does not matter in the end, as long as the result looks good. 0106 if (!m_model || !m_model->isHSXModel()) { 0107 return; 0108 } 0109 0110 // This selector was ported from MyPaint in 2010 0111 if (!m_realPixelCache || m_realPixelCache->colorSpace() != m_model->colorSpace()) { 0112 m_realPixelCache = new KisPaintDevice(m_model->colorSpace()); 0113 m_realCircleBorder = new KisPaintDevice(m_model->colorSpace()); 0114 // m_cachedColorSpace = colorSpace(); 0115 } 0116 else { 0117 m_realPixelCache->clear(); 0118 m_realCircleBorder->clear(); 0119 } 0120 0121 const int pixelSize = m_model->colorSpace()->pixelSize(); 0122 0123 QRect pickRectHighDPI = QRect(QPoint(0, 0), size()*devicePixelRatioF()); 0124 KisSequentialIterator it(m_realPixelCache, pickRectHighDPI); 0125 KisSequentialIterator borderIt(m_realCircleBorder, pickRectHighDPI); 0126 QVector4D values; 0127 QVector4D values2; 0128 0129 while (it.nextPixel() && borderIt.nextPixel()) { 0130 const int x = it.x(); 0131 const int y = it.y(); 0132 0133 bool needsBlending = getChannelValues(QPoint(x, y), values, values2); 0134 0135 if (needsBlending) { 0136 const qreal aaFactor = static_cast<qreal>(values2[3]); 0137 KoColor color = m_model->convertChannelValuesToKoColor(values2); 0138 color.setOpacity(aaFactor); 0139 setColorWithIterator(borderIt, color, pixelSize); 0140 } 0141 0142 KoColor color = m_model->convertChannelValuesToKoColor(values); 0143 setColorWithIterator(it, color, pixelSize); 0144 } 0145 0146 KisPainter gc(m_realPixelCache); 0147 gc.bitBlt(QPoint(0,0), m_realCircleBorder, QRect(rect().topLeft(), rect().size()*devicePixelRatioF())); 0148 0149 QPainter painter(this); 0150 QImage renderedImage = displayConverter()->toQImage(m_realPixelCache, displayConfiguration()->previewInPaintingCS()); 0151 renderedImage.setDevicePixelRatio(devicePixelRatioF()); 0152 0153 painter.drawImage(0, 0, renderedImage); 0154 } 0155 0156 void WGMyPaintShadeSelector::resizeEvent(QResizeEvent *event) 0157 { 0158 WGSelectorWidgetBase::resizeEvent(event); 0159 recalculateSizeHD(); 0160 } 0161 0162 bool WGMyPaintShadeSelector::getChannelValues(QPoint pos, QVector4D &values, QVector4D &blendValues) 0163 { 0164 bool needsBlending = false; 0165 0166 const float v_factor = 0.6f; 0167 const float s_factor = 0.6f; 0168 const float v_factor2 = 0.013f; 0169 const float s_factor2 = 0.013f; 0170 0171 const int stripe_width = (15 * m_sizeHD)/255; 0172 int s_radiusHD = m_sizeHD/2.6; 0173 0174 float h = 0; 0175 float s = 0; 0176 float v = 0; 0177 0178 int dx = pos.x() - m_widthHD/2; 0179 int dy = pos.y() - m_heightHD/2; 0180 int diag = sqrt(2.0) * m_sizeHD/2; 0181 0182 int dxs, dys; 0183 if (dx > 0) 0184 dxs = dx - stripe_width; 0185 else 0186 dxs = dx + stripe_width; 0187 if (dy > 0) 0188 dys = dy - stripe_width; 0189 else 0190 dys = dy + stripe_width; 0191 0192 qreal r = std::sqrt(qreal(sqr(dxs)+sqr(dys))); 0193 0194 if (qMin(abs(dx), abs(dy)) < stripe_width) { 0195 // horizontal and vertical lines 0196 bool horizontal = std::abs(dx) > std::abs(dy); 0197 dx = (dx/qreal(m_sizeHD))*255; 0198 dy = (dy/qreal(m_sizeHD))*255; 0199 0200 h = 0; 0201 // x-axis = value, y-axis = saturation 0202 v = dx*v_factor + signedSqr(dx)*v_factor2; 0203 s = - (dy*s_factor + signedSqr(dy)*s_factor2); 0204 // but not both at once 0205 if (horizontal) { 0206 // horizontal stripe 0207 s = 0.0; 0208 } else { 0209 // vertical stripe 0210 v = 0.0; 0211 } 0212 } 0213 else if (std::min(std::abs(dx - dy), std::abs(dx + dy)) < stripe_width) { 0214 0215 dx = (dx/qreal(m_sizeHD))*255; 0216 dy = (dy/qreal(m_sizeHD))*255; 0217 0218 h = 0; 0219 // x-axis = value, y-axis = saturation 0220 v = dx*v_factor + signedSqr(dx)*v_factor2; 0221 s = - (dy*s_factor + signedSqr(dy)*s_factor2); 0222 // both at once 0223 } 0224 else if (r < s_radiusHD+1) { 0225 0226 // hue 0227 if (dx > 0) 0228 h = 90*sqr2(r/s_radiusHD); 0229 else 0230 h = 360 - 90*sqr2(r/s_radiusHD); 0231 s = 256*(atan2f(std::abs(dxs),dys)/M_PI) - 128; 0232 0233 if (r > s_radiusHD) { 0234 needsBlending = true; 0235 // antialiasing boarder 0236 qreal aaFactor = r-floor(r); // part after the decimal point 0237 aaFactor = 1-aaFactor; 0238 0239 qreal fh = m_colorH + h/360.0; 0240 qreal fs = m_colorS + s/255.0; 0241 qreal fv = m_colorV + v/255.0; 0242 0243 fh -= floor(fh); 0244 fs = qBound(qreal(0.0), fs, qreal(1.0)); 0245 fv = qBound(qreal(0.01), fv, qreal(1.0)); 0246 blendValues = QVector4D(fh, fs, fv, aaFactor); 0247 0248 h = 180 + 180*atan2f(dys,-dxs)/M_PI; 0249 v = 255*(r-s_radiusHD)/(diag-s_radiusHD) - 128; 0250 s = 0; // overwrite the s value that was meant for the inside of the circle 0251 // here we already have drawn the inside, and the value left should be just the background value 0252 } 0253 } 0254 else { 0255 // background (hue+darkness gradient) 0256 h = 180 + 180*atan2f(dys,-dxs)/M_PI; 0257 v = 255*(r-s_radiusHD)/(diag-s_radiusHD) - 128; 0258 } 0259 0260 qreal fh = m_colorH + h/360.0; 0261 qreal fs = m_colorS + s/255.0; 0262 qreal fv = m_colorV + v/255.0; 0263 0264 fh -= floor(fh); 0265 fs = qBound(qreal(0.0), fs, qreal(1.0)); 0266 fv = qBound(qreal(0.01), fv, qreal(1.0)); 0267 values = QVector4D(fh, fs, fv, 0); 0268 0269 return needsBlending; 0270 } 0271 0272 void WGMyPaintShadeSelector::pickColorAt(const QPointF &posF) 0273 { 0274 QPoint pos = (posF * devicePixelRatioF()).toPoint(); 0275 QVector4D values, dummy; 0276 getChannelValues(pos, values, dummy); 0277 m_allowUpdates = false; 0278 emit sigChannelValuesChanged(values); 0279 m_allowUpdates = true; 0280 } 0281 0282 void WGMyPaintShadeSelector::recalculateSizeHD() 0283 { 0284 m_widthHD = qMax(1, width()) * devicePixelRatioF(); 0285 m_heightHD = qMax(1, height()) *devicePixelRatioF(); 0286 m_sizeHD = qMin(m_widthHD, m_heightHD); 0287 } 0288 0289 void WGMyPaintShadeSelector::slotSetChannelValues(const QVector4D &values) 0290 { 0291 if (m_allowUpdates) { 0292 m_colorH = values.x(); 0293 m_colorS = values.y(); 0294 m_colorV = values.z(); 0295 update(); 0296 } 0297 }