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();
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
rgba palette[MAX_PALETTE]
Definition palette.c:35
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())