diff --git a/libs/widgets/KisVisualColorSelectorShape.cpp b/libs/widgets/KisVisualColorSelectorShape.cpp index d075aed1c9..ace1ed2ec8 100644 --- a/libs/widgets/KisVisualColorSelectorShape.cpp +++ b/libs/widgets/KisVisualColorSelectorShape.cpp @@ -1,296 +1,337 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisVisualColorSelectorShape.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoColorConversions.h" #include "KoColorDisplayRendererInterface.h" #include "KoChannelInfo.h" #include #include #include "kis_signal_compressor.h" #include "kis_debug.h" struct KisVisualColorSelectorShape::Private { QImage gradient; + QImage alphaMask; QImage fullSelector; - bool imagesNeedUpdate {true}; + bool imagesNeedUpdate { true }; + bool alphaNeedsUpdate { true }; QPointF currentCoordinates; // somewhat redundant? QVector4D currentChannelValues; Dimensions dimension; const KoColorSpace *colorSpace; int channel1; int channel2; const KoColorDisplayRendererInterface *displayRenderer = 0; }; KisVisualColorSelectorShape::KisVisualColorSelectorShape(QWidget *parent, KisVisualColorSelectorShape::Dimensions dimension, const KoColorSpace *cs, int channel1, int channel2, const KoColorDisplayRendererInterface *displayRenderer): QWidget(parent), m_d(new Private) { m_d->dimension = dimension; m_d->colorSpace = cs; int maxchannel = m_d->colorSpace->colorChannelCount()-1; m_d->channel1 = qBound(0, channel1, maxchannel); m_d->channel2 = qBound(0, channel2, maxchannel); this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); setDisplayRenderer(displayRenderer); } KisVisualColorSelectorShape::~KisVisualColorSelectorShape() { } QPointF KisVisualColorSelectorShape::getCursorPosition() { return m_d->currentCoordinates; } void KisVisualColorSelectorShape::setCursorPosition(QPointF position, bool signal) { QPointF newPos(qBound(0.0, position.x(), 1.0), qBound(0.0, position.y(), 1.0)); if (newPos != m_d->currentCoordinates) { m_d->currentCoordinates = newPos; // for internal consistency, because we have a bit of redundancy here m_d->currentChannelValues[m_d->channel1] = newPos.x(); if (m_d->dimension == Dimensions::twodimensional){ m_d->currentChannelValues[m_d->channel2] = newPos.y(); } update(); if (signal){ emit sigCursorMoved(newPos); } } } void KisVisualColorSelectorShape::setChannelValues(QVector4D channelValues, bool setCursor) { //qDebug() << this << "setChannelValues"; m_d->currentChannelValues = channelValues; if (setCursor) { m_d->currentCoordinates = QPointF(qBound(0.f, channelValues[m_d->channel1], 1.f), qBound(0.f, channelValues[m_d->channel2], 1.f)); } else { // for internal consistency, because we have a bit of redundancy here m_d->currentChannelValues[m_d->channel1] = m_d->currentCoordinates.x(); if (m_d->dimension == Dimensions::twodimensional){ m_d->currentChannelValues[m_d->channel2] = m_d->currentCoordinates.y(); } } m_d->imagesNeedUpdate = true; update(); } void KisVisualColorSelectorShape::setDisplayRenderer (const KoColorDisplayRendererInterface *displayRenderer) { if (displayRenderer) { if (m_d->displayRenderer) { m_d->displayRenderer->disconnect(this); } m_d->displayRenderer = displayRenderer; } else { m_d->displayRenderer = KoDumbColorDisplayRenderer::instance(); } } void KisVisualColorSelectorShape::forceImageUpdate() { //qDebug() << this << "forceImageUpdate"; + m_d->alphaNeedsUpdate = true; m_d->imagesNeedUpdate = true; } QColor KisVisualColorSelectorShape::getColorFromConverter(KoColor c){ QColor col; KoColor color = c; if (m_d->displayRenderer) { color.convertTo(m_d->displayRenderer->getPaintingColorSpace()); col = m_d->displayRenderer->toQColor(c); } else { col = c.toQColor(); } return col; } // currently unused? void KisVisualColorSelectorShape::slotSetActiveChannels(int channel1, int channel2) { //qDebug() << this << "slotSetActiveChannels"; int maxchannel = m_d->colorSpace->colorChannelCount()-1; m_d->channel1 = qBound(0, channel1, maxchannel); m_d->channel2 = qBound(0, channel2, maxchannel); m_d->imagesNeedUpdate = true; update(); } bool KisVisualColorSelectorShape::imagesNeedUpdate() const { return m_d->imagesNeedUpdate; } QImage KisVisualColorSelectorShape::getImageMap() { //qDebug() << this << ">>>>>>>>> getImageMap()" << m_d->imagesNeedUpdate; if (m_d->imagesNeedUpdate) { // Fill a buffer with the right kocolors m_d->gradient = renderBackground(m_d->currentChannelValues, m_d->colorSpace->pixelSize()); m_d->imagesNeedUpdate = false; } return m_d->gradient; } +const QImage KisVisualColorSelectorShape::getAlphaMask() const +{ + if (m_d->alphaNeedsUpdate) { + m_d->alphaMask = renderAlphaMask(); + m_d->alphaNeedsUpdate = false; + } + return m_d->alphaMask; +} + QImage KisVisualColorSelectorShape::convertImageMap(const quint8 *rawColor, quint32 bufferSize, QSize imgSize) const { Q_ASSERT(bufferSize == imgSize.width() * imgSize.height() * m_d->colorSpace->pixelSize()); QImage image; // Convert the buffer to a qimage if (m_d->displayRenderer) { image = m_d->displayRenderer->convertToQImage(m_d->colorSpace, rawColor, imgSize.width(), imgSize.height()); } else { image = m_d->colorSpace->convertToQImage(rawColor, imgSize.width(), imgSize.height(), 0, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } // safeguard: if (image.isNull()) { image = QImage(width(), height(), QImage::Format_ARGB32); image.fill(Qt::black); } return image; } QImage KisVisualColorSelectorShape::renderBackground(const QVector4D &channelValues, quint32 pixelSize) const { const KisVisualColorSelector *selector = qobject_cast(parent()); Q_ASSERT(selector); // Hi-DPI aware rendering requires that we determine the device pixel dimension; // actual widget size in device pixels is not accessible unfortunately, it might be 1px smaller... const qreal deviceDivider = 1.0 / devicePixelRatioF(); const int deviceWidth = qCeil(width() * devicePixelRatioF()); const int deviceHeight = qCeil(height() * devicePixelRatioF()); quint32 imageSize = deviceWidth * deviceHeight * m_d->colorSpace->pixelSize(); QScopedArrayPointer raw(new quint8[imageSize] {}); quint8 *dataPtr = raw.data(); QVector4D coordinates = channelValues; + + QImage alpha = getAlphaMask(); + bool checkAlpha = !alpha.isNull() && alpha.valid(deviceWidth - 1, deviceHeight - 1); + KIS_SAFE_ASSERT_RECOVER(!checkAlpha || alpha.format() == QImage::Format_Alpha8) { + checkAlpha = false; + } + + KoColor filler(Qt::white, m_d->colorSpace); for (int y = 0; y < deviceHeight; y++) { + const uchar *alphaLine = checkAlpha ? alpha.scanLine(y) : 0; for (int x=0; x < deviceWidth; x++) { - QPointF newcoordinate = convertWidgetCoordinateToShapeCoordinate(QPointF(x, y) * deviceDivider); - coordinates[m_d->channel1] = newcoordinate.x(); - if (m_d->dimension == Dimensions::twodimensional){ - coordinates[m_d->channel2] = newcoordinate.y(); + if (!checkAlpha || alphaLine[x]) { + QPointF newcoordinate = convertWidgetCoordinateToShapeCoordinate(QPointF(x, y) * deviceDivider); + coordinates[m_d->channel1] = newcoordinate.x(); + if (m_d->dimension == Dimensions::twodimensional) { + coordinates[m_d->channel2] = newcoordinate.y(); + } + KoColor c = selector->convertShapeCoordsToKoColor(coordinates); + memcpy(dataPtr, c.data(), pixelSize); + } + else { + // need to write a color with non-zero alpha, otherwise the display converter + // will for some arcane reason crop the final QImage and screw rendering + memcpy(dataPtr, filler.data(), pixelSize); } - KoColor c = selector->convertShapeCoordsToKoColor(coordinates); - memcpy(dataPtr, c.data(), pixelSize); dataPtr += pixelSize; } } QImage image = convertImageMap(raw.data(), imageSize, QSize(deviceWidth, deviceHeight)); image.setDevicePixelRatio(devicePixelRatioF()); + + if (!alpha.isNull()) { + QPainter painter(&image); + // transfer alphaMask to Alpha channel + painter.setCompositionMode(QPainter::CompositionMode_DestinationIn); + painter.drawImage(0, 0, alpha); + } + return image; } +QImage KisVisualColorSelectorShape::renderAlphaMask() const +{ + return QImage(); +} + void KisVisualColorSelectorShape::mousePressEvent(QMouseEvent *e) { if (e->button() == Qt::LeftButton) { QPointF coordinates = convertWidgetCoordinateToShapeCoordinate(e->localPos()); setCursorPosition(coordinates, true); } else { e->ignore(); } } void KisVisualColorSelectorShape::mouseMoveEvent(QMouseEvent *e) { if (e->buttons() & Qt::LeftButton) { QPointF coordinates = convertWidgetCoordinateToShapeCoordinate(e->localPos()); setCursorPosition(coordinates, true); } else { e->ignore(); } } void KisVisualColorSelectorShape::mouseReleaseEvent(QMouseEvent *e) { if (e->button() != Qt::LeftButton) { e->ignore(); } } void KisVisualColorSelectorShape::paintEvent(QPaintEvent*) { QPainter painter(this); drawCursor(); painter.drawImage(0,0,m_d->fullSelector); } void KisVisualColorSelectorShape::resizeEvent(QResizeEvent *) { forceImageUpdate(); setMask(getMaskMap()); } KisVisualColorSelectorShape::Dimensions KisVisualColorSelectorShape::getDimensions() const { return m_d->dimension; } void KisVisualColorSelectorShape::setFullImage(QImage full) { m_d->fullSelector = full; } KoColor KisVisualColorSelectorShape::getCurrentColor() { const KisVisualColorSelector *selector = qobject_cast(parent()); if (selector) { return selector->convertShapeCoordsToKoColor(m_d->currentChannelValues); } return KoColor(m_d->colorSpace); } QVector KisVisualColorSelectorShape::getChannels() const { QVector channels(2); channels[0] = m_d->channel1; channels[1] = m_d->channel2; return channels; } diff --git a/libs/widgets/KisVisualColorSelectorShape.h b/libs/widgets/KisVisualColorSelectorShape.h index e9dd88bb71..840e835dae 100644 --- a/libs/widgets/KisVisualColorSelectorShape.h +++ b/libs/widgets/KisVisualColorSelectorShape.h @@ -1,220 +1,226 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_VISUAL_COLOR_SELECTOR_SHAPE_H #define KIS_VISUAL_COLOR_SELECTOR_SHAPE_H #include #include #include #include #include #include #include #include "KoColorDisplayRendererInterface.h" #include "KisVisualColorSelector.h" #include "KisColorSelectorConfiguration.h" /** * @brief The KisVisualColorSelectorShape class * A 2d widget can represent at maximum 2 coordinates. * So first decide howmany coordinates you need. (onedimensional, or twodimensional) * Then the model, (Channel, HSV, HSL, HSI, YUV). Channel is the raw color channels. * When it finds a non-implemented feature it'll return to Channel. * Then, select the channels you wish to be affected. This uses the model, so for cmyk * the channel is c=0, m=1, y=2, k=3, but for hsv, hue=0, sat=1, and val=2 * These can also be set with 'slotsetactive channels'. * Then finally, connect the displayrenderer, you can also do this with 'setdisplayrenderer' * * Either way, this class is made to be subclassed, with a few virtuals so that the geometry * can be calculated properly. */ class KisVisualColorSelectorShape : public QWidget { Q_OBJECT public: /** * @brief The Dimensions enum * Whether or not the shape is single or two dimensional. **/ enum Dimensions{onedimensional, twodimensional}; enum ColorModel{Channel, HSV, HSL, HSI, HSY, YUV}; explicit KisVisualColorSelectorShape(QWidget *parent, KisVisualColorSelectorShape::Dimensions dimension, const KoColorSpace *cs, int channel1, int channel2, const KoColorDisplayRendererInterface *displayRenderer = KoDumbColorDisplayRenderer::instance()); ~KisVisualColorSelectorShape() override; /** * @brief getCursorPosition * @return current cursor position in shape-coordinates. */ QPointF getCursorPosition(); /** * @brief getDimensions * @return whether this is a single or twodimensional widget. */ Dimensions getDimensions() const; /** * @brief getPixmap * @return the pixmap of the gradient, for drawing on with a subclass. * the pixmap will not change unless 'm_d->setPixmap=true' which is toggled by * refresh and update functions. */ bool imagesNeedUpdate() const; QImage getImageMap(); + const QImage getAlphaMask() const; /** * @brief setFullImage * Set the full widget image to be painted. * @param full this should be the full image. */ void setFullImage(QImage full); /** * @brief getCurrentColor * @return the current kocolor */ KoColor getCurrentColor(); /** * @brief setDisplayRenderer * disconnect the old display renderer if needed and connect the new one. * @param displayRenderer */ void setDisplayRenderer (const KoColorDisplayRendererInterface *displayRenderer); /** * @brief getColorFromConverter * @param c a koColor. * @return get the qcolor from the given kocolorusing this widget's display renderer. */ QColor getColorFromConverter(KoColor c); /** * @brief getSpaceForSquare * @param geom the full widget rectangle * @return rectangle with enough space for second widget */ virtual QRect getSpaceForSquare(QRect geom) = 0; virtual QRect getSpaceForCircle(QRect geom) = 0; virtual QRect getSpaceForTriangle(QRect geom) = 0; /** * @brief forceImageUpdate * force the image to recache. */ void forceImageUpdate(); /** * @brief setBorderWidth * set the border of the single dimensional selector. * @param width */ virtual void setBorderWidth(int width) = 0; /** * @brief getChannels * get used channels * @return */ QVector getChannels() const; /** * @brief setCursorPosition * Set the cursor to normalized shape coordinates. This will only repaint the cursor. * @param position normalized shape coordinates ([0,1] range, not yet transformed to actual channel values!) * @param signal if true, emit a sigCursorMoved signal */ void setCursorPosition(QPointF position, bool signal = false); /** * @brief setChannelValues * Set the current channel values; * Note that channel values controlled by the shape itself have no effect unless setCursor is true. * This will trigger a full widget repaint. * @param position normalized shape coordinates ([0,1] range) * these are not yet transformed to color space specific ranges! * @param setCursor if true, sets the cursor too, otherwise the shape-controlled channels are not set */ void setChannelValues(QVector4D channelValues, bool setCursor); Q_SIGNALS: void sigCursorMoved(QPointF pos); public Q_SLOTS: /** * @brief slotSetActiveChannels * Change the active channels if necessary. * @param channel1 used by single and twodimensional widgets. * @param channel2 only used by twodimensional widgets. */ void slotSetActiveChannels(int channel1, int channel2); protected: /** * @brief convertImageMap * convert image data containing raw KoColor data into a QImage * @param data must point to memory of size width()*height()*pixelSize * @param size the number of bytes to read from data, must match aforementioned cirteria * @return the converted QImage guaranteed to match the widget size (black content on failure) */ QImage convertImageMap(const quint8 *rawColor, quint32 bufferSize, QSize imgSize) const; /** * @brief renderBackground * Render the widget background visible inside the widget's mask in current color space * Rendering shall be done with the conversion functions of KisVisualColorSelector * @param data points to zero-initialized memory of size width()*height()*pixelSize * @param pixelSize the data size to transfer from KoColor::data() to data per pixel * in the current color space * @param channelValues the normalized channel values of the currently picked color */ virtual QImage renderBackground(const QVector4D &channelValues, quint32 pixelSize) const; + /** + * @brief render the alpha mask for the widget background + * the returned image is expected to be QImage::Format_Alpha8 + */ + virtual QImage renderAlphaMask() const; void mousePressEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void paintEvent(QPaintEvent*) override; void resizeEvent(QResizeEvent *) override; private: struct Private; const QScopedPointer m_d; /** * @brief convertShapeCoordinateToWidgetCoordinate * @return take the position in the shape and convert it to screen coordinates. */ virtual QPointF convertShapeCoordinateToWidgetCoordinate(QPointF) const = 0; /** * @brief convertWidgetCoordinateToShapeCoordinate * Convert a coordinate in the widget's height/width to a shape coordinate. * @param coordinate the position your wish to have the shape coordinates of. */ virtual QPointF convertWidgetCoordinateToShapeCoordinate(QPointF coordinate) const = 0; /** * @brief getPixmap * @return the pixmap of this shape. */ virtual QRegion getMaskMap() = 0; virtual void drawCursor() = 0; }; #endif diff --git a/libs/widgets/KisVisualEllipticalSelectorShape.cpp b/libs/widgets/KisVisualEllipticalSelectorShape.cpp index aa0c94ad58..921e279773 100644 --- a/libs/widgets/KisVisualEllipticalSelectorShape.cpp +++ b/libs/widgets/KisVisualEllipticalSelectorShape.cpp @@ -1,282 +1,240 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisVisualEllipticalSelectorShape.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoColorConversions.h" #include "KoColorDisplayRendererInterface.h" #include "KoChannelInfo.h" #include #include #include "kis_signal_compressor.h" #include "kis_debug.h" #include "kis_global.h" KisVisualEllipticalSelectorShape::KisVisualEllipticalSelectorShape(QWidget *parent, Dimensions dimension, const KoColorSpace *cs, int channel1, int channel2, const KoColorDisplayRendererInterface *displayRenderer, int barWidth, singelDTypes d) : KisVisualColorSelectorShape(parent, dimension, cs, channel1, channel2, displayRenderer) { //qDebug() << "creating KisVisualEllipticalSelectorShape" << this; m_type = d; m_barWidth = barWidth; } KisVisualEllipticalSelectorShape::~KisVisualEllipticalSelectorShape() { //qDebug() << "deleting KisVisualEllipticalSelectorShape" << this; } QSize KisVisualEllipticalSelectorShape::sizeHint() const { return QSize(180,180); } void KisVisualEllipticalSelectorShape::setBorderWidth(int width) { m_barWidth = width; forceImageUpdate(); update(); } QRect KisVisualEllipticalSelectorShape::getSpaceForSquare(QRect geom) { int sizeValue = qMin(width(),height()); QRect b(geom.left(), geom.top(), sizeValue, sizeValue); QLineF radius(b.center(), QPointF(b.left()+m_barWidth, b.center().y()) ); radius.setAngle(135); QPointF tl = radius.p2(); radius.setAngle(315); QPointF br = radius.p2(); QRect r(tl.toPoint(), br.toPoint()); return r; } QRect KisVisualEllipticalSelectorShape::getSpaceForCircle(QRect geom) { int sizeValue = qMin(width(),height()); QRect b(geom.left(), geom.top(), sizeValue, sizeValue); QPointF tl = QPointF (b.topLeft().x()+m_barWidth, b.topLeft().y()+m_barWidth); QPointF br = QPointF (b.bottomRight().x()-m_barWidth, b.bottomRight().y()-m_barWidth); QRect r(tl.toPoint(), br.toPoint()); return r; } QRect KisVisualEllipticalSelectorShape::getSpaceForTriangle(QRect geom) { int sizeValue = qMin(width(),height()); QRect b(geom.left(), geom.top(), sizeValue, sizeValue); QLineF radius(b.center(), QPointF(b.left()+m_barWidth, b.center().y()) ); radius.setAngle(90);//point at yellowgreen :) QPointF t = radius.p2(); radius.setAngle(330);//point to purple :) QPointF br = radius.p2(); radius.setAngle(210);//point to cerulean :) QPointF bl = radius.p2(); QPointF tl = QPoint(bl.x(),t.y()); QRect r(tl.toPoint(), br.toPoint()); return r; } QPointF KisVisualEllipticalSelectorShape::convertShapeCoordinateToWidgetCoordinate(QPointF coordinate) const { qreal offset = 7.0; qreal a = (qreal)width()*0.5; QPointF center(a, a); QLineF line(center, QPoint((m_barWidth*0.5),a)); qreal angle = coordinate.x()*360.0; angle = 360.0 - fmod(angle+180.0, 360.0); if (m_type==KisVisualEllipticalSelectorShape::borderMirrored) { angle = (coordinate.x()/2)*360.0; angle = fmod((angle+270.0), 360.0); } line.setAngle(angle); if (getDimensions()!=KisVisualColorSelectorShape::onedimensional) { line.setLength(qMin(coordinate.y()*(a-offset), a-offset)); } return line.p2(); } QPointF KisVisualEllipticalSelectorShape::convertWidgetCoordinateToShapeCoordinate(QPointF coordinate) const { //default implementation: qreal x = 0.5; qreal y = 1.0; qreal offset = 7.0; QPointF center = QRectF(QPointF(0.0, 0.0), this->size()).center(); qreal a = (qreal(this->width()) / qreal(2)); qreal xRel = center.x()-coordinate.x(); qreal yRel = center.y()-coordinate.y(); qreal radius = sqrt(xRel*xRel+yRel*yRel); if (m_type!=KisVisualEllipticalSelectorShape::borderMirrored){ qreal angle = atan2(yRel, xRel); angle = kisRadiansToDegrees(angle); angle = fmod(angle+360, 360.0); x = angle/360.0; if (getDimensions()==KisVisualColorSelectorShape::twodimensional) { y = qBound(0.0,radius/(a-offset), 1.0); } } else { qreal angle = atan2(xRel, yRel); angle = kisRadiansToDegrees(angle); angle = fmod(angle+180, 360.0); if (angle>180.0) { angle = 360.0-angle; } x = (angle/360.0)*2; if (getDimensions()==KisVisualColorSelectorShape::twodimensional) { y = qBound(0.0,(radius+offset)/a, 1.0); } } return QPointF(x, y); } QRegion KisVisualEllipticalSelectorShape::getMaskMap() { QRegion mask = QRegion(0,0,width(),height(), QRegion::Ellipse); if (getDimensions()==KisVisualColorSelectorShape::onedimensional) { mask = mask.subtracted(QRegion(m_barWidth, m_barWidth, width()-(m_barWidth*2), height()-(m_barWidth*2), QRegion::Ellipse)); } return mask; } -QImage KisVisualEllipticalSelectorShape::renderBackground(const QVector4D &channelValues, quint32 pixelSize) const +QImage KisVisualEllipticalSelectorShape::renderAlphaMask() const { - const KisVisualColorSelector *selector = qobject_cast(parent()); - Q_ASSERT(selector); // Hi-DPI aware rendering requires that we determine the device pixel dimension; // actual widget size in device pixels is not accessible unfortunately, it might be 1px smaller... - const qreal deviceDivider = 1.0 / devicePixelRatioF(); const int deviceWidth = qCeil(width() * devicePixelRatioF()); const int deviceHeight = qCeil(height() * devicePixelRatioF()); - // optimization assumes widget is (close to) square, but should still render correctly as ellipse - int rMaxSquare = qRound(qMax(deviceWidth, deviceHeight) * 0.5f + 0.5f); - rMaxSquare *= rMaxSquare; - int rMinSquare = 0; - if (getDimensions() == Dimensions::onedimensional) - { - rMinSquare = qMax(0, qRound(qMin(deviceWidth, deviceHeight) * 0.5f - m_barWidth * devicePixelRatioF())); - rMinSquare *= rMinSquare; - } - int cx = deviceWidth/2; - int cy = deviceHeight/2; - // Fill a buffer with the right kocolors - quint32 imageSize = deviceWidth * deviceHeight * pixelSize; - QScopedArrayPointer raw(new quint8[imageSize] {}); - quint8 *dataPtr = raw.data(); - bool is2D = (getDimensions() == Dimensions::twodimensional); - QVector4D coordinates = channelValues; - QVector channels = getChannels(); - for (int y = 0; y < deviceHeight; y++) { - int dy = y - cy; - for (int x=0; x < deviceWidth; x++) { - int dx = x - cx; - int radSquare = dx*dx + dy*dy; - if (radSquare >= rMinSquare && radSquare < rMaxSquare) - { - QPointF newcoordinate = convertWidgetCoordinateToShapeCoordinate(QPointF(x, y) * deviceDivider); - coordinates[channels.at(0)] = newcoordinate.x(); - if (is2D){ - coordinates[channels.at(1)] = newcoordinate.y(); - } - KoColor c = selector->convertShapeCoordsToKoColor(coordinates); - memcpy(dataPtr, c.data(), pixelSize); - } - dataPtr += pixelSize; - } - } - QImage image = convertImageMap(raw.data(), imageSize, QSize(deviceWidth, deviceHeight)); - image.setDevicePixelRatio(devicePixelRatioF()); - // cleanup edges by erasing with antialiased circles - QPainter painter(&image); + QImage alphaMask(deviceWidth, deviceHeight, QImage::Format_Alpha8); + alphaMask.fill(0); + alphaMask.setDevicePixelRatio(devicePixelRatioF()); + QPainter painter(&alphaMask); painter.setRenderHint(QPainter::Antialiasing); - painter.setCompositionMode(QPainter::CompositionMode_Clear); - QPen pen; - pen.setWidth(5); - painter.setPen(pen); - painter.drawEllipse(QRect(0,0,width(),height())); - - if (getDimensions()==KisVisualColorSelectorShape::onedimensional) { - QRect innerRect(m_barWidth, m_barWidth, width()-(m_barWidth*2), height()-(m_barWidth*2)); - painter.setBrush(Qt::SolidPattern); - painter.drawEllipse(innerRect); + painter.setBrush(Qt::white); + painter.setPen(Qt::NoPen); + painter.drawEllipse(2, 2, width() - 4, height() - 4); + //painter.setBrush(Qt::black); + if (getDimensions() == KisVisualColorSelectorShape::onedimensional) { + painter.setCompositionMode(QPainter::CompositionMode_Clear); + painter.drawEllipse(m_barWidth - 2, m_barWidth - 2, width() - 2*(m_barWidth-2), height() - 2*(m_barWidth-2)); } - return image; + return alphaMask; } void KisVisualEllipticalSelectorShape::drawCursor() { //qDebug() << this << "KisVisualEllipticalSelectorShape::drawCursor: image needs update" << imagesNeedUpdate(); QPointF cursorPoint = convertShapeCoordinateToWidgetCoordinate(getCursorPosition()); QImage fullSelector = getImageMap(); QColor col = getColorFromConverter(getCurrentColor()); QPainter painter; painter.begin(&fullSelector); painter.setRenderHint(QPainter::Antialiasing); QBrush fill; fill.setStyle(Qt::SolidPattern); int cursorwidth = 5; if (m_type==KisVisualEllipticalSelectorShape::borderMirrored) { painter.setPen(Qt::white); fill.setColor(Qt::white); painter.setBrush(fill); painter.drawEllipse(cursorPoint, cursorwidth, cursorwidth); QPointF mirror(width() - cursorPoint.x(), cursorPoint.y()); painter.drawEllipse(mirror, cursorwidth, cursorwidth); fill.setColor(col); painter.setPen(Qt::black); painter.setBrush(fill); painter.drawEllipse(cursorPoint, cursorwidth-1, cursorwidth-1); painter.drawEllipse(mirror, cursorwidth-1, cursorwidth-1); } else { painter.setPen(Qt::white); fill.setColor(Qt::white); painter.setBrush(fill); painter.drawEllipse(cursorPoint, cursorwidth, cursorwidth); fill.setColor(col); painter.setPen(Qt::black); painter.setBrush(fill); painter.drawEllipse(cursorPoint, cursorwidth-1.0, cursorwidth-1.0); } painter.end(); setFullImage(fullSelector); } diff --git a/libs/widgets/KisVisualEllipticalSelectorShape.h b/libs/widgets/KisVisualEllipticalSelectorShape.h index d28a3c619f..ed2028f1cd 100644 --- a/libs/widgets/KisVisualEllipticalSelectorShape.h +++ b/libs/widgets/KisVisualEllipticalSelectorShape.h @@ -1,73 +1,73 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KISVISUALCOLORSELECTOR_H #define KISVISUALCOLORSELECTOR_H #include #include #include #include #include #include #include #include "KoColorDisplayRendererInterface.h" #include "KisColorSelectorConfiguration.h" #include "KisVisualColorSelectorShape.h" class KisVisualEllipticalSelectorShape : public KisVisualColorSelectorShape { Q_OBJECT public: enum singelDTypes{border, borderMirrored}; explicit KisVisualEllipticalSelectorShape(QWidget *parent, Dimensions dimension, const KoColorSpace *cs, int channel1, int channel2, const KoColorDisplayRendererInterface *displayRenderer = KoDumbColorDisplayRenderer::instance(), int barWidth=20, KisVisualEllipticalSelectorShape::singelDTypes d = KisVisualEllipticalSelectorShape::border ); ~KisVisualEllipticalSelectorShape() override; void setBorderWidth(int width) override; /** * @brief getSpaceForSquare * @param geom the full widget rectangle * @return rectangle with enough space for second widget */ QRect getSpaceForSquare(QRect geom) override; QRect getSpaceForCircle(QRect geom) override; QRect getSpaceForTriangle(QRect geom) override; protected: - virtual QImage renderBackground(const QVector4D &channelValues, quint32 pixelSize) const override; + QImage renderAlphaMask() const override; private: QPointF convertShapeCoordinateToWidgetCoordinate(QPointF coordinate) const override; QPointF convertWidgetCoordinateToShapeCoordinate(QPointF coordinate) const override; singelDTypes m_type; int m_barWidth; QRegion getMaskMap() override; void drawCursor() override; QSize sizeHint() const override; }; #endif // KISVISUALCOLORSELECTOR_H diff --git a/libs/widgets/KisVisualTriangleSelectorShape.cpp b/libs/widgets/KisVisualTriangleSelectorShape.cpp index 84d1a42664..efd2e2ac0f 100644 --- a/libs/widgets/KisVisualTriangleSelectorShape.cpp +++ b/libs/widgets/KisVisualTriangleSelectorShape.cpp @@ -1,205 +1,177 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisVisualTriangleSelectorShape.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "KoColorConversions.h" #include "KoColorDisplayRendererInterface.h" #include "KoChannelInfo.h" #include #include #include "kis_signal_compressor.h" #include "kis_debug.h" #include "kis_global.h" KisVisualTriangleSelectorShape::KisVisualTriangleSelectorShape(QWidget *parent, Dimensions dimension, const KoColorSpace *cs, int channel1, int channel2, const KoColorDisplayRendererInterface *displayRenderer, - int barwidth) - : KisVisualColorSelectorShape(parent, dimension, cs, channel1, channel2, displayRenderer) + int margin) + : KisVisualColorSelectorShape(parent, dimension, cs, channel1, channel2, displayRenderer), + m_margin(margin) { //qDebug() << "creating KisVisualTriangleSelectorShape" << this; - m_barWidth = barwidth; - setTriangle(); } KisVisualTriangleSelectorShape::~KisVisualTriangleSelectorShape() { //qDebug() << "deleting KisVisualTriangleSelectorShape" << this; } -void KisVisualTriangleSelectorShape::setBorderWidth(int width) +void KisVisualTriangleSelectorShape::setBorderWidth(int /*width*/) { - m_barWidth = width; + // triangle doesn't have a 1-dimensional mode } QRect KisVisualTriangleSelectorShape::getSpaceForSquare(QRect geom) { return geom; } QRect KisVisualTriangleSelectorShape::getSpaceForCircle(QRect geom) { return geom; } QRect KisVisualTriangleSelectorShape::getSpaceForTriangle(QRect geom) { return geom; } -void KisVisualTriangleSelectorShape::setTriangle() -{ - QPoint apex = QPoint (width()*0.5,0); - QPolygon triangle; - triangle<< QPoint(0,height()) << apex << QPoint(width(),height()) << QPoint(0,height()); - m_triangle = triangle; - QLineF a(triangle.at(0),triangle.at(1)); - QLineF b(triangle.at(0),triangle.at(2)); - QLineF ap(triangle.at(2), a.pointAt(0.5)); - QLineF bp(triangle.at(1), b.pointAt(0.5)); - QPointF intersect; - ap.intersect(bp,&intersect); - m_center = intersect; - QLineF r(triangle.at(0), intersect); - m_radius = r.length(); -} QPointF KisVisualTriangleSelectorShape::convertShapeCoordinateToWidgetCoordinate(QPointF coordinate) const { - qreal offset=7.0;//the offset is so we get a nice little border that allows selecting extreme colors better. - qreal yOffset = (cos(kisDegreesToRadians(30))*offset)*2; - qreal xOffset = qFloor(sin(kisDegreesToRadians(30))*offset); - qreal y = qMax(qMin((coordinate.y()*(height()-yOffset-offset))+yOffset, (qreal)height()-offset),yOffset); - - qreal triWidth = width(); - qreal horizontalLineLength = ((y-yOffset)*(2./sqrt(3.))); - qreal horizontalLineStart = (triWidth*0.5)-(horizontalLineLength*0.5); - qreal relativeX = qMin(coordinate.x()*(horizontalLineLength), horizontalLineLength); - qreal x = qMax(relativeX + horizontalLineStart + xOffset, horizontalLineStart+xOffset); - if (y<=yOffset){ - x = 0.5*width(); - } + // margin serves to render the cursor, and triangle is rendered 1px larger than its active area + qreal offset = m_margin + 1.0; + + qreal y = (coordinate.y() * (height() - 1 - 2 * offset)) + offset; - return QPointF(x,y); + qreal triWidth = width() - 1 - 2 * offset; + qreal horizontalLineLength = coordinate.y() * triWidth; + qreal horizontalLineStart = offset + 0.5 * (triWidth - horizontalLineLength); + + qreal x = coordinate.x() * horizontalLineLength + horizontalLineStart; + + return QPointF(x, y); } QPointF KisVisualTriangleSelectorShape::convertWidgetCoordinateToShapeCoordinate(QPointF coordinate) const { - //default implementation: gotten from the kotrianglecolorselector/kis_color_selector_triangle. - qreal x = 0.5; - qreal y = 0.5; - qreal offset=7.0; //the offset is so we get a nice little border that allows selecting extreme colors better. - qreal yOffset = (cos(kisDegreesToRadians(30))*offset)*2; - qreal xOffset = qFloor(sin(kisDegreesToRadians(30))*offset); + // margin serves to render the cursor, and triangle is rendered 1px larger than its active area + qreal offset = m_margin + 1.0; - y = qMin(qMax((qreal)coordinate.y()-yOffset, 0.0)/(height()-yOffset-offset), 1.0); + qreal x = 0.5; + qreal y = qBound(0.0, (coordinate.y() - offset)/(height() - 1 - 2 * offset), 1.0); - qreal triWidth = width(); - qreal horizontalLineLength = ((qreal)coordinate.y()-yOffset)*(2./sqrt(3.)); - qreal horizontalLineStart = (triWidth*0.5)-(horizontalLineLength*0.5); + if (y > 0) { + qreal triWidth = width() - 1 - 2 * offset; + qreal horizontalLineLength = y * triWidth; + qreal horizontalLineStart = offset + 0.5 * (triWidth - horizontalLineLength); - qreal relativeX = qMax((qreal)coordinate.x()-xOffset-horizontalLineStart,0.0); - x = qMin(relativeX/horizontalLineLength, 1.0); - if (coordinate.y()<=yOffset){ - x = 0.5; + x = qBound(0.0, (coordinate.x() - horizontalLineStart) / horizontalLineLength, 1.0); } + return QPointF(x, y); } QRegion KisVisualTriangleSelectorShape::getMaskMap() { - QRegion mask = QRegion(m_triangle); - //QRegion mask = QRegion(); - //if (getDimensions()==KisVisualColorSelectorShape::onedimensional) { - // mask = mask.subtracted(QRegion(m_barWidth, m_barWidth, width()-(m_barWidth*2), height()-(m_barWidth*2))); - //} - return mask; + const int cursorWidth = qMax(2 * m_margin, 2); + QPolygon maskPoly; + maskPoly << QPoint(qFloor(0.5 * (width() - cursorWidth)), 0) + << QPoint(qCeil(0.5 * (width() + cursorWidth)), 0) + << QPoint(width(), height() - cursorWidth) + << QPoint(width(), height()) + << QPoint(0, height()) + << QPoint(0, height() - cursorWidth); + + return QRegion(maskPoly); } -void KisVisualTriangleSelectorShape::resizeEvent(QResizeEvent *e) +QImage KisVisualTriangleSelectorShape::renderAlphaMask() const { - //qDebug() << this << "KisVisualTriangleSelectorShape::resizeEvent(QResizeEvent *)"; - setTriangle(); - KisVisualColorSelectorShape::resizeEvent(e); + // Hi-DPI aware rendering requires that we determine the device pixel dimension; + // actual widget size in device pixels is not accessible unfortunately, it might be 1px smaller... + const int deviceWidth = qCeil(width() * devicePixelRatioF()); + const int deviceHeight = qCeil(height() * devicePixelRatioF()); + + QImage alphaMask(deviceWidth, deviceHeight, QImage::Format_Alpha8); + alphaMask.fill(0); + alphaMask.setDevicePixelRatio(devicePixelRatioF()); + QPainter painter(&alphaMask); + painter.setRenderHint(QPainter::Antialiasing); + painter.setBrush(Qt::white); + painter.setPen(Qt::NoPen); + QPointF triangle[3] = { + QPointF(0.5 * width(), m_margin), + QPointF(m_margin, height() - m_margin), + QPointF(width() - m_margin, height() - m_margin), + }; + painter.drawConvexPolygon(triangle, 3); + + return alphaMask; } void KisVisualTriangleSelectorShape::drawCursor() { //qDebug() << this << "KisVisualTriangleSelectorShape::drawCursor: image needs update" << imagesNeedUpdate(); QPointF cursorPoint = convertShapeCoordinateToWidgetCoordinate(getCursorPosition()); QImage fullSelector = getImageMap(); QColor col = getColorFromConverter(getCurrentColor()); - QPainter painter; - painter.begin(&fullSelector); + QPainter painter(&fullSelector); painter.setRenderHint(QPainter::Antialiasing); - painter.save(); - painter.setCompositionMode(QPainter::CompositionMode_Clear); - QPen pen; - pen.setWidth(10); - painter.setPen(pen); - painter.drawPolygon(m_triangle); - painter.restore(); - - //QPainterPath path; - QBrush fill; - fill.setStyle(Qt::SolidPattern); + QBrush fill(Qt::SolidPattern); int cursorwidth = 5; - //QRect innerRect(m_barWidth, m_barWidth, width()-(m_barWidth*2), height()-(m_barWidth*2)); - /*if(m_type==KisVisualTriangleSelectorShape::borderMirrored){ - painter.setPen(Qt::white); - fill.setColor(Qt::white); - painter.setBrush(fill); - painter.drawEllipse(cursorPoint, cursorwidth, cursorwidth); - QPoint mirror(innerRect.center().x()+(innerRect.center().x()-cursorPoint.x()),cursorPoint.y()); - painter.drawEllipse(mirror, cursorwidth, cursorwidth); - fill.setColor(col); - painter.setPen(Qt::black); - painter.setBrush(fill); - painter.drawEllipse(cursorPoint, cursorwidth-1, cursorwidth-1); - painter.drawEllipse(mirror, cursorwidth-1, cursorwidth-1); - - } else {*/ + painter.setPen(Qt::white); fill.setColor(Qt::white); painter.setBrush(fill); painter.drawEllipse(cursorPoint, cursorwidth, cursorwidth); fill.setColor(col); painter.setPen(Qt::black); painter.setBrush(fill); painter.drawEllipse(cursorPoint, cursorwidth-1.0, cursorwidth-1.0); - //} + painter.end(); setFullImage(fullSelector); } diff --git a/libs/widgets/KisVisualTriangleSelectorShape.h b/libs/widgets/KisVisualTriangleSelectorShape.h index 387f6b82b2..b36951491a 100644 --- a/libs/widgets/KisVisualTriangleSelectorShape.h +++ b/libs/widgets/KisVisualTriangleSelectorShape.h @@ -1,74 +1,71 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_VISUAL_TRIANGLE_SELECTOR_SHAPE_H #define KIS_VISUAL_TRIANGLE_SELECTOR_SHAPE_H #include #include #include #include #include #include #include #include "KoColorDisplayRendererInterface.h" #include "KisColorSelectorConfiguration.h" #include "KisVisualColorSelectorShape.h" class KisVisualTriangleSelectorShape : public KisVisualColorSelectorShape { Q_OBJECT public: explicit KisVisualTriangleSelectorShape(QWidget *parent, Dimensions dimension, const KoColorSpace *cs, int channel1, int channel2, const KoColorDisplayRendererInterface *displayRenderer = KoDumbColorDisplayRenderer::instance(), - int barwidth=20 + int margin = 5 ); ~KisVisualTriangleSelectorShape() override; - void setBorderWidth(int width) override; - void setTriangle(); + void setBorderWidth(int /*width*/) override; /** * @brief getSpaceForSquare * @param geom the full widget rectangle * @return rectangle with enough space for second widget */ QRect getSpaceForSquare(QRect geom) override; QRect getSpaceForCircle(QRect geom) override; QRect getSpaceForTriangle(QRect geom) override; protected: - void resizeEvent(QResizeEvent *e) override; + QImage renderAlphaMask() const override; private: QPointF convertShapeCoordinateToWidgetCoordinate(QPointF coordinate) const override; QPointF convertWidgetCoordinateToShapeCoordinate(QPointF coordinate) const override; - int m_barWidth; - QPolygon m_triangle; - QPointF m_center; - qreal m_radius; QRegion getMaskMap() override; void drawCursor() override; + + int m_margin { 5 }; }; #endif