1// Aseprite
2// Copyright (C) 2019-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/set_pixel_format.h"
13
14#include "app/cmd/remove_palette.h"
15#include "app/cmd/replace_image.h"
16#include "app/cmd/set_cel_opacity.h"
17#include "app/cmd/set_palette.h"
18#include "app/doc.h"
19#include "app/doc_event.h"
20#include "doc/cel.h"
21#include "doc/cels_range.h"
22#include "doc/document.h"
23#include "doc/layer.h"
24#include "doc/palette.h"
25#include "doc/rgbmap.h"
26#include "doc/sprite.h"
27#include "doc/tilesets.h"
28#include "render/quantization.h"
29#include "render/task_delegate.h"
30
31namespace app {
32namespace cmd {
33
34using namespace doc;
35
36namespace {
37
38class SuperDelegate : public render::TaskDelegate {
39public:
40 SuperDelegate(int nimages, render::TaskDelegate* delegate)
41 : m_nimages(nimages)
42 , m_curImage(0)
43 , m_delegate(delegate) {
44 }
45
46 void notifyTaskProgress(double progress) override {
47 if (m_delegate)
48 m_delegate->notifyTaskProgress(
49 (progress + m_curImage) / m_nimages);
50 }
51
52 bool continueTask() override {
53 if (m_delegate)
54 return m_delegate->continueTask();
55 else
56 return true;
57 }
58
59 void nextImage() {
60 ++m_curImage;
61 }
62
63private:
64 int m_nimages;
65 int m_curImage;
66 TaskDelegate* m_delegate;
67};
68
69} // anonymous namespace
70
71SetPixelFormat::SetPixelFormat(Sprite* sprite,
72 const PixelFormat newFormat,
73 const render::Dithering& dithering,
74 const doc::RgbMapAlgorithm mapAlgorithm,
75 doc::rgba_to_graya_func toGray,
76 render::TaskDelegate* delegate)
77 : WithSprite(sprite)
78 , m_oldFormat(sprite->pixelFormat())
79 , m_newFormat(newFormat)
80{
81 if (sprite->pixelFormat() == newFormat)
82 return;
83
84 // Calculate the number of images to convert just to show a proper
85 // progress bar.
86 tile_index nimages = 0;
87 for (Cel* cel : sprite->uniqueCels())
88 if (!cel->layer()->isTilemap())
89 ++nimages;
90 if (sprite->hasTilesets()) {
91 for (Tileset* tileset : *sprite->tilesets())
92 nimages += tileset->size();
93 }
94
95 SuperDelegate superDel(nimages, delegate);
96
97 // Convert cel images
98 for (Cel* cel : sprite->uniqueCels()) {
99 if (cel->layer()->isTilemap())
100 continue;
101
102 ImageRef oldImage = cel->imageRef();
103 convertImage(sprite, dithering,
104 oldImage,
105 cel->frame(),
106 cel->layer()->isBackground(),
107 mapAlgorithm,
108 toGray,
109 &superDel);
110
111 superDel.nextImage();
112 }
113
114 // Convert tileset images
115 if (sprite->hasTilesets()) {
116 for (Tileset* tileset : *sprite->tilesets()) {
117 for (tile_index i=0; i<tileset->size(); ++i) {
118 ImageRef oldImage = tileset->get(i);
119 if (oldImage) {
120 convertImage(sprite, dithering,
121 oldImage,
122 0, // TODO select a frame or generate other tilesets?
123 false, // TODO is background? it depends of the layer where this tileset is used
124 mapAlgorithm,
125 toGray,
126 &superDel);
127 }
128 superDel.nextImage();
129 }
130 }
131 }
132
133 // Set all cels opacity to 100% if we are converting to indexed.
134 // TODO remove this
135 if (newFormat == IMAGE_INDEXED) {
136 for (Cel* cel : sprite->uniqueCels()) {
137 if (cel->opacity() < 255)
138 m_seq.add(new cmd::SetCelOpacity(cel, 255));
139 }
140 }
141
142 // When we are converting to grayscale color mode, we've to destroy
143 // all palettes and put only one grayscaled-palette at the first
144 // frame.
145 if (newFormat == IMAGE_GRAYSCALE) {
146 // Add cmds to revert all palette changes.
147 PalettesList palettes = sprite->getPalettes();
148 for (Palette* pal : palettes)
149 if (pal->frame() != 0)
150 m_seq.add(new cmd::RemovePalette(sprite, pal));
151
152 std::unique_ptr<Palette> graypal(Palette::createGrayscale());
153 if (*graypal != *sprite->palette(0))
154 m_seq.add(new cmd::SetPalette(sprite, 0, graypal.get()));
155 }
156}
157
158void SetPixelFormat::onExecute()
159{
160 m_seq.execute(context());
161 setFormat(m_newFormat);
162}
163
164void SetPixelFormat::onUndo()
165{
166 m_seq.undo();
167 setFormat(m_oldFormat);
168}
169
170void SetPixelFormat::onRedo()
171{
172 m_seq.redo();
173 setFormat(m_newFormat);
174}
175
176void SetPixelFormat::setFormat(PixelFormat format)
177{
178 Sprite* sprite = this->sprite();
179
180 sprite->setPixelFormat(format);
181 if (format == IMAGE_INDEXED) {
182 int maskIndex = sprite->palette(0)->findMaskColor();
183 sprite->setTransparentColor(maskIndex == -1 ? 0 : maskIndex);
184 }
185 else
186 sprite->setTransparentColor(0);
187 sprite->incrementVersion();
188
189 // Regenerate extras
190 Doc* doc = static_cast<Doc*>(sprite->document());
191 doc->setExtraCel(ExtraCelRef(nullptr));
192
193 // Generate notification
194 DocEvent ev(doc);
195 ev.sprite(sprite);
196 doc->notify_observers<DocEvent&>(&DocObserver::onPixelFormatChanged, ev);
197}
198
199void SetPixelFormat::convertImage(doc::Sprite* sprite,
200 const render::Dithering& dithering,
201 const doc::ImageRef& oldImage,
202 const doc::frame_t frame,
203 const bool isBackground,
204 const doc::RgbMapAlgorithm mapAlgorithm,
205 doc::rgba_to_graya_func toGray,
206 render::TaskDelegate* delegate)
207{
208 ASSERT(oldImage);
209 ASSERT(oldImage->pixelFormat() != IMAGE_TILEMAP);
210
211 // Making the RGBMap for Image->INDEXDED conversion.
212 // TODO: this is needed only when newImage
213 RgbMap* rgbmap;
214 int newMaskIndex = (isBackground ? -1 : 0);
215 if (m_newFormat == IMAGE_INDEXED) {
216 rgbmap = sprite->rgbMap(frame, sprite->rgbMapForSprite(), mapAlgorithm);
217 if (m_oldFormat == IMAGE_INDEXED)
218 newMaskIndex = sprite->transparentColor();
219 else
220 newMaskIndex = rgbmap->maskIndex();
221 }
222 else {
223 rgbmap = nullptr;
224 }
225 ImageRef newImage(
226 render::convert_pixel_format
227 (oldImage.get(), nullptr, m_newFormat,
228 dithering,
229 rgbmap,
230 sprite->palette(frame),
231 isBackground,
232 newMaskIndex,
233 toGray,
234 delegate));
235
236 m_seq.add(new cmd::ReplaceImage(sprite, oldImage, newImage));
237}
238
239} // namespace cmd
240} // namespace app
241