Commit 1d3eedc5 authored by Carl Olsson's avatar Carl Olsson Committed by Boudewijn Rempt

Added basic dither functionality from Palettize filter to the Gradient

Map filter.
Allows dithered blending between stop colours without introducing any
new colours which is desirable for pixel art.

Moved dither functionality/ui out of Palettize filter into KisDitherUtil
and KisDitherWidget.
parent 97796f04
......@@ -270,6 +270,7 @@ set(kritaui_LIB_SRCS
widgets/KoFillConfigWidget.cpp
widgets/KisLayerStyleAngleSelector.cpp
widgets/KisMemoryReportButton.cpp
widgets/KisDitherWidget.cpp
KisPaletteEditor.cpp
dialogs/KisDlgPaletteEditor.cpp
......@@ -280,6 +281,7 @@ set(kritaui_LIB_SRCS
utils/kis_document_aware_spin_box_unit_manager.cpp
utils/KisSpinBoxSplineUnitConverter.cpp
utils/KisClipboardUtil.cpp
utils/KisDitherUtil.cpp
input/kis_input_manager.cpp
input/kis_input_manager_p.cpp
......@@ -463,6 +465,7 @@ endif()
ki18n_wrap_ui(kritaui_LIB_SRCS
widgets/KoFillConfigWidget.ui
widgets/KoStrokeConfigWidget.ui
widgets/KisDitherWidget.ui
forms/wdgdlgpngimport.ui
forms/wdgfullscreensettings.ui
forms/wdgautogradient.ui
......
/*
* This file is part of the KDE project
*
* Copyright (c) 2019 Carl Olsson <carl.olsson@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
* 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 "KisDitherUtil.h"
#include <KoPattern.h>
#include <kis_properties_configuration.h>
#include <KoResourceServerProvider.h>
#include <kis_random_generator.h>
KisDitherUtil::KisDitherUtil()
: m_thresholdMode(ThresholdMode::Pattern), m_patternValueMode(PatternValueMode::Auto),
m_pattern(0), m_noiseSeed(0), m_patternUseAlpha(false), m_spread(1.0)
{
}
void KisDitherUtil::setThresholdMode(const ThresholdMode thresholdMode)
{
m_thresholdMode = thresholdMode;
}
void KisDitherUtil::setPattern(const QString &name, const PatternValueMode valueMode)
{
m_patternValueMode = valueMode;
m_pattern = KoResourceServerProvider::instance()->patternServer()->resourceByName(name);
if (m_pattern && m_thresholdMode == ThresholdMode::Pattern && m_patternValueMode == PatternValueMode::Auto) {
// Automatically pick between lightness-based and alpha-based patterns by whichever has maximum range
qreal lightnessMin = 1.0, lightnessMax = 0.0;
qreal alphaMin = 1.0, alphaMax = 0.0;
const QImage &image = m_pattern->pattern();
for (int y = 0; y < image.height(); ++y) {
for (int x = 0; x < image.width(); ++x) {
const QColor pixel = image.pixelColor(x, y);
lightnessMin = std::min(lightnessMin, pixel.lightnessF());
lightnessMax = std::max(lightnessMax, pixel.lightnessF());
alphaMin = std::min(alphaMin, pixel.alphaF());
alphaMax = std::max(alphaMax, pixel.alphaF());
}
}
m_patternUseAlpha = (alphaMax - alphaMin > lightnessMax - lightnessMin);
}
else {
m_patternUseAlpha = (m_patternValueMode == PatternValueMode::Alpha);
}
}
void KisDitherUtil::setNoiseSeed(const quint64 &noiseSeed)
{
m_noiseSeed = noiseSeed;
}
void KisDitherUtil::setSpread(const qreal &spread)
{
m_spread = spread;
}
qreal KisDitherUtil::threshold(const QPoint &pos)
{
qreal threshold;
if (m_thresholdMode == ThresholdMode::Pattern && m_pattern) {
const QImage &image = m_pattern->pattern();
const QColor color = image.pixelColor(pos.x() % image.width(), pos.y() % image.height());
threshold = (m_patternUseAlpha ? color.alphaF() : color.lightnessF());
}
else if (m_thresholdMode == ThresholdMode::Noise) {
KisRandomGenerator random(m_noiseSeed);
threshold = random.doubleRandomAt(pos.x(), pos.y());
}
else threshold = 0.5;
return 0.5 - (m_spread / 2.0) + threshold * m_spread;
}
void KisDitherUtil::setConfiguration(const KisPropertiesConfiguration &config, const QString &prefix)
{
setThresholdMode(ThresholdMode(config.getInt(prefix + "thresholdMode")));
setPattern(config.getString(prefix + "pattern"), PatternValueMode(config.getInt(prefix + "patternValueMode")));
setNoiseSeed(quint64(config.getInt(prefix + "noiseSeed")));
setSpread(config.getDouble(prefix + "spread"));
}
/*
* This file is part of the KDE project
*
* Copyright (c) 2019 Carl Olsson <carl.olsson@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
* 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_DITHER_UTIL_H
#define KIS_DITHER_UTIL_H
#include <kritaui_export.h>
#include <kis_types.h>
class KoPattern;
class KisPropertiesConfiguration;
class KRITAUI_EXPORT KisDitherUtil
{
public:
enum ThresholdMode {
Pattern,
Noise
};
enum PatternValueMode {
Auto,
Lightness,
Alpha
};
KisDitherUtil();
void setThresholdMode(const ThresholdMode thresholdMode);
void setPattern(const QString &name, const PatternValueMode valueMode);
void setNoiseSeed(const quint64 &noiseSeed);
void setSpread(const qreal &spread);
qreal threshold(const QPoint &pos);
void setConfiguration(const KisPropertiesConfiguration &config, const QString &prefix = "");
private:
ThresholdMode m_thresholdMode;
PatternValueMode m_patternValueMode;
KoPattern* m_pattern;
quint64 m_noiseSeed;
bool m_patternUseAlpha;
qreal m_spread;
};
#endif
/*
* This file is part of Krita
*
* Copyright (c) 2019 Carl Olsson <carl.olsson@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
* 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 "KisDitherWidget.h"
#include <kpluginfactory.h>
#include <KoUpdater.h>
#include <KoResourceServerProvider.h>
#include <KoResourceServer.h>
#include <KoResourceServerAdapter.h>
#include <KoResourceItemChooser.h>
#include <KoColorSet.h>
#include <KoPattern.h>
#include <kis_properties_configuration.h>
#include "KisDitherUtil.h"
KisDitherWidget::KisDitherWidget(QWidget* parent)
: QWidget(parent), Ui::KisDitherWidget()
{
setupUi(this);
QObject::connect(thresholdModeComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &KisDitherWidget::sigConfigurationItemChanged);
patternIconWidget->setFixedSize(32, 32);
KoResourceServer<KoPattern>* patternServer = KoResourceServerProvider::instance()->patternServer();
QSharedPointer<KoAbstractResourceServerAdapter> patternAdapter(new KoResourceServerAdapter<KoPattern>(patternServer));
m_ditherPatternWidget = new KoResourceItemChooser(patternAdapter, this, false);
patternIconWidget->setPopupWidget(m_ditherPatternWidget);
QObject::connect(m_ditherPatternWidget, &KoResourceItemChooser::resourceSelected, patternIconWidget, &KisIconWidget::setResource);
QObject::connect(m_ditherPatternWidget, &KoResourceItemChooser::resourceSelected, this, &KisDitherWidget::sigConfigurationItemChanged);
QObject::connect(patternValueModeComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &KisDitherWidget::sigConfigurationItemChanged);
noiseSeedLineEdit->setValidator(new QIntValidator(this));
QObject::connect(noiseSeedLineEdit, &QLineEdit::textChanged, this, &KisDitherWidget::sigConfigurationItemChanged);
QObject::connect(noiseSeedRandomizeButton, &QToolButton::clicked, [this](){
noiseSeedLineEdit->setText(QString::number(rand()));
});
spreadSpinBox->setPrefix(QString("%1 ").arg(i18n("Spread:")));
spreadSpinBox->setRange(0.0, 1.0, 3);
spreadSpinBox->setSingleStep(0.125);
QObject::connect(spreadSpinBox, &KisDoubleSliderSpinBox::valueChanged, this, &KisDitherWidget::sigConfigurationItemChanged);
}
void KisDitherWidget::setConfiguration(const KisPropertiesConfiguration &config, const QString &prefix)
{
thresholdModeComboBox->setCurrentIndex(config.getInt(prefix + "thresholdMode"));
KoPattern* pattern = KoResourceServerProvider::instance()->patternServer()->resourceByName(config.getString(prefix + "pattern"));
if (pattern) m_ditherPatternWidget->setCurrentResource(pattern);
patternValueModeComboBox->setCurrentIndex(config.getInt(prefix + "patternValueMode"));
noiseSeedLineEdit->setText(QString::number(config.getInt(prefix + "noiseSeed")));
spreadSpinBox->setValue(config.getDouble(prefix + "spread"));
}
void KisDitherWidget::configuration(KisPropertiesConfiguration &config, const QString &prefix) const
{
config.setProperty(prefix + "thresholdMode",thresholdModeComboBox->currentIndex());
if (m_ditherPatternWidget->currentResource()) config.setProperty(prefix + "pattern", QVariant(m_ditherPatternWidget->currentResource()->name()));
config.setProperty(prefix + "patternValueMode", patternValueModeComboBox->currentIndex());
config.setProperty(prefix + "noiseSeed", noiseSeedLineEdit->text().toInt());
config.setProperty(prefix + "spread", spreadSpinBox->value());
}
void KisDitherWidget::factoryConfiguration(KisPropertiesConfiguration &config, const QString &prefix)
{
config.setProperty(prefix + "thresholdMode", KisDitherUtil::ThresholdMode::Pattern);
config.setProperty(prefix + "pattern", "DITH 0202 GEN ");
config.setProperty(prefix + "patternValueMode", KisDitherUtil::PatternValueMode::Auto);
config.setProperty(prefix + "noiseSeed", rand());
config.setProperty(prefix + "spread", 1.0);
}
/*
* This file is part of the KDE project
*
* Copyright (c) 2019 Carl Olsson <carl.olsson@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
* 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_DITHER_WIDGET_H
#define KIS_DITHER_WIDGET_H
#include <kritaui_export.h>
#include <QWidget>
#include "ui_KisDitherWidget.h"
class KoResourceItemChooser;
class KisPropertiesConfiguration;
class KRITAUI_EXPORT KisDitherWidget : public QWidget, public Ui::KisDitherWidget
{
Q_OBJECT
public:
KisDitherWidget(QWidget* parent = 0);
void setConfiguration(const KisPropertiesConfiguration &config, const QString &prefix = "");
void configuration(KisPropertiesConfiguration &config, const QString &prefix = "") const;
static void factoryConfiguration(KisPropertiesConfiguration &config, const QString &prefix = "");
Q_SIGNALS:
void sigConfigurationItemChanged();
private:
KoResourceItemChooser* m_ditherPatternWidget;
};
#endif
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>KisDitherWidget</class>
<widget class="QWidget" name="KisDitherWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>185</width>
<height>154</height>
</rect>
</property>
<property name="windowTitle">
<string>KisDitherWidget</string>
</property>
<layout class="QFormLayout" name="formLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="1" column="0">
<widget class="QLabel" name="thresholdModeLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Threshold Mode</string>
</property>
<property name="buddy">
<cstring>thresholdModeComboBox</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="thresholdModeComboBox">
<item>
<property name="text">
<string>Pattern</string>
</property>
</item>
<item>
<property name="text">
<string>Noise</string>
</property>
</item>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="KisDoubleSliderSpinBox" name="spreadSpinBox"/>
</item>
<item row="2" column="0" colspan="2">
<widget class="QStackedWidget" name="thresholdModeStackedWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="currentIndex">
<number>1</number>
</property>
<property name="prefix" stdset="0">
<string>Amount: </string>
</property>
<widget class="QWidget" name="patternWidget">
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="patternLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Pattern</string>
</property>
<property name="buddy">
<cstring>patternIconWidget</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="KisIconWidget" name="patternIconWidget">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="patternValueModeLabel">
<property name="text">
<string>Value Mode</string>
</property>
<property name="buddy">
<cstring>patternValueModeComboBox</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="patternValueModeComboBox">
<item>
<property name="text">
<string>Auto</string>
</property>
</item>
<item>
<property name="text">
<string>Lightness</string>
</property>
</item>
<item>
<property name="text">
<string>Alpha</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="noiseWidget">
<layout class="QFormLayout" name="formLayout_5">
<item row="0" column="0">
<widget class="QLabel" name="noiseSeedLabel">
<property name="text">
<string>Seed</string>
</property>
<property name="buddy">
<cstring>noiseSeedLineEdit</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="noiseSeedLineEdit"/>
</item>
<item row="1" column="1">
<widget class="QToolButton" name="noiseSeedRandomizeButton">
<property name="text">
<string>Randomize</string>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextOnly</enum>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>KisIconWidget</class>
<extends>QToolButton</extends>
<header location="global">kis_iconwidget.h</header>
</customwidget>
<customwidget>
<class>KisDoubleSliderSpinBox</class>
<extends>QDoubleSpinBox</extends>
<header location="global">kis_slider_spin_box.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>thresholdModeComboBox</tabstop>
<tabstop>patternIconWidget</tabstop>
<tabstop>patternValueModeComboBox</tabstop>
<tabstop>noiseSeedLineEdit</tabstop>
<tabstop>noiseSeedRandomizeButton</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>thresholdModeComboBox</sender>
<signal>currentIndexChanged(int)</signal>
<receiver>thresholdModeStackedWidget</receiver>
<slot>setCurrentIndex(int)</slot>
<hints>
<hint type="sourcelabel">
<x>180</x>
<y>11</y>
</hint>
<hint type="destinationlabel">
<x>185</x>
<y>36</y>
</hint>
</hints>
</connection>
<connection>
<sender>thresholdModeStackedWidget</sender>
<signal>currentChanged(int)</signal>
<receiver>thresholdModeComboBox</receiver>
<slot>setCurrentIndex(int)</slot>
<hints>
<hint type="sourcelabel">
<x>242</x>
<y>96</y>
</hint>
<hint type="destinationlabel">
<x>239</x>
<y>17</y>
</hint>
</hints>
</connection>
</connections>
</ui>
......@@ -57,6 +57,9 @@ KritaGradientMapConfigWidget::KritaGradientMapConfigWidget(QWidget *parent, KisP
connect(m_gradientPopUp, SIGNAL(resourceSelected(QSharedPointer<KoShapeBackground>)), this, SLOT(setAbstractGradientToEditor()));
connect(m_page->gradientEditor, SIGNAL(sigGradientChanged()), m_gradientChangedCompressor, SLOT(start()));
connect(m_gradientChangedCompressor, SIGNAL(timeout()), this, SIGNAL(sigConfigurationItemChanged()));
QObject::connect(m_page->ditherGroupBox, &QGroupBox::toggled, this, &KisConfigWidget::sigConfigurationItemChanged);
QObject::connect(m_page->ditherWidget, &KisDitherWidget::sigConfigurationItemChanged, this, &KisConfigWidget::sigConfigurationItemChanged);
}
KritaGradientMapConfigWidget::~KritaGradientMapConfigWidget()
......@@ -85,6 +88,9 @@ KisPropertiesConfigurationSP KritaGradientMapConfigWidget::configuration() const
cfg->setProperty("gradientXML", doc.toString());
}
cfg->setProperty("ditherEnabled", m_page->ditherGroupBox->isChecked());
m_page->ditherWidget->configuration(*cfg, "dither/");
return cfg;
}
......@@ -101,6 +107,9 @@ void KritaGradientMapConfigWidget::setConfiguration(const KisPropertiesConfigura
m_activeGradient->setStops(gradient.stops());
}
}
m_page->ditherGroupBox->setChecked(config->getBool("ditherEnabled"));
m_page->ditherWidget->setConfiguration(*config, "dither/");
}
void KritaGradientMapConfigWidget::setView(KisViewManager *view)
......
......@@ -33,6 +33,7 @@
#include <KoStopGradient.h>
#include <KoColorSet.h>
#include "gradientmap.h"
#include <KisDitherUtil.h>
#include <KisSequentialIteratorProgress.h>
......@@ -69,7 +70,9 @@ void KritaFilterGradientMap::processImpl(KisPaintDeviceSP device,
}
KoStopGradient gradient = KoStopGradient::fromXML(doc.firstChildElement());
bool threshold = true;
const bool ditherEnabled = config->getBool("ditherEnabled");
KisDitherUtil ditherUtil;
if (ditherEnabled) ditherUtil.setConfiguration(*config, "dither/");
KoColor outColor(Qt::white, device->colorSpace());
KisSequentialIteratorProgress it(device, applyRect, progressUpdater);
......@@ -77,11 +80,11 @@ void KritaFilterGradientMap::processImpl(KisPaintDeviceSP device,
const int pixelSize = device->colorSpace()->pixelSize();
while (it.nextPixel()) {
grey = qreal(device->colorSpace()->intensity8(it.oldRawData())) / 255;
if (threshold) {
if (ditherEnabled) {
KoGradientStop leftStop, rightStop;
if (!gradient.stopsAt(leftStop, rightStop, grey)) continue;
qreal localT = (grey - leftStop.first) / (rightStop.first - leftStop.first);
if (localT < qreal(rand()) / qreal(RAND_MAX)) {
if (localT < ditherUtil.threshold(QPoint(it.x(), it.y()))) {
outColor = leftStop.second;
}
else {
......@@ -109,6 +112,10 @@ KisFilterConfigurationSP KritaFilterGradientMap::factoryConfiguration() const
stopGradient.toXML(doc, elt);
doc.appendChild(elt);
config->setProperty("gradientXML", doc.toString());
config->setProperty("ditherEnabled", false);
KisDitherWidget::factoryConfiguration(*config, "dither/");
return config;
}
......
......@@ -43,6 +43,21 @@
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="ditherGroupBox">
<property name="title">
<string>Dither</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0" colspan="2">
<widget class="KisDitherWidget" name="ditherWidget" native="true"/>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
......@@ -70,6 +85,12 @@
<header>kis_stopgradient_editor.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>KisDitherWidget</class>
<extends>QWidget</extends>
<header location="global">KisDitherWidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
......
This diff is collapsed.
......@@ -60,7 +60,7 @@ public:
enum AlphaMode {
Clip,
Index,
UseDither
Dither
};
enum ThresholdMode {
Pattern,
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment