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 }