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
28namespace app {
29
30using namespace ui;
31
32class SelectPaletteColorsCommand : public Command {
33public:
34 enum Modifier {
35 UsedColors,
36 UnusedColors,
37 UsedTiles,
38 UnusedTiles
39 };
40
41 SelectPaletteColorsCommand();
42
43protected:
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
49private:
50 void selectTiles(const Layer* layer,
51 const SelectedFrames& selectedFrames,
52 PalettePicks& usedTiles);
53
54 Modifier m_modifier;
55};
56
57SelectPaletteColorsCommand::SelectPaletteColorsCommand()
58 : Command(CommandId::SelectPaletteColors(), CmdRecordableFlag)
59 , m_modifier(Modifier::UsedColors)
60{
61}
62
63bool 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
78void 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
91void 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
111void 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
224std::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
235Command* CommandFactory::createSelectPaletteColorsCommand()
236{
237 return new SelectPaletteColorsCommand;
238}
239
240} // namespace app
241