1 /*
2  *  Copyright (c) 2010 Adam Celarek <kdedev at xibo dot at>
3  *  Copyright (c) 2008 Martin Renold <martinxyz@gmx.ch>
4  *  Copyright (c) 2009 Ilya Portnov <nomail>
5  *
6  *  This class is based on "lib/colorchanger.hpp" from MyPaint (mypaint.intilinux.com)
7  *
8  *  This library is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU Lesser General Public License as published by
10  *  the Free Software Foundation; version 2 of the License, or
11  *  (at your option) any later version.
12  *
13  *  This library is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *  GNU Lesser General Public License for more details.
17  *
18  *  You should have received a copy of the GNU Lesser General Public License
19  *  along with this program; if not, write to the Free Software
20  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21  */
22 
23 #include "kis_my_paint_shade_selector.h"
24 
25 #include <cmath>
26 #include <cstdlib>
27 
28 #include <QImage>
29 #include <QColor>
30 #include <QPainter>
31 #include <QMouseEvent>
32 #include <QApplication>
33 #include <QDesktopWidget>
34 #include <QtGlobal>
35 #include <QTimer>
36 
37 #include <kconfig.h>
38 #include <kconfiggroup.h>
39 #include <ksharedconfig.h>
40 
41 #include "KoColorSpace.h"
42 #include "KoColorSpaceRegistry.h"
43 #include "KoColor.h"
44 #include "KoCanvasResourceProvider.h"
45 
46 #include "kis_paint_device.h"
47 #include "kis_painter.h"
48 #include "kis_display_color_converter.h"
49 
50 inline int sqr(int x);
51 inline qreal sqr2(qreal x);
52 inline int signedSqr(int x);
53 
54 
KisMyPaintShadeSelector(QWidget * parent)55 KisMyPaintShadeSelector::KisMyPaintShadeSelector(QWidget *parent) :
56         KisColorSelectorBase(parent),
57         m_updateTimer(new QTimer(this))
58 {
59     setAcceptDrops(true);
60 
61     updateSettings();
62 
63     setMinimumSize(80, 80);
64     setColor(KoColor(Qt::red, colorSpace()));
65 
66     m_updateTimer->setInterval(1);
67     m_updateTimer->setSingleShot(true);
68     connect(m_updateTimer, SIGNAL(timeout()), this, SLOT(update()));
69 }
70 
paintEvent(QPaintEvent *)71 void KisMyPaintShadeSelector::paintEvent(QPaintEvent *) {
72     // Hint to the casual reader: some of the calculation here do not
73     // what Martin Renold originally intended. Not everything here will make sense.
74     // It does not matter in the end, as long as the result looks good.
75 
76     // This selector was ported from MyPaint in 2010
77     if (m_cachedColorSpace != colorSpace()) {
78         m_realPixelCache = new KisPaintDevice(colorSpace());
79         m_realCircleBorder = new KisPaintDevice(colorSpace());
80         m_cachedColorSpace = colorSpace();
81     }
82     else {
83         m_realPixelCache->clear();
84         m_realCircleBorder->clear();
85     }
86 	KConfigGroup cfg =  KSharedConfig::openConfig()->group("advancedColorSelector");
87 	QString shadeMyPaintType=cfg.readEntry("shadeMyPaintType", "HSV");
88 
89     int sizeHD = qMin(width(), height())*devicePixelRatioF();
90     int s_radiusHD = sizeHD/2.6;
91 
92     int widthHD = width()*devicePixelRatioF();
93     int heightHD = height()*devicePixelRatioF();
94 
95     const int pixelSize = m_realPixelCache->colorSpace()->pixelSize();
96     const int borderPixelSize = m_realCircleBorder->colorSpace()->pixelSize();
97 
98     QRect pickRectHighDPI = QRect(QPoint(0, 0), size()*devicePixelRatioF());
99     KisSequentialIterator it(m_realPixelCache, pickRectHighDPI);
100     KisSequentialIterator borderIt(m_realCircleBorder, pickRectHighDPI);
101 
102 
103     while(it.nextPixel()) {
104         borderIt.nextPixel();
105 
106         int x = it.x();
107         int y = it.y();
108 
109         float v_factor = 0.6f;
110         float s_factor = 0.6f;
111         float v_factor2 = 0.013f;
112         float s_factor2 = 0.013f;
113 
114         int stripe_width = 15*sizeHD/255.;
115 
116         float h = 0;
117         float s = 0;
118         float v = 0;
119 
120         int dx = x-widthHD/2;
121         int dy = y-heightHD/2;
122         int diag = sqrt(2.0)*sizeHD/2;
123 
124         int dxs, dys;
125         if (dx > 0)
126             dxs = dx - stripe_width;
127         else
128             dxs = dx + stripe_width;
129         if (dy > 0)
130             dys = dy - stripe_width;
131         else
132             dys = dy + stripe_width;
133 
134         qreal r = std::sqrt(qreal(sqr(dxs)+sqr(dys)));
135 
136         if (qMin(abs(dx), abs(dy)) < stripe_width) {
137             // horizontal and vertical lines
138             bool horizontal = std::abs(dx) > std::abs(dy);
139             dx = (dx/qreal(sizeHD))*255;
140             dy = (dy/qreal(sizeHD))*255;
141 
142             h = 0;
143             // x-axis = value, y-axis = saturation
144             v =    dx*v_factor + signedSqr(dx)*v_factor2;
145             s = - (dy*s_factor + signedSqr(dy)*s_factor2);
146             // but not both at once
147             if (horizontal) {
148                 // horizontal stripe
149                 s = 0.0;
150             } else {
151                 // vertical stripe
152                 v = 0.0;
153             }
154         }
155         else if (std::min(std::abs(dx - dy), std::abs(dx + dy)) < stripe_width) {
156 
157             dx = (dx/qreal(sizeHD))*255;
158             dy = (dy/qreal(sizeHD))*255;
159 
160             h = 0;
161             // x-axis = value, y-axis = saturation
162             v =    dx*v_factor + signedSqr(dx)*v_factor2;
163             s = - (dy*s_factor + signedSqr(dy)*s_factor2);
164             // both at once
165         }
166         else if (r < s_radiusHD+1) {
167             // hue
168             if (dx > 0)
169                 h = 90*sqr2(r/s_radiusHD);
170             else
171                 h = 360 - 90*sqr2(r/s_radiusHD);
172             s = 256*(atan2f(std::abs(dxs),dys)/M_PI) - 128;
173 
174             if (r > s_radiusHD) {
175                 // antialiasing boarder
176                 qreal aaFactor = r-floor(r); // part after the decimal point
177                 aaFactor = 1-aaFactor;
178 
179                 qreal fh = m_colorH + h/360.0;
180                 qreal fs = m_colorS + s/255.0;
181                 qreal fv = m_colorV + v/255.0;
182 
183                 fh -= floor(fh);
184                 fs = qBound(qreal(0.0), fs, qreal(1.0));
185                 fv = qBound(qreal(0.01), fv, qreal(1.0));
186                 KoColor color;
187                 //KoColor color = converter()->fromHsvF(fh, fs, fv);
188                 if(shadeMyPaintType=="HSV"){color = converter()->fromHsvF(fh, fs, fv);}
189                 else if(shadeMyPaintType=="HSL"){color = converter()->fromHslF(fh, fs, fv);}
190                 else if(shadeMyPaintType=="HSI"){color = converter()->fromHsiF(fh, fs, fv);}
191                 else if(shadeMyPaintType=="HSY"){color = converter()->fromHsyF(fh, fs, fv, R, G, B);}
192                 else{dbgKrita<<"MyPaint Color selector don't work right.";
193                 color = converter()->fromHsvF(fh, fs, fv);}
194 //dbgKrita<<color->toQcolor();
195                 color.setOpacity(aaFactor);
196                 Acs::setColorWithIterator(borderIt, color, borderPixelSize);
197 
198                 h = 180 + 180*atan2f(dys,-dxs)/M_PI;
199                 v = 255*(r-s_radiusHD)/(diag-s_radiusHD) - 128;
200                 s = 0; // overwrite the s value that was meant for the inside of the circle
201                 // here we already have drawn the inside, and the value left should be just the background value
202 
203             }
204         }
205         else {
206             // background (hue+darkness gradient)
207             h = 180 + 180*atan2f(dys,-dxs)/M_PI;
208             v = 255*(r-s_radiusHD)/(diag-s_radiusHD) - 128;
209         }
210 
211         qreal fh = m_colorH + h/360.0;
212         qreal fs = m_colorS + s/255.0;
213         qreal fv = m_colorV + v/255.0;
214 
215         fh -= floor(fh);
216         fs = qBound(qreal(0.0), fs, qreal(1.0));
217         fv = qBound(qreal(0.01), fv, qreal(1.0));
218         KoColor color;
219         //KoColor color = converter()->fromHsvF(fh, fs, fv);
220         if(shadeMyPaintType=="HSV"){color = converter()->fromHsvF(fh, fs, fv);}
221         else if(shadeMyPaintType=="HSL"){color = converter()->fromHslF(fh, fs, fv);}
222         else if(shadeMyPaintType=="HSI"){color = converter()->fromHsiF(fh, fs, fv);}
223         else if(shadeMyPaintType=="HSY"){color = converter()->fromHsyF(fh, fs, fv);}
224         else{dbgKrita<<"MyPaint Color selector don't work right.";
225         color = converter()->fromHsvF(fh, fs, fv);}
226 
227         Acs::setColorWithIterator(it, color, pixelSize);
228     }
229 
230     KisPainter gc(m_realPixelCache);
231     gc.bitBlt(QPoint(0,0), m_realCircleBorder, QRect(rect().topLeft(), rect().size()*devicePixelRatioF()));
232 
233     QPainter painter(this);
234     QImage renderedImage = converter()->toQImage(m_realPixelCache);
235     renderedImage.setDevicePixelRatio(devicePixelRatioF());
236 
237     painter.drawImage(0, 0, renderedImage);
238 }
239 
240 
mousePressEvent(QMouseEvent * e)241 void KisMyPaintShadeSelector::mousePressEvent(QMouseEvent* e)
242 {
243     e->setAccepted(false);
244     KisColorSelectorBase::mousePressEvent(e);
245 
246     if (!e->isAccepted()) {
247         if(rect().contains(e->pos())) {
248             KoColor color(Acs::pickColor(m_realPixelCache, e->pos()*devicePixelRatioF()));
249             this->updateColorPreview(color);
250             updatePreviousColorPreview();
251         }
252     }
253 }
254 
mouseMoveEvent(QMouseEvent * e)255 void KisMyPaintShadeSelector::mouseMoveEvent(QMouseEvent *e)
256 {
257     if(rect().contains(e->pos())) {
258         KoColor color(Acs::pickColor(m_realPixelCache, e->pos()*devicePixelRatioF()));
259         this->updateColorPreview(color);
260     }
261     KisColorSelectorBase::mouseMoveEvent(e);
262 }
263 
mouseReleaseEvent(QMouseEvent * e)264 void KisMyPaintShadeSelector::mouseReleaseEvent(QMouseEvent *e)
265 {
266     e->setAccepted(false);
267     KisColorSelectorBase::mouseReleaseEvent(e);
268 
269     if(!e->isAccepted()) {
270         KoColor color(Acs::pickColor(m_realPixelCache, e->pos()*devicePixelRatioF()));
271 
272         Acs::ColorRole role = Acs::buttonToRole(e->button());
273 
274         KConfigGroup cfg =  KSharedConfig::openConfig()->group("advancedColorSelector");
275 
276         bool onRightClick = cfg.readEntry("shadeSelectorUpdateOnRightClick", false);
277         bool onLeftClick = cfg.readEntry("shadeSelectorUpdateOnLeftClick", false);
278 
279         bool explicitColorReset =
280             (e->button() == Qt::LeftButton && onLeftClick) ||
281             (e->button() == Qt::RightButton && onRightClick);
282 
283         this->updateColor(color, role, explicitColorReset);
284         updateBaseColorPreview(color);
285         e->accept();
286     }
287 }
288 
createPopup() const289 KisColorSelectorBase* KisMyPaintShadeSelector::createPopup() const
290 {
291     KisColorSelectorBase* popup = new KisMyPaintShadeSelector(0);
292     popup->setColor(m_lastRealColor);
293     return popup;
294 }
295 
setColor(const KoColor & color)296 void KisMyPaintShadeSelector::setColor(const KoColor &color) {
297 
298 	KConfigGroup cfg =  KSharedConfig::openConfig()->group("advancedColorSelector");
299 	QString shadeMyPaintType=cfg.readEntry("shadeMyPaintType", "HSV");
300 
301     R = cfg.readEntry("lumaR", 0.2126);
302     G = cfg.readEntry("lumaG", 0.7152);
303     B = cfg.readEntry("lumaB", 0.0722);
304 
305 	if(shadeMyPaintType=="HSV"){this->converter()->getHsvF(color, &m_colorH, &m_colorS, &m_colorV);}
306 	if(shadeMyPaintType=="HSL"){this->converter()->getHslF(color, &m_colorH, &m_colorS, &m_colorV);}
307 	if(shadeMyPaintType=="HSI"){this->converter()->getHsiF(color, &m_colorH, &m_colorS, &m_colorV);}
308 	if(shadeMyPaintType=="HSY"){this->converter()->getHsyF(color, &m_colorH, &m_colorS, &m_colorV, R, G, B);}
309     m_lastRealColor = color;
310     this->updateColorPreview(color);
311 
312     m_updateTimer->start();
313 }
314 
canvasResourceChanged(int key,const QVariant & v)315 void KisMyPaintShadeSelector::canvasResourceChanged(int key, const QVariant &v)
316 {
317     if(m_colorUpdateAllowed==false)
318         return;
319 
320     KConfigGroup cfg =  KSharedConfig::openConfig()->group("advancedColorSelector");
321 
322     bool onForeground = cfg.readEntry("shadeSelectorUpdateOnForeground", false);
323     bool onBackground = cfg.readEntry("shadeSelectorUpdateOnBackground", true);
324 
325     if ((key == KoCanvasResourceProvider::ForegroundColor && onForeground) ||
326         (key == KoCanvasResourceProvider::BackgroundColor && onBackground)) {
327 
328         setColor(v.value<KoColor>());
329     }
330 }
331 
sqr(int x)332 inline int sqr(int x) {
333     return x*x;
334 }
335 
sqr2(qreal x)336 inline qreal sqr2(qreal x) {
337     return (x*x)/2+x/2;
338 }
339 
signedSqr(int x)340 inline int signedSqr(int x) {
341     int sign = x>0?1:-1;
342     return x*x*sign;
343 }
344