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 | |
34 | namespace app { |
35 | |
36 | struct 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 | |
46 | class PaletteFromSpriteWindow : public app::gen::PaletteFromSprite { |
47 | public: |
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 | |
67 | private: |
68 | RgbMapAlgorithmSelector m_algoSelector; |
69 | }; |
70 | |
71 | #endif |
72 | |
73 | class ColorQuantizationCommand : public CommandWithNewParams<ColorQuantizationParams> { |
74 | public: |
75 | ColorQuantizationCommand(); |
76 | |
77 | protected: |
78 | bool onEnabled(Context* context) override; |
79 | void onExecute(Context* context) override; |
80 | }; |
81 | |
82 | ColorQuantizationCommand::ColorQuantizationCommand() |
83 | : CommandWithNewParams<ColorQuantizationParams>( |
84 | CommandId::ColorQuantization(), |
85 | CmdRecordableFlag) |
86 | { |
87 | } |
88 | |
89 | bool ColorQuantizationCommand::onEnabled(Context* ctx) |
90 | { |
91 | return ctx->checkFlags(ContextFlags::ActiveDocumentIsWritable); |
92 | } |
93 | |
94 | void 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 | |
225 | Command* CommandFactory::createColorQuantizationCommand() |
226 | { |
227 | return new ColorQuantizationCommand; |
228 | } |
229 | |
230 | } // namespace app |
231 | |