1// Aseprite
2// Copyright (C) 2019-2021 Igara Studio S.A.
3// Copyright (C) 2001-2018 David Capello
4//
5// This program is distributed under the terms of
6// the End-User License Agreement for Aseprite.
7
8#ifdef HAVE_CONFIG_H
9#include "config.h"
10#endif
11
12#include "app/app.h"
13#include "app/cmd/set_palette.h"
14#include "app/commands/new_params.h"
15#include "app/console.h"
16#include "app/context.h"
17#include "app/context_access.h"
18#include "app/job.h"
19#include "app/pref/preferences.h"
20#include "app/sprite_job.h"
21#include "app/transaction.h"
22#include "app/ui/color_bar.h"
23#include "app/ui/rgbmap_algorithm_selector.h"
24#include "app/ui_context.h"
25#include "doc/palette.h"
26#include "doc/sprite.h"
27#include "render/quantization.h"
28#include "ui/manager.h"
29
30#include "palette_from_sprite.xml.h"
31
32#include <algorithm>
33
34namespace app {
35
36struct ColorQuantizationParams : public NewParams {
37 Param<bool> ui { this, true, "ui" };
38 Param<bool> withAlpha { this, true, "withAlpha" };
39 Param<int> maxColors { this, 256, "maxColors" };
40 Param<bool> useRange { this, false, "useRange" };
41 Param<RgbMapAlgorithm> algorithm { this, RgbMapAlgorithm::DEFAULT, "algorithm" };
42};
43
44#if ENABLE_UI
45
46class PaletteFromSpriteWindow : public app::gen::PaletteFromSprite {
47public:
48 PaletteFromSpriteWindow() {
49 rgbmapAlgorithmPlaceholder()->addChild(&m_algoSelector);
50
51 advancedCheck()->Click.connect(
52 [this](ui::Event&){
53 advanced()->setVisible(advancedCheck()->isSelected());
54 expandWindow(sizeHint());
55 });
56
57 }
58
59 doc::RgbMapAlgorithm algorithm() {
60 return m_algoSelector.algorithm();
61 }
62
63 void algorithm(const doc::RgbMapAlgorithm mapAlgo) {
64 m_algoSelector.algorithm(mapAlgo);
65 }
66
67private:
68 RgbMapAlgorithmSelector m_algoSelector;
69};
70
71#endif
72
73class ColorQuantizationCommand : public CommandWithNewParams<ColorQuantizationParams> {
74public:
75 ColorQuantizationCommand();
76
77protected:
78 bool onEnabled(Context* context) override;
79 void onExecute(Context* context) override;
80};
81
82ColorQuantizationCommand::ColorQuantizationCommand()
83 : CommandWithNewParams<ColorQuantizationParams>(
84 CommandId::ColorQuantization(),
85 CmdRecordableFlag)
86{
87}
88
89bool ColorQuantizationCommand::onEnabled(Context* ctx)
90{
91 return ctx->checkFlags(ContextFlags::ActiveDocumentIsWritable);
92}
93
94void ColorQuantizationCommand::onExecute(Context* ctx)
95{
96#ifdef ENABLE_UI
97 const bool ui = (params().ui() && ctx->isUIAvailable());
98#endif
99
100 auto& pref = Preferences::instance();
101 bool withAlpha = params().withAlpha();
102 int maxColors = params().maxColors();
103 RgbMapAlgorithm algorithm = params().algorithm();
104 bool createPal;
105
106 Site site = ctx->activeSite();
107 PalettePicks entries = site.selectedColors();
108
109#ifdef ENABLE_UI
110 if (ui) {
111 PaletteFromSpriteWindow window;
112 {
113 ContextReader reader(ctx);
114 const Palette* curPalette = site.sprite()->palette(site.frame());
115
116 if (!params().algorithm.isSet())
117 algorithm = pref.quantization.rgbmapAlgorithm();
118 if (!params().withAlpha.isSet())
119 withAlpha = pref.quantization.withAlpha();
120
121 const bool advanced = pref.quantization.advanced();
122 window.advancedCheck()->setSelected(advanced);
123 window.advanced()->setVisible(advanced);
124
125 window.algorithm(algorithm);
126 window.newPalette()->setSelected(true);
127 window.ncolors()->setTextf("%d", maxColors);
128
129 if (entries.picks() > 1) {
130 window.currentRange()->setTextf(
131 "%s, %d color(s)",
132 window.currentRange()->text().c_str(),
133 entries.picks());
134 }
135 else
136 window.currentRange()->setEnabled(false);
137
138 window.currentPalette()->setTextf(
139 "%s, %d color(s)",
140 window.currentPalette()->text().c_str(),
141 curPalette->size());
142 }
143
144 window.openWindowInForeground();
145 if (window.closer() != window.ok())
146 return;
147
148 maxColors = window.ncolors()->textInt();
149 withAlpha = window.alphaChannel()->isSelected();
150 algorithm = window.algorithm();
151
152 pref.quantization.withAlpha(withAlpha);
153 pref.quantization.advanced(window.advancedCheck()->isSelected());
154
155 if (window.newPalette()->isSelected()) {
156 createPal = true;
157 }
158 else {
159 createPal = false;
160 if (window.currentPalette()->isSelected()) {
161 entries = PalettePicks(site.palette()->size());
162 entries.all();
163 }
164 }
165 }
166 else
167#endif // ENABLE_UI
168 {
169 createPal = (!params().useRange());
170 }
171
172 if (createPal) {
173 entries = PalettePicks(std::max(1, maxColors));
174 entries.all();
175 }
176 if (entries.picks() == 0)
177 return;
178
179 try {
180 ContextReader reader(ctx);
181 Sprite* sprite = site.sprite();
182 frame_t frame = site.frame();
183 const Palette* curPalette = site.sprite()->palette(frame);
184 Palette tmpPalette(frame, entries.picks());
185
186 SpriteJob job(reader, "Color Quantization");
187 const bool newBlend = pref.experimental.newBlend();
188 job.startJobWithCallback(
189 [sprite, withAlpha, &tmpPalette, &job, newBlend, algorithm]{
190 render::create_palette_from_sprite(
191 sprite, 0, sprite->lastFrame(),
192 withAlpha, &tmpPalette,
193 &job, // SpriteJob is a render::TaskDelegate
194 newBlend,
195 algorithm);
196 });
197 job.waitJob();
198 if (job.isCanceled())
199 return;
200
201 std::unique_ptr<Palette> newPalette(
202 new Palette(createPal ? tmpPalette:
203 *site.palette()));
204
205 if (createPal) {
206 entries = PalettePicks(newPalette->size());
207 entries.all();
208 }
209
210 int i = 0, j = 0;
211 for (bool state : entries) {
212 if (state)
213 newPalette->setEntry(i, tmpPalette.getEntry(j++));
214 ++i;
215 }
216
217 if (*curPalette != *newPalette)
218 job.tx()(new cmd::SetPalette(sprite, frame, newPalette.get()));
219 }
220 catch (const base::Exception& e) {
221 Console::showException(e);
222 }
223}
224
225Command* CommandFactory::createColorQuantizationCommand()
226{
227 return new ColorQuantizationCommand;
228}
229
230} // namespace app
231