File indexing completed on 2024-05-19 04:29:30
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 "KisVisualEllipticalSelectorShape.h" 0007 0008 #include <QColor> 0009 #include <QPainter> 0010 #include <QRect> 0011 #include <QLineF> 0012 #include <QtMath> 0013 0014 #include "kis_debug.h" 0015 #include "kis_global.h" 0016 0017 #include "resources/KoGamutMask.h" 0018 0019 #define KVESS_MARGIN 2 0020 0021 KisVisualEllipticalSelectorShape::KisVisualEllipticalSelectorShape(KisVisualColorSelector *parent, 0022 Dimensions dimension, 0023 int channel1, int channel2, 0024 int barWidth, 0025 singelDTypes d) 0026 : KisVisualColorSelectorShape(parent, dimension, channel1, channel2) 0027 { 0028 //qDebug() << "creating KisVisualEllipticalSelectorShape" << this; 0029 m_type = d; 0030 m_barWidth = barWidth; 0031 m_gamutMaskNeedsUpdate = (dimension == KisVisualColorSelectorShape::twodimensional); 0032 } 0033 0034 KisVisualEllipticalSelectorShape::~KisVisualEllipticalSelectorShape() 0035 { 0036 //qDebug() << "deleting KisVisualEllipticalSelectorShape" << this; 0037 } 0038 0039 void KisVisualEllipticalSelectorShape::setBorderWidth(int width) 0040 { 0041 m_barWidth = width; 0042 forceImageUpdate(); 0043 update(); 0044 } 0045 0046 QRect KisVisualEllipticalSelectorShape::getSpaceForSquare(QRect geom) 0047 { 0048 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(geom.contains(geometry()), geom); 0049 int sizeValue = qMin(width(), height()); 0050 QRectF b(0, 0, sizeValue, sizeValue); 0051 QLineF radius(b.center(), QPointF(b.left() + m_barWidth - 1, b.center().y()) ); 0052 radius.setAngle(135); 0053 QPointF tl(qFloor(radius.p2().x()), qFloor(radius.p2().y())); 0054 QPointF br = b.bottomRight() - tl; 0055 // QRect interprets bottomRight differently (unsuitable) for "historical reasons", 0056 // so construct a QRectF and convert to QRect 0057 QRect r = QRectF(tl, br).toRect(); 0058 r.translate(pos()); 0059 return r; 0060 } 0061 0062 QRect KisVisualEllipticalSelectorShape::getSpaceForCircle(QRect geom) 0063 { 0064 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(geom.contains(geometry()), geom); 0065 int sizeValue = qMin(width(), height()); 0066 QRect b(0, 0, sizeValue, sizeValue); 0067 QPointF tl = QPointF (b.topLeft().x()+m_barWidth, b.topLeft().y()+m_barWidth); 0068 QPointF br = QPointF (b.bottomRight().x()-m_barWidth, b.bottomRight().y()-m_barWidth); 0069 QRect r(tl.toPoint(), br.toPoint()); 0070 r.translate(pos()); 0071 return r; 0072 } 0073 0074 QRect KisVisualEllipticalSelectorShape::getSpaceForTriangle(QRect geom) 0075 { 0076 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(geom.contains(geometry()), geom); 0077 int sizeValue = qMin(width(), height()); 0078 QPointF center(0.5 * width(), 0.5 * height()); 0079 qreal radius = 0.5 * sizeValue - (m_barWidth + 4); 0080 QLineF rLine(center, QPointF(center.x() + radius, center.y())); 0081 rLine.setAngle(330); 0082 QPoint br(rLine.p2().toPoint()); 0083 QPoint tl(width() - br.x(), m_barWidth + 4); 0084 // can't use QRect(tl, br) constructor because it interprets br unsuitably for "historical reasons" 0085 QRect bound(tl, QSize(br.x() - tl.x(), br.y() - tl.y())); 0086 bound.adjust(-5, -5, 5, 5); 0087 bound.translate(pos()); 0088 return bound; 0089 } 0090 0091 bool KisVisualEllipticalSelectorShape::supportsGamutMask() const 0092 { 0093 return (getDimensions() == KisVisualColorSelectorShape::twodimensional); 0094 } 0095 0096 void KisVisualEllipticalSelectorShape::updateGamutMask() 0097 { 0098 if (supportsGamutMask()) { 0099 m_gamutMaskNeedsUpdate = true; 0100 KoGamutMask *mask = colorSelector()->activeGamutMask(); 0101 if (mask) { 0102 m_gamutMaskTransform = mask->viewToMaskTransform(width() - 2*KVESS_MARGIN); 0103 m_gamutMaskTransform.translate(-KVESS_MARGIN, -KVESS_MARGIN); 0104 } 0105 update(); 0106 } 0107 } 0108 0109 QPointF KisVisualEllipticalSelectorShape::convertShapeCoordinateToWidgetCoordinate(QPointF coordinate) const 0110 { 0111 qreal offset = 7.0; 0112 qreal a = (qreal)width()*0.5; 0113 QPointF center(a, a); 0114 QLineF line(center, QPoint((m_barWidth*0.5),a)); 0115 qreal angle = coordinate.x()*360.0; 0116 angle = 360.0 - fmod(angle+180.0, 360.0); 0117 if (m_type==KisVisualEllipticalSelectorShape::borderMirrored) { 0118 angle = (coordinate.x()/2)*360.0; 0119 angle = fmod((angle+270.0), 360.0); 0120 } 0121 line.setAngle(angle); 0122 if (getDimensions()!=KisVisualColorSelectorShape::onedimensional) { 0123 line.setLength(qMin(coordinate.y()*(a-offset), a-offset)); 0124 } 0125 return line.p2(); 0126 } 0127 0128 QPointF KisVisualEllipticalSelectorShape::convertWidgetCoordinateToShapeCoordinate(QPointF coordinate) const 0129 { 0130 //default implementation: 0131 qreal x = 0.5; 0132 qreal y = 1.0; 0133 qreal offset = 7.0; 0134 QPointF center = QRectF(QPointF(0.0, 0.0), this->size()).center(); 0135 qreal a = (qreal(this->width()) / qreal(2)); 0136 qreal xRel = center.x()-coordinate.x(); 0137 qreal yRel = center.y()-coordinate.y(); 0138 qreal radius = sqrt(xRel*xRel+yRel*yRel); 0139 0140 if (m_type!=KisVisualEllipticalSelectorShape::borderMirrored){ 0141 qreal angle = atan2(yRel, xRel); 0142 angle = kisRadiansToDegrees(angle); 0143 angle = fmod(angle+360, 360.0); 0144 x = angle/360.0; 0145 if (getDimensions()==KisVisualColorSelectorShape::twodimensional) { 0146 y = qBound(0.0,radius/(a-offset), 1.0); 0147 } 0148 0149 } else { 0150 qreal angle = atan2(xRel, yRel); 0151 angle = kisRadiansToDegrees(angle); 0152 angle = fmod(angle+180, 360.0); 0153 if (angle>180.0) { 0154 angle = 360.0-angle; 0155 } 0156 x = (angle/360.0)*2; 0157 if (getDimensions()==KisVisualColorSelectorShape::twodimensional) { 0158 y = qBound(0.0,(radius+offset)/a, 1.0); 0159 } 0160 } 0161 0162 return QPointF(x, y); 0163 } 0164 0165 QPointF KisVisualEllipticalSelectorShape::mousePositionToShapeCoordinate(const QPointF &pos, const QPointF &dragStart) const 0166 { 0167 QPointF pos2(pos); 0168 if (m_type == KisVisualEllipticalSelectorShape::borderMirrored) { 0169 qreal h_center = width()/2.0; 0170 bool start_left = dragStart.x() < h_center; 0171 bool cursor_left = pos.x() < h_center; 0172 if (start_left != cursor_left) { 0173 pos2.setX(h_center); 0174 } 0175 } 0176 else if (getDimensions() == KisVisualColorSelectorShape::twodimensional) { 0177 KoGamutMask *mask = colorSelector()->activeGamutMask(); 0178 if (mask) { 0179 QPointF maskPoint = m_gamutMaskTransform.map(pos); 0180 if (!mask->coordIsClear(maskPoint, true)) { 0181 // Ideally we try to find the closest point on the mask border, possibly 0182 // depending on dragStart. Currently just returns old position. 0183 return getCursorPosition(); 0184 } 0185 } 0186 } 0187 return convertWidgetCoordinateToShapeCoordinate(pos2); 0188 } 0189 0190 QRegion KisVisualEllipticalSelectorShape::getMaskMap() 0191 { 0192 QRegion mask = QRegion(0,0,width(),height(), QRegion::Ellipse); 0193 if (getDimensions()==KisVisualColorSelectorShape::onedimensional) { 0194 mask = mask.subtracted(QRegion(m_barWidth, m_barWidth, width()-(m_barWidth*2), height()-(m_barWidth*2), QRegion::Ellipse)); 0195 } 0196 return mask; 0197 } 0198 0199 QImage KisVisualEllipticalSelectorShape::renderAlphaMaskImpl(qreal outerBorder, qreal innerBorder) const 0200 { 0201 // Hi-DPI aware rendering requires that we determine the device pixel dimension; 0202 // actual widget size in device pixels is not accessible unfortunately, it might be 1px smaller... 0203 const int deviceWidth = qCeil(width() * devicePixelRatioF()); 0204 const int deviceHeight = qCeil(height() * devicePixelRatioF()); 0205 0206 QImage alphaMask(deviceWidth, deviceHeight, QImage::Format_Alpha8); 0207 alphaMask.fill(0); 0208 alphaMask.setDevicePixelRatio(devicePixelRatioF()); 0209 QPainter painter(&alphaMask); 0210 painter.setRenderHint(QPainter::Antialiasing); 0211 painter.setBrush(Qt::white); 0212 painter.setPen(Qt::NoPen); 0213 QRectF circle(outerBorder, outerBorder, width() - 2*outerBorder, height() - 2*outerBorder); 0214 painter.drawEllipse(circle); 0215 0216 //painter.setBrush(Qt::black); 0217 if (innerBorder > outerBorder) { 0218 circle = QRectF(innerBorder, innerBorder, width() - 2*innerBorder, height() - 2*innerBorder); 0219 painter.setCompositionMode(QPainter::CompositionMode_Clear); 0220 painter.drawEllipse(circle); 0221 } 0222 return alphaMask; 0223 } 0224 0225 QImage KisVisualEllipticalSelectorShape::renderAlphaMask() const 0226 { 0227 KisVisualColorSelector::RenderMode mode = colorSelector()->renderMode(); 0228 if (isHueControl() && mode == KisVisualColorSelector::StaticBackground) { 0229 return QImage(); 0230 } 0231 qreal outerBorder = KVESS_MARGIN; 0232 qreal innerBorder = -1; 0233 if (mode == KisVisualColorSelector::CompositeBackground && isHueControl()) { 0234 outerBorder += 0.25 * (m_barWidth - 4); 0235 } 0236 if (getDimensions() == KisVisualColorSelectorShape::onedimensional) { 0237 innerBorder = m_barWidth - 2; 0238 } 0239 return renderAlphaMaskImpl(outerBorder, innerBorder); 0240 } 0241 0242 QImage KisVisualEllipticalSelectorShape::renderStaticAlphaMask() const 0243 { 0244 KisVisualColorSelector::RenderMode mode = colorSelector()->renderMode(); 0245 if (!isHueControl() || mode == KisVisualColorSelector::DynamicBackground) { 0246 return QImage(); 0247 } 0248 qreal innerBorder = m_barWidth - 2; 0249 if (mode == KisVisualColorSelector::CompositeBackground) { 0250 innerBorder = KVESS_MARGIN + 1 + 0.25 * (m_barWidth - 4); 0251 } 0252 return renderAlphaMaskImpl(KVESS_MARGIN, innerBorder); 0253 } 0254 0255 void KisVisualEllipticalSelectorShape::renderGamutMask() 0256 { 0257 KoGamutMask *mask = colorSelector()->activeGamutMask(); 0258 0259 if (!mask) { 0260 m_gamutMaskImage = QImage(); 0261 return; 0262 } 0263 const int deviceWidth = qCeil(width() * devicePixelRatioF()); 0264 const int deviceHeight = qCeil(height() * devicePixelRatioF()); 0265 0266 if (m_gamutMaskImage.size() != QSize(deviceWidth, deviceHeight)) { 0267 m_gamutMaskImage = QImage(deviceWidth, deviceHeight, QImage::Format_ARGB32_Premultiplied); 0268 m_gamutMaskImage.setDevicePixelRatio(devicePixelRatioF()); 0269 } 0270 m_gamutMaskImage.fill(0); 0271 0272 QPainter painter(&m_gamutMaskImage); 0273 QPen pen(Qt::white); 0274 painter.setRenderHint(QPainter::Antialiasing, true); 0275 painter.translate(KVESS_MARGIN, KVESS_MARGIN); 0276 painter.setBrush(QColor(0, 0, 0, 128)); 0277 painter.setPen(pen); 0278 0279 painter.drawEllipse(QRectF(0, 0, width() - 2*KVESS_MARGIN, height() - 2*KVESS_MARGIN)); 0280 0281 painter.setTransform(mask->maskToViewTransform(width() - 2*KVESS_MARGIN), true); 0282 painter.setCompositionMode(QPainter::CompositionMode_DestinationIn); 0283 mask->paint(painter, true); 0284 0285 // TODO: implement a way to render gamut mask outline with custom pen 0286 // determine how many units 1 pixel is now: 0287 //QLineF measure = painter.transform().map(QLineF(0.0, 0.0, 1.0, 0.0)); 0288 //pen.setWidthF(1.0 / measure.length()); 0289 //painter.setPen(pen); 0290 painter.setCompositionMode(QPainter::CompositionMode_SourceOver); 0291 mask->paintStroke(painter, true); 0292 0293 m_gamutMaskNeedsUpdate = false; 0294 } 0295 0296 void KisVisualEllipticalSelectorShape::drawCursor(QPainter &painter) 0297 { 0298 //qDebug() << this << "KisVisualEllipticalSelectorShape::drawCursor: image needs update" << imagesNeedUpdate(); 0299 QPointF cursorPoint = convertShapeCoordinateToWidgetCoordinate(getCursorPosition()); 0300 QColor col = getColorFromConverter(getCurrentColor()); 0301 QBrush fill(Qt::SolidPattern); 0302 0303 int cursorwidth = 5; 0304 0305 if (m_type==KisVisualEllipticalSelectorShape::borderMirrored) { 0306 painter.setPen(Qt::white); 0307 fill.setColor(Qt::white); 0308 painter.setBrush(fill); 0309 painter.drawEllipse(cursorPoint, cursorwidth, cursorwidth); 0310 QPointF mirror(width() - cursorPoint.x(), cursorPoint.y()); 0311 painter.drawEllipse(mirror, cursorwidth, cursorwidth); 0312 fill.setColor(col); 0313 painter.setPen(Qt::black); 0314 painter.setBrush(fill); 0315 painter.drawEllipse(cursorPoint, cursorwidth-1, cursorwidth-1); 0316 painter.drawEllipse(mirror, cursorwidth-1, cursorwidth-1); 0317 0318 } else { 0319 painter.setPen(Qt::white); 0320 fill.setColor(Qt::white); 0321 painter.setBrush(fill); 0322 painter.drawEllipse(cursorPoint, cursorwidth, cursorwidth); 0323 fill.setColor(col); 0324 painter.setPen(Qt::black); 0325 painter.setBrush(fill); 0326 painter.drawEllipse(cursorPoint, cursorwidth-1.0, cursorwidth-1.0); 0327 } 0328 } 0329 0330 void KisVisualEllipticalSelectorShape::drawGamutMask(QPainter &painter) 0331 { 0332 if (m_gamutMaskNeedsUpdate) { 0333 renderGamutMask(); 0334 } 0335 painter.drawImage(0, 0, m_gamutMaskImage); 0336 }