1// Aseprite
2// Copyright (C) 2018-2022 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/cmd/assign_color_profile.h"
13#include "app/cmd/convert_color_profile.h"
14#include "app/cmd/set_pixel_ratio.h"
15#include "app/cmd/set_user_data.h"
16#include "app/color.h"
17#include "app/commands/command.h"
18#include "app/context_access.h"
19#include "app/doc_api.h"
20#include "app/i18n/strings.h"
21#include "app/modules/gui.h"
22#include "app/pref/preferences.h"
23#include "app/tx.h"
24#include "app/ui/color_button.h"
25#include "app/ui/user_data_view.h"
26#include "app/util/pixel_ratio.h"
27#include "base/mem_utils.h"
28#include "doc/image.h"
29#include "doc/palette.h"
30#include "doc/sprite.h"
31#include "doc/user_data.h"
32#include "fmt/format.h"
33#include "os/color_space.h"
34#include "os/system.h"
35#include "ui/ui.h"
36
37#include "sprite_properties.xml.h"
38
39namespace app {
40
41using namespace ui;
42
43class SpritePropertiesWindow : public app::gen::SpriteProperties {
44public:
45 SpritePropertiesWindow(Sprite* sprite)
46 : SpriteProperties()
47 , m_sprite(sprite)
48 , m_userDataView(Preferences::instance().sprite.userDataVisibility)
49 {
50 userData()->Click.connect([this]{ onToggleUserData(); });
51
52 m_userDataView.configureAndSet(m_sprite->userData(),
53 propertiesGrid());
54
55 remapWindow();
56 centerWindow();
57 load_window_pos(this, "SpriteProperties");
58 manager()->invalidate();
59 }
60
61 const UserData& getUserData() const { return m_userDataView.userData(); }
62
63private:
64 void onToggleUserData() {
65 m_userDataView.toggleVisibility();
66 remapWindow();
67 manager()->invalidate();
68 }
69
70 Sprite* m_sprite;
71 UserDataView m_userDataView;
72};
73
74class SpritePropertiesCommand : public Command {
75public:
76 SpritePropertiesCommand();
77
78protected:
79 bool onEnabled(Context* context) override;
80 void onExecute(Context* context) override;
81};
82
83SpritePropertiesCommand::SpritePropertiesCommand()
84 : Command(CommandId::SpriteProperties(), CmdUIOnlyFlag)
85{
86}
87
88bool SpritePropertiesCommand::onEnabled(Context* context)
89{
90 return context->checkFlags(ContextFlags::ActiveDocumentIsWritable |
91 ContextFlags::HasActiveSprite);
92}
93
94void SpritePropertiesCommand::onExecute(Context* context)
95{
96 std::string imgtype_text;
97 ColorButton* color_button = nullptr;
98
99 // List of available color profiles
100 std::vector<os::ColorSpaceRef> colorSpaces;
101 os::instance()->listColorSpaces(colorSpaces);
102
103 // Load the window widget
104 SpritePropertiesWindow window(context->activeDocument()->sprite());
105
106 int selectedColorProfile = -1;
107
108 auto updateButtons =
109 [&] {
110 bool enabled = (selectedColorProfile != window.colorProfile()->getSelectedItemIndex());
111 window.assignColorProfile()->setEnabled(enabled);
112 window.convertColorProfile()->setEnabled(enabled);
113 window.ok()->setEnabled(!enabled);
114 };
115
116 // Get sprite properties and fill frame fields
117 {
118 const ContextReader reader(context);
119 const Doc* document(reader.document());
120 const Sprite* sprite(reader.sprite());
121
122 // Update widgets values
123 switch (sprite->pixelFormat()) {
124 case IMAGE_RGB:
125 imgtype_text = Strings::sprite_properties_rgb();
126 break;
127 case IMAGE_GRAYSCALE:
128 imgtype_text = Strings::sprite_properties_grayscale();
129 break;
130 case IMAGE_INDEXED:
131 imgtype_text = fmt::format(Strings::sprite_properties_indexed_color(),
132 sprite->palette(0)->size());
133 break;
134 default:
135 ASSERT(false);
136 imgtype_text = Strings::general_unknown();
137 break;
138 }
139
140 // Filename
141 window.name()->setText(document->filename());
142
143 // Color mode
144 window.type()->setText(imgtype_text.c_str());
145
146 // Sprite size (width and height)
147 window.size()->setTextf(
148 "%dx%d (%s)",
149 sprite->width(),
150 sprite->height(),
151 base::get_pretty_memory_size(sprite->getMemSize()).c_str());
152
153 // How many frames
154 window.frames()->setTextf("%d", (int)sprite->totalFrames());
155
156 if (sprite->pixelFormat() == IMAGE_INDEXED) {
157 color_button = new ColorButton(app::Color::fromIndex(sprite->transparentColor()),
158 IMAGE_INDEXED,
159 ColorButtonOptions());
160
161 window.transparentColorPlaceholder()->addChild(color_button);
162
163 // TODO add a way to get or create an existent TooltipManager
164 TooltipManager* tooltipManager = new TooltipManager;
165 window.addChild(tooltipManager);
166 tooltipManager->addTooltipFor(
167 color_button,
168 Strings::sprite_properties_transparent_color_tooltip(),
169 LEFT);
170 }
171 else {
172 window.transparentColorPlaceholder()->addChild(
173 new Label(Strings::sprite_properties_indexed_image_only()));
174 }
175
176 // Pixel ratio
177 window.pixelRatio()->setValue(
178 base::convert_to<std::string>(sprite->pixelRatio()));
179
180 // Color profile
181 selectedColorProfile = -1;
182 int i = 0;
183 for (auto& cs : colorSpaces) {
184 if (cs->gfxColorSpace()->nearlyEqual(*sprite->colorSpace())) {
185 selectedColorProfile = i;
186 break;
187 }
188 ++i;
189 }
190 if (selectedColorProfile < 0) {
191 colorSpaces.push_back(os::instance()->makeColorSpace(sprite->colorSpace()));
192 selectedColorProfile = colorSpaces.size()-1;
193 }
194
195 for (auto& cs : colorSpaces)
196 window.colorProfile()->addItem(cs->gfxColorSpace()->name());
197 window.colorProfile()->setSelectedItemIndex(selectedColorProfile);
198
199 window.assignColorProfile()->setEnabled(false);
200 window.convertColorProfile()->setEnabled(false);
201 window.colorProfile()->Change.connect(updateButtons);
202
203 window.assignColorProfile()->Click.connect(
204 [&](Event&){
205 selectedColorProfile = window.colorProfile()->getSelectedItemIndex();
206
207 ContextWriter writer(context);
208 Sprite* sprite(writer.sprite());
209 Tx tx(writer.context(), Strings::sprite_properties_assign_color_profile());
210 tx(new cmd::AssignColorProfile(
211 sprite, colorSpaces[selectedColorProfile]->gfxColorSpace()));
212 tx.commit();
213
214 updateButtons();
215 });
216 window.convertColorProfile()->Click.connect(
217 [&](Event&){
218 selectedColorProfile = window.colorProfile()->getSelectedItemIndex();
219
220 ContextWriter writer(context);
221 Sprite* sprite(writer.sprite());
222 Tx tx(writer.context(), Strings::sprite_properties_convert_color_profile());
223 tx(new cmd::ConvertColorProfile(
224 sprite, colorSpaces[selectedColorProfile]->gfxColorSpace()));
225 tx.commit();
226
227 updateButtons();
228 });
229 }
230
231 window.remapWindow();
232 window.centerWindow();
233
234 load_window_pos(&window, "SpriteProperties");
235 window.setVisible(true);
236 window.openWindowInForeground();
237
238 if (window.closer() == window.ok()) {
239 ContextWriter writer(context);
240 Sprite* sprite(writer.sprite());
241
242 color_t index = (color_button ? color_button->getColor().getIndex():
243 sprite->transparentColor());
244 PixelRatio pixelRatio =
245 base::convert_to<PixelRatio>(window.pixelRatio()->getValue());
246
247 const UserData newUserData = window.getUserData();
248
249 if (index != sprite->transparentColor() ||
250 pixelRatio != sprite->pixelRatio() ||
251 newUserData != sprite->userData()) {
252 Tx tx(writer.context(), Strings::sprite_properties_change_sprite_props());
253 DocApi api = writer.document()->getApi(tx);
254
255 if (index != sprite->transparentColor())
256 api.setSpriteTransparentColor(sprite, index);
257
258 if (pixelRatio != sprite->pixelRatio())
259 tx(new cmd::SetPixelRatio(sprite, pixelRatio));
260
261 if (newUserData != sprite->userData())
262 tx(new cmd::SetUserData(sprite, newUserData, static_cast<Doc*>(sprite->document())));
263
264 tx.commit();
265
266 update_screen_for_document(writer.document());
267 }
268 }
269
270 save_window_pos(&window, "SpriteProperties");
271}
272
273Command* CommandFactory::createSpritePropertiesCommand()
274{
275 return new SpritePropertiesCommand;
276}
277
278} // namespace app
279