1 | // Aseprite |
2 | // Copyright (C) 2018-2021 Igara Studio S.A. |
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/cmd/convert_color_profile.h" |
12 | |
13 | #include "app/cmd/assign_color_profile.h" |
14 | #include "app/cmd/replace_image.h" |
15 | #include "app/cmd/set_palette.h" |
16 | #include "app/doc.h" |
17 | #include "doc/cels_range.h" |
18 | #include "doc/palette.h" |
19 | #include "doc/sprite.h" |
20 | #include "os/color_space.h" |
21 | #include "os/system.h" |
22 | |
23 | namespace app { |
24 | namespace cmd { |
25 | |
26 | static doc::ImageRef convert_image_color_space(const doc::Image* srcImage, |
27 | const gfx::ColorSpaceRef& newCS, |
28 | os::ColorSpaceConversion* conversion) |
29 | { |
30 | ImageSpec spec = srcImage->spec(); |
31 | spec.setColorSpace(newCS); |
32 | ImageRef dstImage(Image::create(spec)); |
33 | |
34 | if (!conversion) { |
35 | dstImage->copy(srcImage, gfx::Clip(0, 0, srcImage->bounds())); |
36 | return dstImage; |
37 | } |
38 | |
39 | if (spec.colorMode() == doc::ColorMode::RGB) { |
40 | for (int y=0; y<spec.height(); ++y) { |
41 | conversion->convertRgba((uint32_t*)dstImage->getPixelAddress(0, y), |
42 | (const uint32_t*)srcImage->getPixelAddress(0, y), |
43 | spec.width()); |
44 | } |
45 | } |
46 | else if (spec.colorMode() == doc::ColorMode::GRAYSCALE) { |
47 | // TODO create a set of functions to create pixel format |
48 | // conversions (this should be available when we add new kind of |
49 | // pixel formats). |
50 | std::vector<uint8_t> buf(spec.width()*spec.height()); |
51 | |
52 | auto it = buf.begin(); |
53 | for (int y=0; y<spec.height(); ++y) { |
54 | auto srcPtr = (const uint16_t*)srcImage->getPixelAddress(0, y); |
55 | for (int x=0; x<spec.width(); ++x, ++srcPtr, ++it) |
56 | *it = doc::graya_getv(*srcPtr); |
57 | } |
58 | |
59 | conversion->convertGray(&buf[0], &buf[0], spec.width()*spec.height()); |
60 | |
61 | it = buf.begin(); |
62 | for (int y=0; y<spec.height(); ++y) { |
63 | auto srcPtr = (const uint16_t*)srcImage->getPixelAddress(0, y); |
64 | auto dstPtr = (uint16_t*)dstImage->getPixelAddress(0, y); |
65 | for (int x=0; x<spec.width(); ++x, ++dstPtr, ++srcPtr, ++it) |
66 | *dstPtr = doc::graya(*it, doc::graya_geta(*srcPtr)); |
67 | } |
68 | } |
69 | |
70 | return dstImage; |
71 | } |
72 | |
73 | void convert_color_profile(doc::Sprite* sprite, |
74 | const gfx::ColorSpaceRef& newCS) |
75 | { |
76 | ASSERT(sprite->colorSpace()); |
77 | ASSERT(newCS); |
78 | |
79 | os::System* system = os::instance(); |
80 | auto srcOCS = system->makeColorSpace(sprite->colorSpace()); |
81 | auto dstOCS = system->makeColorSpace(newCS); |
82 | ASSERT(srcOCS); |
83 | ASSERT(dstOCS); |
84 | |
85 | auto conversion = system->convertBetweenColorSpace(srcOCS, dstOCS); |
86 | |
87 | // Convert images |
88 | if (sprite->pixelFormat() != doc::IMAGE_INDEXED) { |
89 | for (Cel* cel : sprite->uniqueCels()) { |
90 | ImageRef old_image = cel->imageRef(); |
91 | if (old_image.get()->pixelFormat() != IMAGE_TILEMAP) { |
92 | ImageRef new_image = convert_image_color_space( |
93 | old_image.get(), newCS, conversion.get()); |
94 | |
95 | sprite->replaceImage(old_image->id(), new_image); |
96 | } |
97 | } |
98 | } |
99 | |
100 | if (conversion) { |
101 | // Convert palette |
102 | if (sprite->pixelFormat() != doc::IMAGE_GRAYSCALE) { |
103 | for (auto& pal : sprite->getPalettes()) { |
104 | Palette newPal(pal->frame(), pal->size()); |
105 | |
106 | for (int i=0; i<pal->size(); ++i) { |
107 | color_t oldCol = pal->entry(i); |
108 | color_t newCol = pal->entry(i); |
109 | conversion->convertRgba((uint32_t*)&newCol, |
110 | (const uint32_t*)&oldCol, 1); |
111 | newPal.setEntry(i, newCol); |
112 | } |
113 | |
114 | if (*pal != newPal) |
115 | sprite->setPalette(&newPal, false); |
116 | } |
117 | } |
118 | } |
119 | |
120 | sprite->setColorSpace(newCS); |
121 | |
122 | // Generate notification so the Doc::osColorSpace() is regenerated |
123 | static_cast<Doc*>(sprite->document())->notifyColorSpaceChanged(); |
124 | } |
125 | |
126 | void convert_color_profile(doc::Image* image, |
127 | doc::Palette* palette, |
128 | const gfx::ColorSpaceRef& oldCS, |
129 | const gfx::ColorSpaceRef& newCS) |
130 | { |
131 | ASSERT(oldCS); |
132 | ASSERT(newCS); |
133 | |
134 | os::System* system = os::instance(); |
135 | auto srcOCS = system->makeColorSpace(oldCS); |
136 | auto dstOCS = system->makeColorSpace(newCS); |
137 | ASSERT(srcOCS); |
138 | ASSERT(dstOCS); |
139 | |
140 | auto conversion = system->convertBetweenColorSpace(srcOCS, dstOCS); |
141 | if (conversion) { |
142 | switch (image->pixelFormat()) { |
143 | case doc::IMAGE_RGB: |
144 | case doc::IMAGE_GRAYSCALE: { |
145 | ImageRef newImage = convert_image_color_space( |
146 | image, newCS, conversion.get()); |
147 | |
148 | image->copy(newImage.get(), gfx::Clip(image->bounds())); |
149 | break; |
150 | } |
151 | |
152 | case doc::IMAGE_INDEXED: { |
153 | for (int i=0; i<palette->size(); ++i) { |
154 | color_t oldCol, newCol; |
155 | oldCol = newCol = palette->entry(i); |
156 | conversion->convertRgba((uint32_t*)&newCol, |
157 | (const uint32_t*)&oldCol, 1); |
158 | palette->setEntry(i, newCol); |
159 | } |
160 | break; |
161 | } |
162 | } |
163 | } |
164 | } |
165 | |
166 | ConvertColorProfile::ConvertColorProfile(doc::Sprite* sprite, const gfx::ColorSpaceRef& newCS) |
167 | : WithSprite(sprite) |
168 | { |
169 | os::System* system = os::instance(); |
170 | |
171 | ASSERT(sprite->colorSpace()); |
172 | ASSERT(newCS); |
173 | |
174 | auto srcOCS = system->makeColorSpace(sprite->colorSpace()); |
175 | auto dstOCS = system->makeColorSpace(newCS); |
176 | |
177 | ASSERT(srcOCS); |
178 | ASSERT(dstOCS); |
179 | |
180 | auto conversion = system->convertBetweenColorSpace(srcOCS, dstOCS); |
181 | |
182 | // Convert images |
183 | if (sprite->pixelFormat() != doc::IMAGE_INDEXED) { |
184 | for (Cel* cel : sprite->uniqueCels()) { |
185 | ImageRef old_image = cel->imageRef(); |
186 | if (old_image.get()->pixelFormat() != IMAGE_TILEMAP) { |
187 | ImageRef new_image = convert_image_color_space( |
188 | old_image.get(), newCS, conversion.get()); |
189 | |
190 | m_seq.add(new cmd::ReplaceImage(sprite, old_image, new_image)); |
191 | } |
192 | } |
193 | } |
194 | |
195 | if (conversion) { |
196 | // Convert palette |
197 | if (sprite->pixelFormat() != doc::IMAGE_GRAYSCALE) { |
198 | for (auto& pal : sprite->getPalettes()) { |
199 | Palette newPal(pal->frame(), pal->size()); |
200 | |
201 | for (int i=0; i<pal->size(); ++i) { |
202 | color_t oldCol = pal->entry(i); |
203 | color_t newCol = pal->entry(i); |
204 | conversion->convertRgba((uint32_t*)&newCol, |
205 | (const uint32_t*)&oldCol, 1); |
206 | newPal.setEntry(i, newCol); |
207 | } |
208 | |
209 | if (*pal != newPal) |
210 | m_seq.add(new cmd::SetPalette(sprite, pal->frame(), &newPal)); |
211 | } |
212 | } |
213 | } |
214 | |
215 | m_seq.add(new cmd::AssignColorProfile(sprite, newCS)); |
216 | } |
217 | |
218 | void ConvertColorProfile::onExecute() |
219 | { |
220 | m_seq.execute(context()); |
221 | } |
222 | |
223 | void ConvertColorProfile::onUndo() |
224 | { |
225 | m_seq.undo(); |
226 | } |
227 | |
228 | void ConvertColorProfile::onRedo() |
229 | { |
230 | m_seq.redo(); |
231 | } |
232 | |
233 | } // namespace cmd |
234 | } // namespace app |
235 | |