1 | // Aseprite |
2 | // Copyright (C) 2021 Igara Studio SA |
3 | // |
4 | // This program is distributed under the terms of |
5 | // the End-User License Agreement for Aseprite. |
6 | |
7 | #ifdef HAVE_CONFIG_H |
8 | #include "config.h" |
9 | #endif |
10 | |
11 | #include "app/app.h" |
12 | #include "app/commands/cmd_set_palette.h" |
13 | #include "app/commands/commands.h" |
14 | #include "app/commands/params.h" |
15 | #include "app/context.h" |
16 | #include "app/i18n/strings.h" |
17 | #include "app/modules/palettes.h" |
18 | #include "app/site.h" |
19 | #include "doc/cel.h" |
20 | #include "doc/frame_range.h" |
21 | #include "doc/image_bits.h" |
22 | #include "doc/layer.h" |
23 | #include "doc/layer_tilemap.h" |
24 | #include "doc/octree_map.h" |
25 | #include "doc/palette.h" |
26 | #include "doc/sprite.h" |
27 | |
28 | namespace app { |
29 | |
30 | using namespace ui; |
31 | |
32 | class SelectPaletteColorsCommand : public Command { |
33 | public: |
34 | enum Modifier { |
35 | UsedColors, |
36 | UnusedColors, |
37 | UsedTiles, |
38 | UnusedTiles |
39 | }; |
40 | |
41 | SelectPaletteColorsCommand(); |
42 | |
43 | protected: |
44 | bool onEnabled(Context* context) override; |
45 | void onLoadParams(const Params& params) override; |
46 | void onExecute(Context* context) override; |
47 | std::string onGetFriendlyName() const override; |
48 | |
49 | private: |
50 | void selectTiles(const Layer* layer, |
51 | const SelectedFrames& selectedFrames, |
52 | PalettePicks& usedTiles); |
53 | |
54 | Modifier m_modifier; |
55 | }; |
56 | |
57 | SelectPaletteColorsCommand::SelectPaletteColorsCommand() |
58 | : Command(CommandId::SelectPaletteColors(), CmdRecordableFlag) |
59 | , m_modifier(Modifier::UsedColors) |
60 | { |
61 | } |
62 | |
63 | bool SelectPaletteColorsCommand::onEnabled(Context* context) |
64 | { |
65 | if (!context->checkFlags(ContextFlags::ActiveDocumentIsWritable | |
66 | ContextFlags::HasActiveSprite)) |
67 | return false; |
68 | |
69 | if (m_modifier == Modifier::UsedTiles || |
70 | m_modifier == Modifier::UnusedTiles) { |
71 | Layer* layer = context->activeSite().layer(); |
72 | return (layer && layer->isTilemap()); |
73 | } |
74 | |
75 | return true; |
76 | } |
77 | |
78 | void SelectPaletteColorsCommand::onLoadParams(const Params& params) |
79 | { |
80 | std::string strParam = params.get("modifier" ); |
81 | if (strParam == "unused_colors" ) |
82 | m_modifier = Modifier::UnusedColors; |
83 | else if (strParam == "used_tiles" ) |
84 | m_modifier = Modifier::UsedTiles; |
85 | else if (strParam == "unused_tiles" ) |
86 | m_modifier = Modifier::UnusedTiles; |
87 | else |
88 | m_modifier = Modifier::UsedColors; |
89 | } |
90 | |
91 | void SelectPaletteColorsCommand::selectTiles( |
92 | const Layer* layer, |
93 | const SelectedFrames& selectedFrames, |
94 | PalettePicks& usedTiles) |
95 | { |
96 | ASSERT(layer); |
97 | ASSERT(layer->isTilemap()); |
98 | |
99 | // For each tile on each cel's tilemap |
100 | for (frame_t frame : selectedFrames) { |
101 | if (Cel* cel = layer->cel(frame)) { |
102 | for (const doc::tile_t t : LockImageBits<TilemapTraits>(cel->image())) { |
103 | tile_index ti = doc::tile_geti(t); |
104 | if (ti >= 0 && ti < usedTiles.size()) |
105 | usedTiles[ti] = true; |
106 | } |
107 | } |
108 | } |
109 | } |
110 | |
111 | void SelectPaletteColorsCommand::onExecute(Context* context) |
112 | { |
113 | Site site = context->activeSite(); |
114 | Sprite* sprite = site.sprite(); |
115 | DocRange range = site.range(); |
116 | SelectedFrames selectedFrames; |
117 | SelectedLayers selectedLayers; |
118 | if (range.type() == DocRange::Type::kNone) { |
119 | // If there isn't a cels range selected, it assumes the whole sprite: |
120 | range.startRange(site.layer(), 0, DocRange::Type::kFrames); |
121 | range.endRange(site.layer(), sprite->lastFrame()); |
122 | selectedFrames = range.selectedFrames(); |
123 | selectedLayers.selectAllLayers(sprite->root()); |
124 | } |
125 | else { |
126 | selectedFrames = range.selectedFrames(); |
127 | selectedLayers = range.selectedLayers(); |
128 | } |
129 | |
130 | if (m_modifier == Modifier::UsedColors || |
131 | m_modifier == Modifier::UnusedColors) { |
132 | doc::OctreeMap octreemap; |
133 | const doc::Palette* currentPalette = get_current_palette(); |
134 | PalettePicks usedEntries(currentPalette->size()); |
135 | |
136 | auto countImage = [&octreemap, &usedEntries](const Image* image){ |
137 | switch (image->pixelFormat()) { |
138 | |
139 | case IMAGE_RGB: |
140 | case IMAGE_GRAYSCALE: |
141 | octreemap.feedWithImage(image, true, image->maskColor(), 8); |
142 | break; |
143 | |
144 | case IMAGE_INDEXED: |
145 | doc::for_each_pixel<IndexedTraits>( |
146 | image, |
147 | [&usedEntries](const color_t p) { |
148 | usedEntries[p] = true; |
149 | }); |
150 | break; |
151 | } |
152 | }; |
153 | |
154 | // Loop throught selected layers and frames |
155 | for (auto& layer : selectedLayers) { |
156 | for (frame_t frame : selectedFrames) { |
157 | const Cel* cel = layer->cel(frame); |
158 | if (cel && cel->image()) { |
159 | const Image* image = cel->image(); |
160 | |
161 | // Tilemap layer case |
162 | if (layer->isTilemap()) { |
163 | Tileset* tileset = static_cast<LayerTilemap*>(layer)->tileset(); |
164 | tile_index ti; |
165 | PalettePicks usedTiles(tileset->size()); |
166 | |
167 | // Looking for tiles (available in tileset) used in the tilemap image: |
168 | doc::for_each_pixel<TilemapTraits>( |
169 | image, |
170 | [&usedTiles, &tileset, &ti](const tile_t t) { |
171 | if (tileset->findTileIndex(tileset->get(t), ti)) |
172 | usedTiles[ti] = true; |
173 | }); |
174 | |
175 | // Looking for tile matches in usedTiles. If a tile matches, then |
176 | // search into the tilemap (pixel by pixel) looking for color matches. |
177 | for (int i=0; i<usedTiles.size(); ++i) { |
178 | if (usedTiles[i]) |
179 | countImage(tileset->get(i).get()); |
180 | } |
181 | } |
182 | // Regular layers |
183 | else { |
184 | countImage(image); |
185 | } |
186 | } |
187 | } |
188 | } |
189 | |
190 | doc::Palette tempPalette; |
191 | octreemap.makePalette(&tempPalette, std::numeric_limits<int>::max(), 8); |
192 | |
193 | for (int i=0; i < currentPalette->size(); ++i) { |
194 | if (tempPalette.findExactMatch(currentPalette->getEntry(i))) { |
195 | usedEntries[i] = true; |
196 | continue; |
197 | } |
198 | } |
199 | |
200 | if (m_modifier == Modifier::UnusedColors) |
201 | usedEntries.invert(); |
202 | context->setSelectedColors(usedEntries); |
203 | } |
204 | else if (m_modifier == Modifier::UsedTiles || |
205 | m_modifier == Modifier::UnusedTiles) { |
206 | Tileset* tileset = site.tileset(); |
207 | if (!tileset) |
208 | return; |
209 | |
210 | PalettePicks usedTiles(tileset->size()); |
211 | for (const Layer* layer : selectedLayers) { |
212 | if (layer->isTilemap() && |
213 | static_cast<const LayerTilemap*>(layer)->tileset() == tileset) { |
214 | selectTiles(layer, selectedFrames, usedTiles); |
215 | } |
216 | } |
217 | |
218 | if (m_modifier == Modifier::UnusedTiles) |
219 | usedTiles.invert(); |
220 | context->setSelectedTiles(usedTiles); |
221 | } |
222 | } |
223 | |
224 | std::string SelectPaletteColorsCommand::onGetFriendlyName() const |
225 | { |
226 | switch (m_modifier) { |
227 | case UsedColors: return Strings::commands_SelectPaletteColors(); |
228 | case UnusedColors: return Strings::commands_SelectPaletteColors_UnusedColors(); |
229 | case UsedTiles: return Strings::commands_SelectPaletteColors_UsedTiles(); |
230 | case UnusedTiles: return Strings::commands_SelectPaletteColors_UnusedTiles(); |
231 | } |
232 | return getBaseFriendlyName(); |
233 | } |
234 | |
235 | Command* CommandFactory::createSelectPaletteColorsCommand() |
236 | { |
237 | return new SelectPaletteColorsCommand; |
238 | } |
239 | |
240 | } // namespace app |
241 | |