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/app.h"
13#include "app/color.h"
14#include "app/color_spaces.h"
15#include "app/color_utils.h"
16#include "app/commands/command.h"
17#include "app/commands/new_params.h"
18#include "app/console.h"
19#include "app/doc.h"
20#include "app/i18n/strings.h"
21#include "app/modules/editors.h"
22#include "app/modules/palettes.h"
23#include "app/pref/preferences.h"
24#include "app/ui/button_set.h"
25#include "app/ui/workspace.h"
26#include "app/ui_context.h"
27#include "app/util/clipboard.h"
28#include "app/util/pixel_ratio.h"
29#include "doc/cel.h"
30#include "doc/image.h"
31#include "doc/layer.h"
32#include "doc/palette.h"
33#include "doc/primitives.h"
34#include "doc/sprite.h"
35#include "fmt/format.h"
36#include "render/quantization.h"
37#include "ui/ui.h"
38
39#include "new_sprite.xml.h"
40
41using namespace ui;
42
43namespace app {
44
45struct NewFileParams : public NewParams {
46 Param<bool> ui { this, true, "ui" };
47 Param<int> width { this, 0, "width" };
48 Param<int> height { this, 0, "height" };
49 Param<ColorMode> colorMode { this, ColorMode::RGB, "colorMode" };
50 Param<bool> fromClipboard { this, false, "fromClipboard" };
51};
52
53class NewFileCommand : public CommandWithNewParams<NewFileParams> {
54public:
55 NewFileCommand();
56
57protected:
58 bool onEnabled(Context* context) override;
59 void onExecute(Context* ctx) override;
60 std::string onGetFriendlyName() const override;
61
62 static int g_spriteCounter;
63};
64
65// static
66int NewFileCommand::g_spriteCounter = 0;
67
68NewFileCommand::NewFileCommand()
69 : CommandWithNewParams(CommandId::NewFile(), CmdRecordableFlag)
70{
71}
72
73bool NewFileCommand::onEnabled(Context* ctx)
74{
75 return
76 (!params().fromClipboard()
77#ifdef ENABLE_UI
78 || (ctx->clipboard()->format() == ClipboardFormat::Image)
79#endif
80 );
81}
82
83void NewFileCommand::onExecute(Context* ctx)
84{
85 int width = params().width();
86 int height = params().height();
87 doc::ColorMode colorMode = params().colorMode();
88 app::Color bgColor = app::Color::fromMask();
89 doc::PixelRatio pixelRatio(1, 1);
90#ifdef ENABLE_UI
91 doc::ImageRef clipboardImage;
92 doc::Palette clipboardPalette(0, 256);
93#endif
94 const int ncolors = get_default_palette()->size();
95
96#ifdef ENABLE_UI
97 if (params().fromClipboard()) {
98 clipboardImage = ctx->clipboard()->getImage(&clipboardPalette);
99 if (!clipboardImage)
100 return;
101
102 width = clipboardImage->width();
103 height = clipboardImage->height();
104 colorMode = clipboardImage->colorMode();
105 }
106 else if (ctx->isUIAvailable() && params().ui()) {
107 Preferences& pref = Preferences::instance();
108 app::Color bg_table[] = { app::Color::fromMask(),
109 app::Color::fromRgb(255, 255, 255),
110 app::Color::fromRgb(0, 0, 0) };
111
112 // Load the window widget
113 app::gen::NewSprite window;
114
115 // Default values: Indexed, 320x240, Background color
116 if (!params().colorMode.isSet()) {
117 colorMode = pref.newFile.colorMode();
118 // Invalid format in config file.
119 if (colorMode != ColorMode::RGB &&
120 colorMode != ColorMode::INDEXED &&
121 colorMode != ColorMode::GRAYSCALE) {
122 colorMode = ColorMode::INDEXED;
123 }
124 }
125
126 int w = pref.newFile.width();
127 int h = pref.newFile.height();
128 int bg = pref.newFile.backgroundColor();
129 bg = std::clamp(bg, 0, 2);
130
131 // If the clipboard contains an image, we can show the size of the
132 // clipboard as default image size.
133 gfx::Size clipboardSize;
134 if (ctx->clipboard()->getImageSize(clipboardSize)) {
135 w = clipboardSize.w;
136 h = clipboardSize.h;
137 }
138
139 if (params().width.isSet()) w = width;
140 if (params().height.isSet()) h = height;
141
142 window.width()->setTextf("%d", std::max(1, w));
143 window.height()->setTextf("%d", std::max(1, h));
144
145 // Select image-type
146 window.colorMode()->setSelectedItem(int(colorMode));
147
148 // Select background color
149 window.bgColor()->setSelectedItem(bg);
150
151 // Advance options
152 bool advanced = pref.newFile.advanced();
153 window.advancedCheck()->setSelected(advanced);
154 window.advancedCheck()->Click.connect(
155 [&]{
156 window.advanced()->setVisible(window.advancedCheck()->isSelected());
157 window.expandWindow(window.sizeHint());
158 });
159 window.advanced()->setVisible(advanced);
160 if (advanced)
161 window.pixelRatio()->setValue(pref.newFile.pixelRatio());
162 else
163 window.pixelRatio()->setValue("1:1");
164
165 // Open the window
166 window.openWindowInForeground();
167
168 if (window.closer() != window.okButton())
169 return;
170
171 bool ok = false;
172
173 // Get the options
174 colorMode = (doc::ColorMode)window.colorMode()->selectedItem();
175 w = window.width()->textInt();
176 h = window.height()->textInt();
177 bg = window.bgColor()->selectedItem();
178 if (window.advancedCheck()->isSelected()) {
179 pixelRatio = base::convert_to<PixelRatio>(
180 window.pixelRatio()->getValue());
181 }
182
183 static_assert(int(ColorMode::RGB) == 0, "RGB pixel format should be 0");
184 static_assert(int(ColorMode::INDEXED) == 2, "Indexed pixel format should be 2");
185
186 colorMode = std::clamp(colorMode, ColorMode::RGB, ColorMode::INDEXED);
187 w = std::clamp(w, 1, DOC_SPRITE_MAX_WIDTH);
188 h = std::clamp(h, 1, DOC_SPRITE_MAX_HEIGHT);
189 bg = std::clamp(bg, 0, 2);
190
191 // Select the background color
192 if (bg >= 0 && bg <= 3) {
193 bgColor = bg_table[bg];
194 ok = true;
195 }
196
197 if (ok) {
198 // Save the configuration
199 pref.newFile.width(w);
200 pref.newFile.height(h);
201 pref.newFile.colorMode(colorMode);
202 pref.newFile.backgroundColor(bg);
203 pref.newFile.advanced(window.advancedCheck()->isSelected());
204 pref.newFile.pixelRatio(window.pixelRatio()->getValue());
205 }
206
207 width = w;
208 height = h;
209 }
210#endif // ENABLE_UI
211
212 ASSERT(colorMode == ColorMode::RGB ||
213 colorMode == ColorMode::GRAYSCALE ||
214 colorMode == ColorMode::INDEXED);
215 if (width < 1 || height < 1)
216 return;
217
218 // Create the new sprite
219 std::unique_ptr<Sprite> sprite(
220 Sprite::MakeStdSprite(
221 ImageSpec(colorMode, width, height, 0,
222 get_working_rgb_space_from_preferences()), ncolors));
223
224 sprite->setPixelRatio(pixelRatio);
225
226 if (sprite->colorMode() != ColorMode::GRAYSCALE)
227 get_default_palette()->copyColorsTo(sprite->palette(frame_t(0)));
228
229 Layer* layer = sprite->root()->firstLayer();
230 if (layer && layer->isImage()) {
231 // If the background color isn't transparent, we have to
232 // convert the `Layer 1' in a `Background'
233 if (bgColor.getType() != app::Color::MaskType) {
234 LayerImage* layerImage = static_cast<LayerImage*>(layer);
235 layerImage->configureAsBackground();
236
237 Image* image = layerImage->cel(frame_t(0))->image();
238
239 // TODO Replace this adding a new parameter to color utils
240 Palette oldPal = *get_current_palette();
241 set_current_palette(get_default_palette(), false);
242
243 doc::clear_image(
244 image,
245 color_utils::color_for_target(
246 bgColor,
247 ColorTarget(
248 ColorTarget::BackgroundLayer,
249 sprite->pixelFormat(),
250 sprite->transparentColor())));
251
252 set_current_palette(&oldPal, false);
253 }
254#ifdef ENABLE_UI
255 else if (clipboardImage) {
256 LayerImage* layerImage = static_cast<LayerImage*>(layer);
257 // layerImage->configureAsBackground();
258
259 Image* image = layerImage->cel(frame_t(0))->image();
260 image->copy(clipboardImage.get(), gfx::Clip(clipboardImage->bounds()));
261
262 if (clipboardPalette.isBlack()) {
263 render::create_palette_from_sprite(
264 sprite.get(), 0, sprite->lastFrame(), true,
265 &clipboardPalette, nullptr, true,
266 Preferences::instance().quantization.rgbmapAlgorithm());
267 }
268 sprite->setPalette(&clipboardPalette, false);
269 }
270#endif // ENABLE_UI
271
272 if (layer->isBackground())
273 layer->setName(Strings::commands_NewFile_BackgroundLayer());
274 else
275 layer->setName(fmt::format("{} {}", Strings::commands_NewLayer_Layer(), 1));
276 }
277
278 // Show the sprite to the user
279 std::unique_ptr<Doc> doc(new Doc(sprite.get()));
280 sprite.release();
281 doc->setFilename(fmt::format("{}-{:04d}",
282 Strings::commands_NewFile_Sprite(), ++g_spriteCounter));
283 doc->setContext(ctx);
284 doc.release();
285}
286
287std::string NewFileCommand::onGetFriendlyName() const
288{
289 if (params().fromClipboard())
290 return fmt::format(Strings::commands_NewFile_FromClipboard());
291 else
292 return fmt::format(Strings::commands_NewFile());
293}
294
295Command* CommandFactory::createNewFileCommand()
296{
297 return new NewFileCommand;
298}
299
300} // namespace app
301