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
23namespace app {
24namespace cmd {
25
26static 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
73void 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
126void 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
166ConvertColorProfile::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
218void ConvertColorProfile::onExecute()
219{
220 m_seq.execute(context());
221}
222
223void ConvertColorProfile::onUndo()
224{
225 m_seq.undo();
226}
227
228void ConvertColorProfile::onRedo()
229{
230 m_seq.redo();
231}
232
233} // namespace cmd
234} // namespace app
235