File indexing completed on 2024-06-16 04:16:01

0001 /*
0002  *  SPDX-FileCopyrightText: 2010 Adam Celarek <kdedev at xibo dot at>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_color_selector_wheel.h"
0008 
0009 #include <QImage>
0010 #include <QPainter>
0011 #include <QColor>
0012 #include <cmath>
0013 #include <kconfig.h>
0014 #include <kconfiggroup.h>
0015 #include <ksharedconfig.h>
0016 
0017 #include "kis_display_color_converter.h"
0018 #include "kis_acs_pixel_cache_renderer.h"
0019 
0020 
0021 KisColorSelectorWheel::KisColorSelectorWheel(KisColorSelector *parent) :
0022     KisColorSelectorComponent(parent),
0023     m_lastClickPos(-1,-1),
0024     m_renderAreaSize(1,1),
0025     m_renderAreaOffsetX(0.0),
0026     m_renderAreaOffsetY(0.0),
0027     m_toRenderArea(QTransform())
0028 {
0029 }
0030 
0031 KoColor KisColorSelectorWheel::selectColor(int x, int y)
0032 {
0033     int xWheel = x-width()/2;
0034     int yWheel = y-height()/2;
0035 
0036     qreal radius = sqrt((double)xWheel*xWheel+yWheel*yWheel);
0037     radius/=qMin(width(), height());
0038     if(radius>0.5)
0039         radius=0.5;
0040 
0041     radius*=2.;
0042 
0043     qreal angle = std::atan2((qreal)yWheel, (qreal)xWheel);
0044     angle+=M_PI;
0045     angle/=2*M_PI;
0046 
0047     switch (m_parameter) {
0048     case KisColorSelectorConfiguration::hsvSH:
0049         emit paramChanged(angle, radius, -1, -1, -1, -1, -1, -1, -1);
0050         break;
0051     case KisColorSelectorConfiguration::hslSH:
0052         emit paramChanged(angle, -1, -1, radius, -1, -1, -1, -1, -1);
0053         break;
0054     case KisColorSelectorConfiguration::hsiSH:
0055         emit paramChanged(angle, -1, -1, -1, -1, radius, -1, -1, -1);
0056         break;
0057     case KisColorSelectorConfiguration::hsySH:
0058         emit paramChanged(angle, -1, -1, -1, -1, -1, -1, radius, -1);
0059         break;
0060     case KisColorSelectorConfiguration::VH:
0061         emit paramChanged(angle, -1, radius, -1, -1, -1, -1, -1, -1);
0062         break;
0063     case KisColorSelectorConfiguration::LH:
0064         emit paramChanged(angle, -1, -1, -1, radius, -1, -1, -1, -1);
0065         break;
0066     case KisColorSelectorConfiguration::IH:
0067         emit paramChanged(angle, -1, -1, -1, -1, -1, radius, -1, -1);
0068         break;
0069     case KisColorSelectorConfiguration::YH:
0070         emit paramChanged(angle, -1, -1, -1, -1, -1, -1, -1, radius);
0071         break;
0072     default:
0073         Q_ASSERT(false);
0074         break;
0075     }
0076 
0077     emit update();
0078 
0079     angle *= 2. * M_PI;
0080     angle -= M_PI;
0081     radius*=0.5;
0082     m_lastClickPos.setX(cos(angle)*radius+0.5);
0083     m_lastClickPos.setY(sin(angle)*radius+0.5);
0084 
0085     return colorAt(x, y, true);
0086 }
0087 
0088 void KisColorSelectorWheel::setColor(const KoColor &color)
0089 {
0090     qreal hsvH, hsvS, hsvV;
0091     qreal hslH, hslS, hslL;
0092     qreal hsiH, hsiS, hsiI;
0093     qreal hsyH, hsyS, hsyY;
0094     KConfigGroup cfg =  KSharedConfig::openConfig()->group("advancedColorSelector");
0095     R = cfg.readEntry("lumaR", 0.2126);
0096     G = cfg.readEntry("lumaG", 0.7152);
0097     B = cfg.readEntry("lumaB", 0.0722);
0098     Gamma = cfg.readEntry("gamma", 2.2);
0099     m_parent->converter()->getHsvF(color, &hsvH, &hsvS, &hsvV);
0100     m_parent->converter()->getHslF(color, &hslH, &hslS, &hslL);
0101     m_parent->converter()->getHsiF(color, &hsiH, &hsiS, &hsiI);
0102     m_parent->converter()->getHsyF(color, &hsyH, &hsyS, &hsyY, R, G, B, Gamma);
0103 
0104     qreal angle = 0.0, radius = 0.0;
0105     switch (m_parameter) {
0106     case KisColorSelectorConfiguration::LH:
0107         emit paramChanged(hslH, -1, -1, -1, hslL, -1, -1, -1, -1);
0108         angle = hslH;
0109         radius = hslL;
0110         break;
0111     case KisColorSelectorConfiguration::VH:
0112         emit paramChanged(hsvH, -1, hsvV, -1, -1, -1, -1, -1, -1);
0113         angle = hsvH;
0114         radius = hsvV;
0115         break;
0116     case KisColorSelectorConfiguration::IH:
0117         emit paramChanged(hslH, -1, -1, -1, -1, -1, hsiI, -1, -1);
0118         angle = hsiH;
0119         radius = hsiI;
0120         break;
0121     case KisColorSelectorConfiguration::YH:
0122         emit paramChanged(hsyH, -1, -1, -1, -1, -1, -1, -1, hsyY);
0123         angle = hsyH;
0124         radius = hsyY;
0125         break;
0126     case KisColorSelectorConfiguration::hsvSH:
0127         emit paramChanged(hsvH, hsvS, -1, -1, -1, -1, -1, -1, -1);
0128         angle = hsvH;
0129         radius = hsvS;
0130         break;
0131     case KisColorSelectorConfiguration::hslSH:
0132         emit paramChanged(hslH, -1, -1, hslS, -1, -1, -1, -1, -1);
0133         angle = hslH;
0134         radius = hslS;
0135         break;
0136     case KisColorSelectorConfiguration::hsiSH:
0137         emit paramChanged(hsiH, -1, -1, -1, -1, hsiS, -1, -1, -1);
0138         angle = hsiH;
0139         radius = hsiS;
0140         break;
0141     case KisColorSelectorConfiguration::hsySH:
0142         emit paramChanged(hsyH, -1, -1, -1, -1, -1, -1, hsyS, -1);
0143         angle = hsyH;
0144         radius = hsyS;
0145         break;
0146     default:
0147         Q_ASSERT(false);
0148         break;
0149     }
0150     angle *= 2. * M_PI;
0151     angle -= M_PI;
0152     radius *= 0.5;
0153 
0154     m_lastClickPos.setX(cos(angle)*radius+0.5);
0155     m_lastClickPos.setY(sin(angle)*radius+0.5);
0156 
0157     //workaround for bug 279500
0158 
0159     if(m_lastClickPos!=QPoint(-1,-1) && m_parent->displayBlip()) {
0160         QPoint pos = (m_lastClickPos*qMin(width(), height())).toPoint();
0161         if(width() < height()) {
0162             pos.setY(pos.y()+height()/2-width()/2);
0163         } else {
0164             pos.setX(pos.x()+width()/2-height()/2);
0165         }
0166 
0167         setLastMousePosition(pos.x(), pos.y());
0168     }
0169 
0170     KisColorSelectorComponent::setColor(color);
0171 }
0172 
0173 void KisColorSelectorWheel::paint(QPainter* painter)
0174 {
0175 
0176     if(isDirty()) {
0177         KisPaintDeviceSP realPixelCache;
0178         Acs::PixelCacheRenderer::render(this, m_parent->converter(), QRect(0, 0, width(), height()), realPixelCache,
0179                                         m_pixelCache, m_pixelCacheOffset, painter->device()->devicePixelRatioF());
0180 
0181         //antialiasing for wheel
0182         QPainter tmpPainter(&m_pixelCache);
0183         tmpPainter.setRenderHint(QPainter::Antialiasing);
0184         tmpPainter.setPen(QPen(QColor(0,0,0,0), 2.5));
0185         tmpPainter.setCompositionMode(QPainter::CompositionMode_Clear);
0186         int size=qMin(width(), height());
0187 
0188         m_renderAreaSize = QSize(size,size);
0189         m_renderAreaOffsetX = ((qreal)width()-(qreal)m_renderAreaSize.width())*0.5;
0190         m_renderAreaOffsetY = ((qreal)height()-(qreal)m_renderAreaSize.height())*0.5;
0191         m_toRenderArea.reset();
0192         m_toRenderArea.translate(-m_renderAreaOffsetX,-m_renderAreaOffsetY);
0193 
0194         QPoint ellipseCenter(width() / 2 - size / 2, height() / 2 - size / 2);
0195         ellipseCenter -= m_pixelCacheOffset;
0196 
0197         tmpPainter.drawEllipse(ellipseCenter.x(), ellipseCenter.y(), size, size);
0198     }
0199 
0200     painter->drawImage(m_pixelCacheOffset.x(),m_pixelCacheOffset.y(), m_pixelCache);
0201 
0202     // draw gamut mask
0203     if (m_gamutMaskOn && m_currentGamutMask) {
0204         QImage maskBuffer = QImage(m_renderAreaSize*painter->device()->devicePixelRatioF(), QImage::Format_ARGB32_Premultiplied);
0205         maskBuffer.setDevicePixelRatio(painter->device()->devicePixelRatioF());
0206         maskBuffer.fill(0);
0207         QPainter maskPainter(&maskBuffer);
0208 
0209         QRect rect = QRect(0, 0, m_renderAreaSize.width(), m_renderAreaSize.height());
0210         maskPainter.setRenderHint(QPainter::Antialiasing, true);
0211 
0212         maskPainter.resetTransform();
0213         maskPainter.translate(rect.width()*0.5, rect.height()*0.5);
0214         maskPainter.scale(rect.width()*0.5, rect.height()*0.5);
0215 
0216         maskPainter.setPen(QPen(QBrush(Qt::white), 0.002));
0217         maskPainter.setBrush(QColor(128,128,128,255)); // middle gray
0218 
0219         maskPainter.drawEllipse(QPointF(0,0), 1.0, 1.0);
0220 
0221         maskPainter.resetTransform();
0222         maskPainter.setTransform(m_currentGamutMask->maskToViewTransform(m_renderAreaSize.width()));
0223 
0224         maskPainter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
0225         m_currentGamutMask->paint(maskPainter, m_maskPreviewActive);
0226 
0227         maskPainter.setCompositionMode(QPainter::CompositionMode_SourceOver);
0228         m_currentGamutMask->paintStroke(maskPainter, m_maskPreviewActive);
0229 
0230         painter->drawImage(m_renderAreaOffsetX, m_renderAreaOffsetY, maskBuffer);
0231     }
0232 
0233     // draw blips
0234 
0235     if(m_lastClickPos!=QPoint(-1,-1) && m_parent->displayBlip()) {
0236         QPoint pos = (m_lastClickPos*qMin(width(), height())).toPoint();
0237         if(width()<height())
0238             pos.setY(pos.y()+height()/2-width()/2);
0239         else
0240             pos.setX(pos.x()+width()/2-height()/2);
0241 
0242         painter->setPen(QColor(0,0,0));
0243         painter->drawEllipse(pos, 5, 5);
0244         painter->setPen(QColor(255,255,255));
0245         painter->drawEllipse(pos, 4, 4);
0246     }
0247 }
0248 
0249 KoColor KisColorSelectorWheel::colorAt(float x, float y, bool forceValid)
0250 {
0251     KoColor color = KoColor::createTransparent(m_parent->colorSpace());
0252 
0253     Q_ASSERT(x>=0 && x<=width());
0254     Q_ASSERT(y>=0 && y<=height());
0255 
0256     const qreal xRel = x - 0.5 * width();
0257     const qreal yRel = y - 0.5 * height();
0258     const qreal minDimension = qMin(width(), height());
0259 
0260     qreal radius = sqrt(xRel*xRel+yRel*yRel);
0261     if (radius > 0.5 * minDimension) {
0262         if (!forceValid) {
0263             return color;
0264         } else {
0265             radius = 0.5 * minDimension;
0266         }
0267     }
0268     radius /= 0.5 * minDimension;
0269 
0270     qreal angle = std::atan2(yRel, xRel);
0271     angle += M_PI;
0272     angle /= 2 * M_PI;
0273 
0274     switch(m_parameter) {
0275     case KisColorSelectorConfiguration::hsvSH:
0276         color = m_parent->converter()->fromHsvF(angle, radius, m_value);
0277         break;
0278     case KisColorSelectorConfiguration::hslSH:
0279         color = m_parent->converter()->fromHslF(angle, radius, m_lightness);
0280         break;
0281     case KisColorSelectorConfiguration::hsiSH:
0282         color = m_parent->converter()->fromHsiF(angle, radius, m_intensity);
0283         break;
0284     case KisColorSelectorConfiguration::hsySH:
0285         color = m_parent->converter()->fromHsyF(angle, radius, m_luma, R, G, B, Gamma);
0286         break;
0287     case KisColorSelectorConfiguration::VH:
0288         color = m_parent->converter()->fromHsvF(angle, m_hsvSaturation, radius);
0289         break;
0290     case KisColorSelectorConfiguration::LH:
0291         color = m_parent->converter()->fromHslF(angle, m_hslSaturation, radius);
0292         break;
0293     case KisColorSelectorConfiguration::IH:
0294         color = m_parent->converter()->fromHsiF(angle, m_hsiSaturation, radius);
0295         break;
0296     case KisColorSelectorConfiguration::YH:
0297         color = m_parent->converter()->fromHsyF(angle, m_hsySaturation, radius, R, G, B, Gamma);
0298         break;
0299     default:
0300         Q_ASSERT(false);
0301 
0302         return color;
0303     }
0304     return color;
0305 }
0306 
0307 bool KisColorSelectorWheel::allowsColorSelectionAtPoint(const QPoint &pt) const
0308 {
0309     if (!m_gamutMaskOn || !m_currentGamutMask) {
0310         return true;
0311     }
0312 
0313     QPointF colorCoord = m_toRenderArea.map(QPointF(pt));
0314     QPointF translatedPoint = m_currentGamutMask->viewToMaskTransform(m_renderAreaSize.width()).map(colorCoord);
0315     bool isClear = m_currentGamutMask->coordIsClear(translatedPoint, m_maskPreviewActive);
0316 
0317     return isClear;
0318 }