Commit f09bc200 authored by Boudewijn Rempt's avatar Boudewijn Rempt

FEATURE: add a blending optioon to the color picker

This patch adds new 'blending' functionality to the color picker (both
the dedicated tool and the ctrl-activated one) along with the associated
"blend" slider to the color picker's tool options GUI.

Patch by Emmet & Eoin O'Neill <emmetoneill.pdx@gmail.com> Thanks!

CCMAIL:emmetoneill.pdx@gmail.com
CCMAIL:kimageshop@kde.org
Differential Revision: https://phabricator.kde.org/D11895
parent ad0f4988
......@@ -399,8 +399,14 @@ void KisToolPaint::addPickerJob(const PickingJob &pickingJob)
KisPaintDeviceSP device = fromCurrentNode ?
currentNode()->colorPickSourceDevice() : image()->projection();
// Used for color picker blending.
KoColor currentColor = canvas()->resourceManager()->foregroundColor();
if( pickingJob.action == PickBgNode || pickingJob.action == PickBgImage ){
currentColor = canvas()->resourceManager()->backgroundColor();
}
image()->addJob(m_pickerStrokeId,
new KisColorPickerStrokeStrategy::Data(device, imagePoint));
new KisColorPickerStrokeStrategy::Data(device, imagePoint, currentColor));
}
void KisToolPaint::beginAlternateAction(KoPointerEvent *event, AlternateAction action)
......
......@@ -175,7 +175,6 @@ private:
bool isPickingAction(AlternateAction action);
struct PickingJob {
PickingJob() {}
PickingJob(QPointF _documentPixel,
......
/*
* Copyright (c) 2009 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2018 Emmet & Eoin O'Neill <emmetoneill.pdx@gmail.com>
*
* 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
......@@ -15,15 +16,11 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <kis_tool_utils.h>
#include <KoColorSpace.h>
#include <KoMixColorsOp.h>
#include <kis_paint_device.h>
#include <kis_layer.h>
#include <kis_group_layer.h>
#include <kis_wrapped_rect.h>
#include <kis_image.h>
#include <kis_transaction.h>
#include <kis_sequential_iterator.h>
#include <kis_properties_configuration.h>
......@@ -32,12 +29,13 @@
namespace KisToolUtils {
bool pick(KisPaintDeviceSP dev, const QPoint& pos, KoColor *color, int radius)
bool pick(KisPaintDeviceSP dev, const QPoint &pos, KoColor *color, KoColor *previousColor, int radius, int blend)
{
KIS_ASSERT(dev);
const KoColorSpace* cs = dev->colorSpace();
KoColor pickedColor(Qt::transparent, cs);
// Ctrl picker sampling radius.
if (radius <= 1) {
dev->pixel(pos.x(), pos.y(), &pickedColor);
} else {
......@@ -59,9 +57,25 @@ namespace KisToolUtils {
}
}
const quint8** cpixels = const_cast<const quint8**>(pixels.constData());
const quint8 **cpixels = const_cast<const quint8**>(pixels.constData());
cs->mixColorsOp()->mixColors(cpixels, pixels.size(), pickedColor.data());
}
// Ctrl picker color blending.
if (previousColor && blend < 100) {
//Scale from 0..100% to 0..255 range for mixOp weights.
quint8 blendScaled = static_cast<quint8>(blend * 2.55f);
const quint8 *colors[2];
colors[0] = previousColor->data();
colors[1] = pickedColor.data();
qint16 weights[2];
weights[0] = 255 - blendScaled;
weights[1] = blendScaled;
const KoMixColorsOp *mixOp = dev->colorSpace()->mixColorsOp();
mixOp->mixColors(colors, weights, 2, pickedColor.data());
}
pickedColor.convertTo(dev->compositionSourceColorSpace());
......@@ -146,6 +160,7 @@ namespace KisToolUtils {
, normaliseValues(false)
, sampleMerged(true)
, radius(1)
, blend(100)
{
}
......@@ -163,6 +178,7 @@ namespace KisToolUtils {
props.setProperty("normaliseValues", normaliseValues);
props.setProperty("sampleMerged", sampleMerged);
props.setProperty("radius", radius);
props.setProperty("blend", blend);
KConfigGroup config = KSharedConfig::openConfig()->group(CONFIG_GROUP_NAME);
......@@ -182,6 +198,7 @@ namespace KisToolUtils {
normaliseValues = props.getBool("normaliseValues", false);
sampleMerged = props.getBool("sampleMerged", !defaultActivation ? false : true);
radius = props.getInt("radius", 1);
blend = props.getInt("blend", 100);
}
}
/*
* Copyright (c) 2009 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2018 Emmet & Eoin O'Neill <emmetoneill.pdx@gmail.com>
*
* 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
......@@ -15,16 +16,15 @@
* 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_TOOL_UTILS_H
#define KIS_TOOL_UTILS_H
#include <QPoint>
#include <KoColor.h>
#include <kis_types.h>
#include <kritaui_export.h>
#include <kis_selection.h>
class QPoint;
class KoColor;
namespace KisToolUtils {
......@@ -37,6 +37,7 @@ struct KRITAUI_EXPORT ColorPickerConfig {
bool normaliseValues;
bool sampleMerged;
int radius;
int blend;
void save(bool defaultActivation = true) const;
void load(bool defaultActivation = true);
......@@ -47,7 +48,8 @@ private:
/**
* return the color at the given position on the given paint device.
*/
bool KRITAUI_EXPORT pick(KisPaintDeviceSP dev, const QPoint& pos, KoColor *color, int radius = 1);
bool KRITAUI_EXPORT pick(KisPaintDeviceSP dev, const QPoint &pos, KoColor *color,
KoColor *previousColor = nullptr, int radius = 1, int blend = 100);
/**
* Recursively search a node with a non-transparent pixel
......
......@@ -18,22 +18,16 @@
#include "kis_color_picker_stroke_strategy.h"
#include <KoColor.h>
#include "kis_debug.h"
#include "kis_tool_utils.h"
#include <kis_wrapped_rect.h>
#include "kis_default_bounds.h"
#include "kis_paint_device.h"
struct KisColorPickerStrokeStrategy::Private
{
Private() : shouldSkipWork(false) {}
bool shouldSkipWork;
int radius = 1;
int blend = 100;
};
KisColorPickerStrokeStrategy::KisColorPickerStrokeStrategy(int lod)
......@@ -46,6 +40,7 @@ KisColorPickerStrokeStrategy::KisColorPickerStrokeStrategy(int lod)
config.load();
m_d->radius = qMax(1, qRound(config.radius * KisLodTransform::lodToScale(lod)));
m_d->blend = config.blend;
}
KisColorPickerStrokeStrategy::~KisColorPickerStrokeStrategy()
......@@ -60,7 +55,8 @@ void KisColorPickerStrokeStrategy::doStrokeCallback(KisStrokeJobData *data)
KIS_ASSERT_RECOVER_RETURN(d);
KoColor color;
bool result = KisToolUtils::pick(d->dev, d->pt, &color, m_d->radius);
KoColor previous = d->currentColor;
bool result = KisToolUtils::pick(d->dev, d->pt, &color, &previous, m_d->radius, m_d->blend);
Q_UNUSED(result);
emit sigColorUpdated(color);
......@@ -76,4 +72,3 @@ KisStrokeStrategy* KisColorPickerStrokeStrategy::createLodClone(int levelOfDetai
Qt::DirectConnection);
return lodStrategy;
}
......@@ -20,12 +20,9 @@
#define __KIS_COLOR_PICKER_STROKE_STRATEGY_H
#include <QObject>
#include <QScopedPointer>
#include "kis_simple_stroke_strategy.h"
#include "kis_lod_transform.h"
class KoColor;
#include "KoColor.h"
class KisColorPickerStrokeStrategy : public QObject, public KisSimpleStrokeStrategy
{
......@@ -33,19 +30,20 @@ class KisColorPickerStrokeStrategy : public QObject, public KisSimpleStrokeStrat
public:
class Data : public KisStrokeJobData {
public:
Data(KisPaintDeviceSP _dev, const QPoint _pt)
: dev(_dev), pt(_pt)
Data(KisPaintDeviceSP _dev, const QPoint _pt, KoColor _currentColor)
: dev(_dev), pt(_pt), currentColor(_currentColor)
{}
KisStrokeJobData* createLodClone(int levelOfDetail) override {
KisLodTransform t(levelOfDetail);
const QPoint realPoint = t.map(pt);
return new Data(dev, realPoint);
return new Data(dev, realPoint, currentColor);
}
KisPaintDeviceSP dev;
QPoint pt;
KoColor currentColor; // Used for color picker blending.
};
public:
KisColorPickerStrokeStrategy(int lod = 0);
......
......@@ -2,6 +2,7 @@
* Copyright (c) 1999 Matthias Elter <me@kde.org>
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
* Copyright (c) 2018 Emmet & Eoin O'Neill <emmetoneill.pdx@gmail.com>
*
* 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
......@@ -19,55 +20,27 @@
*/
#include "kis_tool_colorpicker.h"
#include <string.h>
#include <boost/thread/locks.hpp>
#include <QPoint>
#include <QLayout>
#include <QCheckBox>
#include <QComboBox>
#include <QSpinBox>
#include <QListWidget>
#include <QList>
#include <QWidget>
#include <QVector>
#include <ksharedconfig.h>
#include <kconfiggroup.h>
#include <klocalizedstring.h>
#include <QMessageBox>
#include "kis_layer.h"
#include "kis_cursor.h"
#include "kis_image.h"
#include "kis_paint_device.h"
#include "kis_properties_configuration.h"
#include "KisDocument.h"
#include "kis_canvas2.h"
#include "KisReferenceImagesLayer.h"
#include "KoPointerEvent.h"
#include "KoCanvasBase.h"
#include "kis_random_accessor_ng.h"
#include "KoColor.h"
#include "KoResourceServerProvider.h"
#include <resources/KoColorSet.h>
#include <KoChannelInfo.h>
#include <KoMixColorsOp.h>
#include "kis_wrapped_rect.h"
#include "kis_tool_utils.h"
namespace
{
// The location of the sample all visible layers in the combobox
// GUI ComboBox index constants
const int SAMPLE_MERGED = 0;
}
KisToolColorPicker::KisToolColorPicker(KoCanvasBase* canvas)
KisToolColorPicker::KisToolColorPicker(KoCanvasBase *canvas)
: KisTool(canvas, KisCursor::pickerCursor()),
m_config(new KisToolUtils::ColorPickerConfig)
{
......@@ -84,7 +57,7 @@ KisToolColorPicker::~KisToolColorPicker()
}
}
void KisToolColorPicker::paint(QPainter& gc, const KoViewConverter &converter)
void KisToolColorPicker::paint(QPainter &gc, const KoViewConverter &converter)
{
Q_UNUSED(gc);
Q_UNUSED(converter);
......@@ -99,6 +72,7 @@ void KisToolColorPicker::activate(ToolActivation activation, const QSet<KoShape*
KisTool::activate(activation, shapes);
}
void KisToolColorPicker::deactivate()
{
m_config->save(m_toolActivationSource == KisTool::DefaultActivation);
......@@ -106,7 +80,7 @@ void KisToolColorPicker::deactivate()
KisTool::deactivate();
}
void KisToolColorPicker::pickColor(const QPointF& pos)
void KisToolColorPicker::pickColor(const QPointF &pos)
{
if (m_colorPickerDelayTimer.isActive()) {
return;
......@@ -116,8 +90,7 @@ void KisToolColorPicker::pickColor(const QPointF& pos)
m_colorPickerDelayTimer.start(100);
}
QScopedPointer<boost::lock_guard<KisImage> > imageLocker;
QScopedPointer<boost::lock_guard<KisImage>> imageLocker;
m_pickedColor.setOpacity(0.0);
......@@ -147,6 +120,7 @@ void KisToolColorPicker::pickColor(const QPointF& pos)
dev = currentImage()->projection();
}
// Color sampling radius.
if (m_config->radius == 1) {
QPoint realPos = pos.toPoint();
if (currentImage()->wrapAroundModePermitted()) {
......@@ -156,7 +130,6 @@ void KisToolColorPicker::pickColor(const QPointF& pos)
dev->pixel(realPos.x(), realPos.y(), &m_pickedColor);
}
else {
const KoColorSpace *cs = dev->colorSpace();
int pixelSize = cs->pixelSize();
......@@ -181,7 +154,6 @@ void KisToolColorPicker::pickColor(const QPointF& pos)
}
}
const quint8 **cpixels = const_cast<const quint8 **>(pixels.constData());
cs->mixColorsOp()->mixColors(cpixels, pixels.size(), dstColor);
......@@ -190,20 +162,38 @@ void KisToolColorPicker::pickColor(const QPointF& pos)
delete[] dstColor;
}
m_pickedColor.convertTo(dev->compositionSourceColorSpace());
}
// Color blending.
if(m_config->blend < 100){
//Scale from 0..100% to 0..255 range for mixOp weights.
quint8 blendScaled = static_cast<quint8>(m_config->blend * 2.55f);
if (m_config->updateColor &&
m_pickedColor.opacityU8() != OPACITY_TRANSPARENT_U8) {
KoColor previousColor = canvas()->resourceManager()->foregroundColor();
KoColor publicColor = m_pickedColor;
publicColor.setOpacity(OPACITY_OPAQUE_U8);
const quint8 *colors[2];
colors[0] = previousColor.data();
colors[1] = m_pickedColor.data();
qint16 weights[2];
weights[0] = 255 - blendScaled;
weights[1] = blendScaled;
if (m_config->toForegroundColor) {
canvas()->resourceManager()->setResource(KoCanvasResourceManager::ForegroundColor, publicColor);
const KoMixColorsOp *mixOp = dev->colorSpace()->mixColorsOp();
mixOp->mixColors(colors, weights, 2, m_pickedColor.data());
}
else {
canvas()->resourceManager()->setResource(KoCanvasResourceManager::BackgroundColor, publicColor);
m_pickedColor.convertTo(dev->compositionSourceColorSpace());
if (m_config->updateColor &&
m_pickedColor.opacityU8() != OPACITY_TRANSPARENT_U8) {
KoColor publicColor = m_pickedColor;
publicColor.setOpacity(OPACITY_OPAQUE_U8);
if (m_config->toForegroundColor) {
canvas()->resourceManager()->setResource(KoCanvasResourceManager::ForegroundColor, publicColor);
}
else {
canvas()->resourceManager()->setResource(KoCanvasResourceManager::BackgroundColor, publicColor);
}
}
}
}
......@@ -225,7 +215,7 @@ void KisToolColorPicker::beginPrimaryAction(KoPointerEvent *event)
}
QPoint pos = convertToImagePixelCoordFloored(event);
// the color picking has to start in the visible part of the layer
// Color picking has to start in the visible part of the layer
if (!currentImage()->bounds().contains(pos) &&
!currentImage()->wrapAroundModePermitted()) {
event->ignore();
......@@ -256,7 +246,7 @@ void KisToolColorPicker::endPrimaryAction(KoPointerEvent *event)
ent.color = m_pickedColor;
// We don't ask for a name, too intrusive here
KoColorSet* palette = m_palettes.at(m_optionsWidget->cmbPalette->currentIndex());
KoColorSet *palette = m_palettes.at(m_optionsWidget->cmbPalette->currentIndex());
palette->add(ent);
if (!palette->save()) {
......@@ -265,7 +255,6 @@ void KisToolColorPicker::endPrimaryAction(KoPointerEvent *event)
}
}
struct PickedChannel {
QString name;
QString valueText;
......@@ -318,6 +307,9 @@ QWidget* KisToolColorPicker::createOptionWidget()
specialSpacer->setFixedSize(0, 0);
m_optionsWidget->layout()->addWidget(specialSpacer);
// Initialize blend KisSliderSpinBox
m_optionsWidget->blend->setRange(0,100);
updateOptionWidget();
connect(m_optionsWidget->cbUpdateCurrentColor, SIGNAL(toggled(bool)), SLOT(slotSetUpdateColor(bool)));
......@@ -326,10 +318,12 @@ QWidget* KisToolColorPicker::createOptionWidget()
SLOT(slotSetAddPalette(bool)));
connect(m_optionsWidget->radius, SIGNAL(valueChanged(int)),
SLOT(slotChangeRadius(int)));
connect(m_optionsWidget->blend, SIGNAL(valueChanged(int)),
SLOT(slotChangeBlend(int)));
connect(m_optionsWidget->cmbSources, SIGNAL(currentIndexChanged(int)),
SLOT(slotSetColorSource(int)));
KoResourceServer<KoColorSet>* srv = KoResourceServerProvider::instance()->paletteServer();
KoResourceServer<KoColorSet> *srv = KoResourceServerProvider::instance()->paletteServer();
if (!srv) {
return m_optionsWidget;
......@@ -356,6 +350,7 @@ void KisToolColorPicker::updateOptionWidget()
m_optionsWidget->cmbSources->setCurrentIndex(SAMPLE_MERGED + !m_config->sampleMerged);
m_optionsWidget->cbPalette->setChecked(m_config->addPalette);
m_optionsWidget->radius->setValue(m_config->radius);
m_optionsWidget->blend->setValue(m_config->blend);
}
void KisToolColorPicker::setToForeground(bool newValue)
......@@ -374,7 +369,6 @@ void KisToolColorPicker::slotSetUpdateColor(bool state)
m_config->updateColor = state;
}
void KisToolColorPicker::slotSetNormaliseValues(bool state)
{
m_config->normaliseValues = state;
......@@ -391,17 +385,21 @@ void KisToolColorPicker::slotChangeRadius(int value)
m_config->radius = value;
}
void KisToolColorPicker::slotChangeBlend(int value)
{
m_config->blend = value;
}
void KisToolColorPicker::slotSetColorSource(int value)
{
m_config->sampleMerged = value == SAMPLE_MERGED;
}
void KisToolColorPicker::slotAddPalette(KoResource* resource)
void KisToolColorPicker::slotAddPalette(KoResource *resource)
{
KoColorSet* palette = dynamic_cast<KoColorSet*>(resource);
KoColorSet *palette = dynamic_cast<KoColorSet*>(resource);
if (palette) {
m_optionsWidget->cmbPalette->addSqueezedItem(palette->name());
m_palettes.append(palette);
}
}
......@@ -2,6 +2,7 @@
* Copyright (c) 1999 Matthias Elter <elter@kde.org>
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
* Copyright (c) 2018 Emmet & Eoin O'Neill <emmetoneill.pdx@gmail.com>
*
* 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
......@@ -21,16 +22,11 @@
#ifndef KIS_TOOL_COLOR_PICKER_H_
#define KIS_TOOL_COLOR_PICKER_H_
#include <QList>
#include <QTimer>
#include "KoToolFactoryBase.h"
#include "ui_wdgcolorpicker.h"
#include "kis_tool.h"
#include <flake/kis_node_shape.h>
#include <KoIcon.h>
#include <kis_icon.h>
#include <QKeySequence>
class KoResource;
class KoColorSet;
......@@ -51,12 +47,11 @@ public:
class KisToolColorPicker : public KisTool
{
Q_OBJECT
Q_PROPERTY(bool toForeground READ toForeground WRITE setToForeground NOTIFY toForegroundChanged)
public:
KisToolColorPicker(KoCanvasBase* canvas);
KisToolColorPicker(KoCanvasBase *canvas);
~KisToolColorPicker() override;
public:
......@@ -69,6 +64,7 @@ public:
bool normaliseValues;
bool sampleMerged;
int radius;
int blend;
void save(ToolActivation activation) const;
void load(ToolActivation activation);
......@@ -81,7 +77,7 @@ public:
void continuePrimaryAction(KoPointerEvent *event) override;
void endPrimaryAction(KoPointerEvent *event) override;
void paint(QPainter& gc, const KoViewConverter &converter) override;
void paint(QPainter &gc, const KoViewConverter &converter) override;
bool toForeground() const;
......@@ -98,6 +94,7 @@ public Q_SLOTS:
void slotSetNormaliseValues(bool);
void slotSetAddPalette(bool);
void slotChangeRadius(int);
void slotChangeBlend(int);
void slotAddPalette(KoResource* resource);
void slotSetColorSource(int value);
......@@ -106,14 +103,15 @@ private:
void pickColor(const QPointF& pos);
void updateOptionWidget();
//Configuration m_config;
// Configuration
QScopedPointer<KisToolUtils::ColorPickerConfig> m_config;
ToolActivation m_toolActivationSource;
bool m_isActivated;
KoColor m_pickedColor;
// used to skip some of the tablet events and don't update the colour that often
// Used to skip some tablet events and update color less often
QTimer m_colorPickerDelayTimer;
ColorPickerOptionsWidget *m_optionsWidget;
......@@ -123,7 +121,6 @@ private:
class KisToolColorPickerFactory : public KoToolFactoryBase
{
public:
KisToolColorPickerFactory()
: KoToolFactoryBase("KritaSelected/KisToolColorPicker") {
......@@ -137,11 +134,9 @@ public:
~KisToolColorPickerFactory() override {}
KoToolBase * createTool(KoCanvasBase *canvas) override {
KoToolBase *createTool(KoCanvasBase *canvas) override {
return new KisToolColorPicker(canvas);
}
};
#endif // KIS_TOOL_COLOR_PICKER_H_
......@@ -7,15 +7,102 @@
<x>0</x>
<y>0</y>
<width>263</width>
<height>307</height>
<height>323</height>
</rect>
</property>
<property name="windowTitle">
<string>Color Picker</string>
</property>
<layout class="QGridLayout">
<item row="5" column="0">
<item row="6" column="0">
<layout class="QHBoxLayout" name="blendLayout">
<property name="spacing">
<number>1</number>
</property>
<item>
<widget class="QLabel" name="blendLabel">
<property name="text">
<string>Blend: </string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="margin">
<number>1</number>
</property>
</widget>
</item>
<item>
<widget class="KisSliderSpinBox" name="blend" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>&lt;nobr&gt;Blending controls the percentage of color that is picked&lt;/nobr&gt; and mixed with your current brush color. A full blending value picks colors completely, ignoring brush color.</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="0">
<widget class="QComboBox" name="cmbSources">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>200</width>
<height>32767</height>
</size>
</property>
<property name="toolTip">
<string>&lt;nobr&gt;Determines whether the color picker will sample&lt;/nobr&gt; colors from all visible layers or only the currently selected layer.</string>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<item>
<property name="text">
<string>Sample All Visible Layers</string>
</property>
</item>
<item>
<property name="text">
<string>Sample Current Layer</string>
</property>
</item>
</widget>
</item>
<item row="8" column="0">
<widget class="QCheckBox" name="cbNormaliseValues">
<property name="toolTip">
<string>Displays per-channel color information below as percentages instead of bytes.</string>