Krita Source Code Documentation
Loading...
Searching...
No Matches
palettize.cpp
Go to the documentation of this file.
1/*
2 * This file is part of Krita
3 *
4 * SPDX-FileCopyrightText: 2019 Carl Olsson <carl.olsson@gmail.com>
5 *
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
9#include "palettize.h"
10
11#include <kis_types.h>
12#include <kpluginfactory.h>
13#include <kis_config_widget.h>
14#include <kis_filter_registry.h>
17#include <KoUpdater.h>
20#include <KoColorSet.h>
21#include <KoPattern.h>
23#include <KisDitherUtil.h>
26
27K_PLUGIN_FACTORY_WITH_JSON(PalettizeFactory, "kritapalettize.json", registerPlugin<Palettize>();)
28
29Palettize::Palettize(QObject *parent, const QVariantList &)
30 : QObject(parent)
31{
33}
34
35#include "palettize.moc"
36
37
38/*******************************************************************************/
39/* KisFilterPalettizeConfiguration */
40/*******************************************************************************/
41
43{
44public:
49
54
55 virtual KisFilterConfigurationSP clone() const override {
56 return new KisFilterPalettizeConfiguration(*this);
57 }
58
60 {
62 const QString md5sum = this->getString("md5sum");
63 const QString name = this->getString("palette");
64
65 return source.bestMatchLoadResult(md5sum, "", name) ;
66 }
67
69 {
70 return palette(resourcesInterface()).resource<KoColorSet>();
71 }
72
74 {
75
77 resources << this->palette(globalResourcesInterface);
78
79 resources << KisDitherWidget::prepareLinkedResources(*this, "dither/", globalResourcesInterface);
80 resources << KisDitherWidget::prepareLinkedResources(*this, "alphaDither/", globalResourcesInterface);
81
82 return resources;
83 }
84};
85
86/*******************************************************************************/
87/* KisPalettizeWidget */
88/*******************************************************************************/
89
91 : KisConfigWidget(parent)
92{
93 Q_UNUSED(m_ditherPatternWidget);
94 setupUi(this);
95
96 paletteIconWidget->setFixedSize(32, 32);
98 paletteIconWidget->setPopupWidget(m_paletteWidget);
101
102 QObject::connect(colorspaceComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &KisConfigWidget::sigConfigurationItemChanged);
103
104 QObject::connect(ditherGroupBox, &QGroupBox::toggled, this, &KisConfigWidget::sigConfigurationItemChanged);
105
107
108 QObject::connect(colorModeComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &KisConfigWidget::sigConfigurationItemChanged);
109
110 offsetScaleSpinBox->setPrefix(QString("%1 ").arg(i18n("Offset Scale:")));
111 offsetScaleSpinBox->setRange(0.0, 1.0, 3);
112 offsetScaleSpinBox->setSingleStep(0.125);
113 QObject::connect(offsetScaleSpinBox, QOverload<double>::of(&KisDoubleSliderSpinBox::valueChanged), this, &KisConfigWidget::sigConfigurationItemChanged);
114
115 QObject::connect(alphaGroupBox, &QGroupBox::toggled, this, &KisConfigWidget::sigConfigurationItemChanged);
116
117 QObject::connect(alphaModeComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &KisConfigWidget::sigConfigurationItemChanged);
118
119 alphaClipSpinBox->setPrefix(QString("%1 ").arg(i18n("Clip:")));
120 alphaClipSpinBox->setRange(0.0, 1.0, 3);
121 alphaClipSpinBox->setSingleStep(0.125);
122 QObject::connect(alphaClipSpinBox, QOverload<double>::of(&KisDoubleSliderSpinBox::valueChanged), this, &KisConfigWidget::sigConfigurationItemChanged);
123
124 alphaIndexSpinBox->setPrefix(QString("%1 ").arg(i18nc("Index as in Index Color", "Index:")));
125 alphaIndexSpinBox->setRange(0, 255);
126 QObject::connect(alphaIndexSpinBox, QOverload<int>::of(&KisSliderSpinBox::valueChanged), this, &KisConfigWidget::sigConfigurationItemChanged);
128 const KoColorSetSP palette = m_paletteWidget->currentResource().staticCast<KoColorSet>();
129 alphaIndexSpinBox->setMaximum(palette ? int(palette->colorCount() - 1) : 0);
130 alphaIndexSpinBox->setValue(std::min(alphaIndexSpinBox->value(), alphaIndexSpinBox->maximum()));
131 });
132
134}
135
137{
138 const KisFilterPalettizeConfiguration *config = dynamic_cast<const KisFilterPalettizeConfiguration*>(_config.data());
140
141 KoColorSetSP palette = config->palette();
142 if (palette) m_paletteWidget->setCurrentResource(palette);
143 colorspaceComboBox->setCurrentIndex(config->getInt("colorspace"));
144 ditherGroupBox->setChecked(config->getBool("ditherEnabled"));
145 ditherWidget->setConfiguration(*config, "dither/");
146 colorModeComboBox->setCurrentIndex(config->getInt("dither/colorMode"));
147 offsetScaleSpinBox->setValue(config->getDouble("dither/offsetScale"));
148 alphaGroupBox->setChecked(config->getBool("alphaEnabled"));
149 alphaModeComboBox->setCurrentIndex(config->getInt("alphaMode"));
150 alphaClipSpinBox->setValue(config->getDouble("alphaClip"));
151 alphaIndexSpinBox->setValue(config->getInt("alphaIndex"));
152 alphaDitherWidget->setConfiguration(*config, "alphaDither/");
153}
154
156{
157 KisFilterSP filter = KisFilterRegistry::instance()->get("palettize");
159
161 config->setProperty("md5sum", QVariant(m_paletteWidget->currentResource()->md5Sum()));
162 config->setProperty("palette", QVariant(m_paletteWidget->currentResource()->name()));
163 }
164 config->setProperty("colorspace", colorspaceComboBox->currentIndex());
165 config->setProperty("ditherEnabled", ditherGroupBox->isChecked());
166 ditherWidget->configuration(*config, "dither/");
167 config->setProperty("dither/colorMode", colorModeComboBox->currentIndex());
168 config->setProperty("dither/offsetScale", offsetScaleSpinBox->value());
169 config->setProperty("alphaEnabled", alphaGroupBox->isChecked());
170 config->setProperty("alphaMode", alphaModeComboBox->currentIndex());
171 config->setProperty("alphaClip", alphaClipSpinBox->value());
172 config->setProperty("alphaIndex", alphaIndexSpinBox->value());
173 alphaDitherWidget->configuration(*config, "alphaDither/");
174
175 return config;
176}
177
178KisConfigWidget* KisFilterPalettize::createConfigurationWidget(QWidget *parent, const KisPaintDeviceSP dev, bool useForMasks) const
179{
180 Q_UNUSED(dev);
181 Q_UNUSED(useForMasks);
182
183 return new KisPalettizeWidget(parent);
184}
185
186/*******************************************************************************/
187/* KisFilterPalettize */
188/*******************************************************************************/
189
196
198{
199 return new KisFilterPalettizeConfiguration("palettize", 1, resourcesInterface);
200}
201
202
204{
205 KisFilterConfigurationSP config = factoryConfiguration(resourcesInterface);
206
207 config->setProperty("palette", "Default");
208 config->setProperty("colorspace", Colorspace::Lab);
209 config->setProperty("ditherEnabled", false);
210 KisDitherWidget::factoryConfiguration(*config, "dither/");
211 config->setProperty("dither/colorMode", ColorMode::PerChannelOffset);
212 config->setProperty("dither/offsetScale", 0.125);
213 config->setProperty("alphaEnabled", true);
214 config->setProperty("alphaMode", AlphaMode::Clip);
215 config->setProperty("alphaClip", 0.5);
216 config->setProperty("alphaIndex", 0);
217 KisDitherWidget::factoryConfiguration(*config, "alphaDither/");
218
219 return config;
220}
221
222void KisFilterPalettize::processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP _config, KoUpdater* progressUpdater) const
223{
224 const KisFilterPalettizeConfiguration *config = dynamic_cast<const KisFilterPalettizeConfiguration*>(_config.data());
227
228 const KoColorSetSP palette = config->palette();
229
230 const int searchColorspace = config->getInt("colorspace");
231 const bool ditherEnabled = config->getBool("ditherEnabled");
232 const int colorMode = config->getInt("dither/colorMode");
233 const double offsetScale = config->getDouble("dither/offsetScale");
234 const bool alphaEnabled = config->getBool("alphaEnabled");
235 const int alphaMode = config->getInt("alphaMode");
236 const double alphaClip = config->getDouble("alphaClip");
237 const int alphaIndex = config->getInt("alphaIndex");
238
239 const KoColorSpace* colorspace = device->colorSpace();
240 const KoColorSpace* workColorspace = (searchColorspace == Colorspace::Lab
242 : KoColorSpaceRegistry::instance()->rgb16("sRGB-elle-V2-srgbtrc.icc"));
243
244 const quint8 colorCount = ditherEnabled && colorMode == ColorMode::NearestColors ? 2 : 1;
245
246 using SearchColor = boost::geometry::model::point<quint16, 3, boost::geometry::cs::cartesian>;
247 struct ColorCandidate {
248 KoColor color;
249 quint16 index;
250 double distance;
251 };
252 using SearchEntry = std::pair<SearchColor, ColorCandidate>;
253 boost::geometry::index::rtree<SearchEntry, boost::geometry::index::quadratic<16>> rtree;
254
255 if (palette) {
256 // Add palette colors to search tree
257 quint16 index = 0;
258 for (int row = 0; row < palette->rowCount(); ++row) {
259 for (int column = 0; column < palette->columnCount(); ++column) {
260 KisSwatch swatch = palette->getColorGlobal(column, row);
261 if (swatch.isValid()) {
262 KoColor color = swatch.color().convertedTo(colorspace);
263 KoColor workColor = swatch.color().convertedTo(workColorspace);
264 SearchColor searchColor;
265 memcpy(&searchColor, workColor.data(), sizeof(SearchColor));
266 // Don't add duplicates so won't dither between identical colors
267 std::vector<SearchEntry> result;
268 rtree.query(boost::geometry::index::contains(searchColor), std::back_inserter(result));
269 if (result.empty()) rtree.insert(SearchEntry(searchColor, {color, index, 0.0}));
270 }
271 ++index;
272 }
273 }
274
275 KisDitherUtil ditherUtil;
276 if (ditherEnabled) ditherUtil.setConfiguration(*config, "dither/");
277
278 KisDitherUtil alphaDitherUtil;
279 if (alphaMode == AlphaMode::Dither) alphaDitherUtil.setConfiguration(*config, "alphaDither/");
280
281 KisSequentialIteratorProgress pixel(device, applyRect, progressUpdater);
282 while (pixel.nextPixel()) {
283 KoColor workColor(pixel.oldRawData(), colorspace);
284 workColor.convertTo(workColorspace);
285
286 // Find dither threshold
287 double threshold = 0.5;
288 if (ditherEnabled) {
289 threshold = ditherUtil.threshold(QPoint(pixel.x(), pixel.y()));
290
291 // Traditional per-channel ordered dithering
292 if (colorMode == ColorMode::PerChannelOffset) {
293 QVector<float> normalized(int(workColorspace->channelCount()));
294 workColorspace->normalisedChannelsValue(workColor.data(), normalized);
295 for (int channel = 0; channel < int(workColorspace->channelCount()); ++channel) {
296 normalized[channel] += (threshold - 0.5) * offsetScale;
297 }
298 workColorspace->fromNormalisedChannelsValue(workColor.data(), normalized);
299 }
300 }
301
302 // Get candidate colors and their distances
303 SearchColor searchColor;
304 memcpy(reinterpret_cast<quint8 *>(&searchColor), workColor.data(), sizeof(SearchColor));
305 std::vector<ColorCandidate> candidateColors;
306 candidateColors.reserve(size_t(colorCount));
307 double distanceSum = 0.0;
308 for (auto it = rtree.qbegin(boost::geometry::index::nearest(searchColor, colorCount)); it != rtree.qend() && candidateColors.size() < colorCount; ++it) {
309 ColorCandidate candidate = it->second;
310 candidate.distance = boost::geometry::distance(searchColor, it->first);
311 candidateColors.push_back(candidate);
312 distanceSum += candidate.distance;
313 }
314
315 // Select color candidate
316 quint16 selected;
317 if (ditherEnabled && colorMode == ColorMode::NearestColors) {
318 // Sort candidates by palette order for stable dither color ordering
319 const bool swap = candidateColors[0].index > candidateColors[1].index;
320 selected = swap ^ (candidateColors[swap].distance / distanceSum > threshold);
321 }
322 else {
323 selected = 0;
324 }
325 ColorCandidate &candidate = candidateColors[selected];
326
327 // Set alpha
328 const double oldAlpha = colorspace->opacityF(pixel.oldRawData());
329 double newAlpha = oldAlpha;
330 if (alphaEnabled && !(!ditherEnabled && alphaMode == AlphaMode::Dither)) {
331 if (alphaMode == AlphaMode::Clip) {
332 newAlpha = oldAlpha < alphaClip? 0.0 : 1.0;
333 }
334 else if (alphaMode == AlphaMode::Index) {
335 newAlpha = (candidate.index == alphaIndex ? 0.0 : 1.0);
336 }
337 else if (alphaMode == AlphaMode::Dither) {
338 newAlpha = oldAlpha < alphaDitherUtil.threshold(QPoint(pixel.x(), pixel.y())) ? 0.0 : 1.0;
339 }
340 }
341 colorspace->setOpacity(candidate.color.data(), newAlpha, 1);
342
343 // Copy color to pixel
344 memcpy(pixel.rawData(), candidate.color.data(), colorspace->pixelSize());
345 }
346 }
347}
KisMagneticGraph::vertex_descriptor source(typename KisMagneticGraph::edge_descriptor e, KisMagneticGraph g)
@ FULLY_INDEPENDENT
qreal distance(const QPointF &p1, const QPointF &p2)
void sigConfigurationItemChanged()
qreal threshold(const QPoint &pos)
void setConfiguration(const KisFilterConfiguration &config, const QString &prefix="")
static QList< KoResourceLoadResult > prepareLinkedResources(const KisFilterConfiguration &config, const QString &prefix, KisResourcesInterfaceSP resourcesInterface)
void sigConfigurationItemChanged()
static void factoryConfiguration(KisPropertiesConfiguration &config, const QString &prefix="")
KisFilterPalettizeConfiguration(const QString &name, qint32 version, KisResourcesInterfaceSP resourcesInterface)
Definition palettize.cpp:45
KisFilterPalettizeConfiguration(const KisFilterPalettizeConfiguration &rhs)
Definition palettize.cpp:50
KoResourceLoadResult palette(KisResourcesInterfaceSP resourcesInterface) const
Definition palettize.cpp:59
KoColorSetSP palette() const
Definition palettize.cpp:68
virtual KisFilterConfigurationSP clone() const override
Definition palettize.cpp:55
QList< KoResourceLoadResult > linkedResources(KisResourcesInterfaceSP globalResourcesInterface) const override
Definition palettize.cpp:73
KisConfigWidget * createConfigurationWidget(QWidget *parent, const KisPaintDeviceSP dev, bool useForMasks) const override
KisFilterConfigurationSP factoryConfiguration(KisResourcesInterfaceSP resourcesInterface) const override
KisFilterConfigurationSP defaultConfiguration(KisResourcesInterfaceSP resourcesInterface) const override
void processImpl(KisPaintDeviceSP device, const QRect &applyRect, const KisFilterConfigurationSP config, KoUpdater *progressUpdater) const override
void add(KisFilterSP item)
static KisFilterRegistry * instance()
static KisResourcesInterfaceSP instance()
void setResource(KoResourceSP resource)
const KoColorSpace * colorSpace() const
void setConfiguration(const KisPropertiesConfigurationSP) override
KisResourceItemChooser * m_ditherPatternWidget
Definition palettize.h:39
KisResourceItemChooser * m_paletteWidget
Definition palettize.h:38
KisPalettizeWidget(QWidget *parent=0)
Definition palettize.cpp:90
KisPropertiesConfigurationSP configuration() const override
void setCurrentResource(KoResourceSP resource)
Sets the item representing the resource as selected.
void resourceSelected(KoResourceSP resource)
Emitted when a resource was selected.
ALWAYS_INLINE quint8 * rawData()
ALWAYS_INLINE int x() const
ALWAYS_INLINE const quint8 * oldRawData() const
ALWAYS_INLINE int y() const
KoColor color() const
Definition KisSwatch.h:30
bool isValid() const
Definition KisSwatch.h:36
virtual quint32 channelCount() const =0
virtual void normalisedChannelsValue(const quint8 *pixel, QVector< float > &channels) const =0
virtual void fromNormalisedChannelsValue(quint8 *pixel, const QVector< float > &values) const =0
void convertTo(const KoColorSpace *cs, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags)
Definition KoColor.cpp:136
KoColor convertedTo(const KoColorSpace *cs, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const
Definition KoColor.cpp:163
quint8 * data()
Definition KoColor.h:144
T get(const QString &id) const
Palettize(QObject *parent, const QVariantList &)
Definition palettize.cpp:29
K_PLUGIN_FACTORY_WITH_JSON(KritaASCCDLFactory, "kritaasccdl.json", registerPlugin< KritaASCCDL >();) KritaASCCDL
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
const KoID FiltersCategoryMapId("map_filters", ki18nc("The category of mapping filters, like bump map or gradient filter map. Verb.", "Map"))
const QString Palettes
void setShowConfigurationWidget(bool v)
virtual KisFilterConfigurationSP factoryConfiguration(KisResourcesInterfaceSP resourcesInterface) const
void setSupportsPainting(bool v)
void setColorSpaceIndependence(ColorSpaceIndependence v)
KisResourcesInterfaceSP resourcesInterface
QString getString(const QString &name, const QString &def=QString()) const
bool getBool(const QString &name, bool def=false) const
int getInt(const QString &name, int def=0) const
double getDouble(const QString &name, double def=0.0) const
const KoColorSpace * lab16(const QString &profileName=QString())
static KoColorSpaceRegistry * instance()
const KoColorSpace * rgb16(const QString &profileName=QString())