6#include "chromalightnessdiagram.h"
8#include "chromalightnessdiagram_p.h"
10#include "abstractdiagram.h"
11#include "cielchd50values.h"
12#include "constpropagatingrawpointer.h"
13#include "constpropagatinguniquepointer.h"
14#include "helperconstants.h"
15#include "rgbcolorspace.h"
22#include <qnamespace.h>
28#include <qsharedpointer.h>
29#include <qsizepolicy.h>
43 : AbstractDiagram(parent)
44 , d_pointer(new ChromaLightnessDiagramPrivate(this))
48 d_pointer->m_rgbColorSpace = colorSpace;
51 setFocusPolicy(Qt::FocusPolicy::StrongFocus);
53 d_pointer->m_chromaLightnessImageParameters.imageSizePhysical =
54 d_pointer->calculateImageSizePhysical();
55 d_pointer->m_chromaLightnessImageParameters.rgbColorSpace = colorSpace;
56 d_pointer->m_chromaLightnessImage.setImageParameters(
57 d_pointer->m_chromaLightnessImageParameters);
60 connect(&d_pointer->m_chromaLightnessImage,
61 &AsyncImageProvider<ChromaLightnessImageParameters>::interlacingPassCompleted,
63 &ChromaLightnessDiagram::callUpdate);
67ChromaLightnessDiagram::~ChromaLightnessDiagram() noexcept
75ChromaLightnessDiagramPrivate::ChromaLightnessDiagramPrivate(ChromaLightnessDiagram *backLink)
76 : m_currentColorCielchD50(CielchD50Values::srgbVersatileInitialColor)
95void ChromaLightnessDiagramPrivate::setCurrentColorFromWidgetPixelPosition(
const QPoint widgetPixelPosition)
97 const GenericColor color = fromWidgetPixelPositionToCielchD50(widgetPixelPosition);
98 q_pointer->setCurrentColorCielchD50(
100 nearestInGamutCielchD50ByAdjustingChromaLightness(color.second, color.first));
118int ChromaLightnessDiagramPrivate::defaultBorderPhysical()
const
120 const qreal border = q_pointer->handleRadius()
121 + q_pointer->handleOutlineThickness() / 2.0;
122 return qCeil(border * q_pointer->devicePixelRatioF());
140int ChromaLightnessDiagramPrivate::leftBorderPhysical()
const
142 const int focusIndicatorThickness = qCeil(
143 q_pointer->handleOutlineThickness() * q_pointer->devicePixelRatioF());
146 const int candidateOne = defaultBorderPhysical() + focusIndicatorThickness;
149 const int candidateTwo = qCeil(
150 q_pointer->spaceForFocusIndicator() * q_pointer->devicePixelRatioF());
152 return qMax(candidateOne, candidateTwo);
160QSize ChromaLightnessDiagramPrivate::calculateImageSizePhysical()
const
162 const QSize borderSizePhysical(
164 leftBorderPhysical() + defaultBorderPhysical(),
165 2 * defaultBorderPhysical()
167 return q_pointer->physicalPixelSize() - borderSizePhysical;
184GenericColor ChromaLightnessDiagramPrivate::fromWidgetPixelPositionToCielchD50(
const QPoint widgetPixelPosition)
const
186 const QPointF offset(leftBorderPhysical(), defaultBorderPhysical());
187 const QPointF imageCoordinatePoint = widgetPixelPosition
190 - offset / q_pointer->devicePixelRatioF()
194 color.third = m_currentColorCielchD50.third;
195 const qreal diagramHeight =
196 calculateImageSizePhysical().height() / q_pointer->devicePixelRatioF();
197 if (diagramHeight > 0) {
198 color.first = imageCoordinatePoint.
y() * 100.0 / diagramHeight * (-1.0) + 100.0;
199 color.second = imageCoordinatePoint.
x() * 100.0 / diagramHeight;
225void ChromaLightnessDiagram::mousePressEvent(
QMouseEvent *event)
227 d_pointer->m_isMouseEventActive =
true;
228 d_pointer->setCurrentColorFromWidgetPixelPosition(
event->pos());
229 if (d_pointer->isWidgetPixelPositionInGamut(
event->pos())) {
245void ChromaLightnessDiagram::mouseMoveEvent(
QMouseEvent *event)
247 d_pointer->setCurrentColorFromWidgetPixelPosition(
event->pos());
248 if (d_pointer->isWidgetPixelPositionInGamut(
event->pos())) {
265void ChromaLightnessDiagram::mouseReleaseEvent(
QMouseEvent *event)
267 d_pointer->setCurrentColorFromWidgetPixelPosition(
event->pos());
276void ChromaLightnessDiagram::paintEvent(
QPaintEvent *event)
297 QImage paintBuffer(physicalPixelSize(),
307 d_pointer->m_chromaLightnessImage.refreshAsync();
308 const QColor myNeutralGray =
309 d_pointer->m_rgbColorSpace->fromCielchD50ToQRgbBound(CielchD50Values::neutralGray);
311 painter.setBrush(myNeutralGray);
312 const auto imageSize =
313 d_pointer->m_chromaLightnessImage.imageParameters().imageSizePhysical;
316 d_pointer->leftBorderPhysical(),
317 d_pointer->defaultBorderPhysical(),
322 d_pointer->leftBorderPhysical(),
323 d_pointer->defaultBorderPhysical(),
324 d_pointer->m_chromaLightnessImage.getCache()
361 pen.
setWidthF(handleOutlineThickness() * devicePixelRatioF());
362 pen.
setColor(focusIndicatorColor());
368 handleOutlineThickness() * devicePixelRatioF() / 2.0,
370 0 + d_pointer->defaultBorderPhysical());
373 handleOutlineThickness() * devicePixelRatioF() / 2.0,
375 physicalPixelSize().height() - d_pointer->defaultBorderPhysical());
376 painter.drawLine(pointOne, pointTwo);
380 const int diagramHeight = d_pointer->calculateImageSizePhysical().height();
383 d_pointer->m_currentColorCielchD50.second * diagramHeight / 100.0,
385 d_pointer->m_currentColorCielchD50.first * diagramHeight / 100.0 * (-1) + diagramHeight);
386 colorCoordinatePoint +=
QPointF(
388 d_pointer->leftBorderPhysical(),
390 d_pointer->defaultBorderPhysical());
392 pen.
setWidthF(handleOutlineThickness() * devicePixelRatioF());
393 pen.
setColor(handleColorFromBackgroundLightness(d_pointer->m_currentColorCielchD50.first));
397 painter.drawEllipse(colorCoordinatePoint,
398 handleRadius() * devicePixelRatioF(),
399 handleRadius() * devicePixelRatioF()
403 paintBuffer.setDevicePixelRatio(devicePixelRatioF());
406 widgetPainter.drawImage(0, 0, paintBuffer);
430void ChromaLightnessDiagram::keyPressEvent(
QKeyEvent *event)
432 GenericColor temp = d_pointer->m_currentColorCielchD50;
433 switch (
event->key()) {
435 temp.first += singleStepLightness;
438 temp.first -= singleStepLightness;
441 temp.second = qMax<double>(0, temp.second - singleStepChroma);
444 temp.second += singleStepChroma;
445 temp = d_pointer->m_rgbColorSpace->reduceCielchD50ChromaToFitIntoGamut(temp);
448 temp.first += pageStepLightness;
451 temp.first -= pageStepLightness;
454 temp.second += pageStepChroma;
455 temp = d_pointer->m_rgbColorSpace->reduceCielchD50ChromaToFitIntoGamut(temp);
458 temp.second = qMax<double>(0, temp.second - pageStepChroma);
479 setCurrentColorCielchD50(
481 d_pointer->m_rgbColorSpace->reduceCielchD50ChromaToFitIntoGamut(temp));
503bool ChromaLightnessDiagramPrivate::isWidgetPixelPositionInGamut(
const QPoint widgetPixelPosition)
const
505 if (calculateImageSizePhysical().isEmpty()) {
513 const GenericColor color = fromWidgetPixelPositionToCielchD50(widgetPixelPosition);
517 if (color.second < 0) {
522 return m_rgbColorSpace->isCielchD50InGamut(color);
533void ChromaLightnessDiagram::setCurrentColorCielchD50(
const PerceptualColor::GenericColor &newCurrentColorCielchD50)
535 if (newCurrentColorCielchD50 == d_pointer->m_currentColorCielchD50) {
539 double oldHue = d_pointer->m_currentColorCielchD50.third;
540 d_pointer->m_currentColorCielchD50 = newCurrentColorCielchD50;
541 if (d_pointer->m_currentColorCielchD50.third != oldHue) {
543 d_pointer->m_chromaLightnessImageParameters.hue =
544 d_pointer->m_currentColorCielchD50.third;
545 d_pointer->m_chromaLightnessImage.setImageParameters(
546 d_pointer->m_chromaLightnessImageParameters);
549 Q_EMIT currentColorCielchD50Changed(newCurrentColorCielchD50);
557void ChromaLightnessDiagram::resizeEvent(
QResizeEvent *event)
560 d_pointer->m_chromaLightnessImageParameters.imageSizePhysical =
561 d_pointer->calculateImageSizePhysical();
562 d_pointer->m_chromaLightnessImage.setImageParameters(
563 d_pointer->m_chromaLightnessImageParameters);
577QSize ChromaLightnessDiagram::sizeHint()
const
579 return minimumSizeHint() * scaleFromMinumumSizeHintToSizeHint;
589QSize ChromaLightnessDiagram::minimumSizeHint()
const
591 const int minimumHeight = qRound(
593 2.0 * d_pointer->defaultBorderPhysical() / devicePixelRatioF()
595 + gradientMinimumLength());
596 const int minimumWidth = qRound(
598 (d_pointer->leftBorderPhysical() + d_pointer->defaultBorderPhysical()) / devicePixelRatioF()
602 + gradientMinimumLength() * d_pointer->m_rgbColorSpace->profileMaximumCielchD50Chroma() / 100.0);
604 return QSize(minimumWidth, minimumHeight);
609GenericColor PerceptualColor::ChromaLightnessDiagram::currentColorCielchD50()
const
611 return d_pointer->m_currentColorCielchD50;
636ChromaLightnessDiagramPrivate::nearestNeighborSearch(
const QPoint point,
const QRect searchRectangle,
const std::function<
bool(
const QPoint)> &doesPointExist)
638 if (!searchRectangle.
isValid()) {
645 if (searchRectangle.
contains(point)) {
646 if (doesPointExist(point)) {
654 const auto hDistanceFromRect = distanceFromRange(searchRectangle.
left(),
656 searchRectangle.
right());
657 const auto vDistanceFromRect = distanceFromRange(searchRectangle.
top(),
659 searchRectangle.
bottom());
666 const auto initialOffset = qMax(1,
667 qMax(hDistanceFromRect, vDistanceFromRect));
668 const auto hMaxDistance = qMax(qAbs(point.
x() - searchRectangle.
left()),
669 qAbs(point.
x() - searchRectangle.
right()));
670 const auto vMaxDistance = qMax(qAbs(point.
y() - searchRectangle.
top()),
671 qAbs(point.
y() - searchRectangle.
bottom()));
672 const auto maximumOffset = qMax(hMaxDistance, vMaxDistance);
673 std::optional<QPoint> nearestPointTillNow;
674 int nearestPointTillNowDistanceSquare = 0;
675 qreal nearestPointTillNowDistance = 0.0;
677 auto searchPointOffsets = [](
int i,
int j) ->
QList<QPoint> {
697 for (i = initialOffset;
698 (i <= maximumOffset) && (!nearestPointTillNow.has_value());
701 for (j = 0; (j <= i) && (!nearestPointTillNow.has_value()); ++j) {
702 const auto container = searchPointOffsets(i, j);
703 for (
const QPoint &temp : std::as_const(container)) {
712 searchPoint = point + temp;
713 if (searchRectangle.
contains(searchPoint)) {
714 if (doesPointExist(searchPoint)) {
715 nearestPointTillNow = searchPoint;
716 nearestPointTillNowDistanceSquare =
717 temp.x() * temp.x() + temp.y() * temp.y();
718 nearestPointTillNowDistance = qSqrt(
719 nearestPointTillNowDistanceSquare);
727 if (!nearestPointTillNow.has_value()) {
730 return nearestPointTillNow;
740 for (; i < nearestPointTillNowDistance; ++i) {
741 qreal maximumJ = qSqrt(nearestPointTillNowDistanceSquare - i * i);
742 for (j = 0; j < maximumJ; ++j) {
743 const auto container = searchPointOffsets(i, j);
744 for (
const QPoint &temp : std::as_const(container)) {
745 searchPoint = point + temp;
746 if (searchRectangle.
contains(searchPoint)) {
747 if (doesPointExist(searchPoint)) {
748 nearestPointTillNow = searchPoint;
749 nearestPointTillNowDistanceSquare =
750 temp.x() * temp.x() + temp.y() * temp.y();
751 nearestPointTillNowDistance = qSqrt(
752 nearestPointTillNowDistanceSquare);
754 nearestPointTillNowDistanceSquare - i * i);
762 return nearestPointTillNow;
784std::optional<QPoint> ChromaLightnessDiagramPrivate::nearestInGamutPixelPosition(
const QPoint originalPixelPosition)
786 m_chromaLightnessImage.refreshSync();
787 const auto upToDateImage = m_chromaLightnessImage.getCache();
789 auto isOpaqueFunction = [&upToDateImage](
const QPoint point) ->
bool {
790 return (qAlpha(upToDateImage.pixel(point)) != 0);
792 return nearestNeighborSearch(originalPixelPosition,
812PerceptualColor::GenericColor ChromaLightnessDiagramPrivate::nearestInGamutCielchD50ByAdjustingChromaLightness(
const double chroma,
const double lightness)
816 temp.first = lightness;
818 temp.third = m_currentColorCielchD50.third;
819 if (temp.second < 0) {
826 if (m_rgbColorSpace->isCielchD50InGamut(temp)) {
830 const auto imageHeight = calculateImageSizePhysical().
height();
832 qRound(temp.second * (imageHeight - 1) / 100.0),
833 qRound(imageHeight - 1 - temp.first * (imageHeight - 1) / 100.0));
836 nearestInGamutPixelPosition(myPixelPosition).value_or(
QPoint(0, 0));
837 GenericColor result = temp;
838 result.second = myPixelPosition.x() * 100.0 / (imageHeight - 1);
839 result.first = 100 - myPixelPosition.y() * 100.0 / (imageHeight - 1);
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
void update(Part *part, const QByteArray &data, qint64 dataSize)
KGUIADDONS_EXPORT qreal chroma(const QColor &)
The namespace of this library.
void setCapStyle(Qt::PenCapStyle style)
void setColor(const QColor &color)
void setWidthF(qreal width)
bool contains(const QPoint &point, bool proper) const const
bool isValid() const const
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)