1 // Aseprite Document Library
2 // Copyright (c) 2001-2017 David Capello
3 //
4 // This file is released under the terms of the MIT license.
5 // Read LICENSE.txt for more information.
6
7 #ifdef HAVE_CONFIG_H
8 #include "config.h"
9 #endif
10
11 #include "doc/palette.h"
12
13 #include "base/base.h"
14 #include "doc/image.h"
15 #include "doc/remap.h"
16
17 #include <algorithm>
18 #include <limits>
19
20 namespace doc {
21
22 using namespace gfx;
23
Palette(frame_t frame,int ncolors)24 Palette::Palette(frame_t frame, int ncolors)
25 : Object(ObjectType::Palette)
26 {
27 ASSERT(ncolors >= 0);
28
29 m_frame = frame;
30 m_colors.resize(ncolors, doc::rgba(0, 0, 0, 255));
31 m_modifications = 0;
32 }
33
Palette(const Palette & palette)34 Palette::Palette(const Palette& palette)
35 : Object(palette)
36 , m_comment(palette.m_comment)
37 {
38 m_frame = palette.m_frame;
39 m_colors = palette.m_colors;
40 m_modifications = 0;
41 }
42
Palette(const Palette & palette,const Remap & remap)43 Palette::Palette(const Palette& palette, const Remap& remap)
44 : Object(palette)
45 , m_comment(palette.m_comment)
46 {
47 m_frame = palette.m_frame;
48
49 resize(palette.size());
50 for (int i=0; i<size(); ++i)
51 setEntry(remap[i], palette.getEntry(i));
52
53 m_modifications = 0;
54 }
55
~Palette()56 Palette::~Palette()
57 {
58 }
59
createGrayscale()60 Palette* Palette::createGrayscale()
61 {
62 Palette* graypal = new Palette(frame_t(0), 256);
63 for (int c=0; c<256; c++)
64 graypal->setEntry(c, rgba(c, c, c, 255));
65 return graypal;
66 }
67
resize(int ncolors)68 void Palette::resize(int ncolors)
69 {
70 ASSERT(ncolors >= 0);
71
72 m_colors.resize(ncolors, doc::rgba(0, 0, 0, 255));
73 ++m_modifications;
74 }
75
addEntry(color_t color)76 void Palette::addEntry(color_t color)
77 {
78 resize(size()+1);
79 setEntry(size()-1, color);
80 }
81
hasAlpha() const82 bool Palette::hasAlpha() const
83 {
84 for (int i=0; i<(int)m_colors.size(); ++i)
85 if (rgba_geta(getEntry(i)) < 255)
86 return true;
87 return false;
88 }
89
setFrame(frame_t frame)90 void Palette::setFrame(frame_t frame)
91 {
92 ASSERT(frame >= 0);
93
94 m_frame = frame;
95 }
96
setEntry(int i,color_t color)97 void Palette::setEntry(int i, color_t color)
98 {
99 ASSERT(i >= 0 && i < size());
100
101 m_colors[i] = color;
102 ++m_modifications;
103 }
104
copyColorsTo(Palette * dst) const105 void Palette::copyColorsTo(Palette* dst) const
106 {
107 dst->m_colors = m_colors;
108 ++dst->m_modifications;
109 }
110
countDiff(const Palette * other,int * from,int * to) const111 int Palette::countDiff(const Palette* other, int* from, int* to) const
112 {
113 int c, diff = 0;
114 int min = MIN(this->m_colors.size(), other->m_colors.size());
115 int max = MAX(this->m_colors.size(), other->m_colors.size());
116
117 if (from) *from = -1;
118 if (to) *to = -1;
119
120 // Compare palettes
121 for (c=0; c<min; ++c) {
122 if (this->m_colors[c] != other->m_colors[c]) {
123 if (from && *from < 0) *from = c;
124 if (to) *to = c;
125 ++diff;
126 }
127 }
128
129 if (max != min) {
130 diff += max - min;
131 if (from && *from < 0) *from = min;
132 if (to) *to = max-1;
133 }
134
135 return diff;
136 }
137
isBlack() const138 bool Palette::isBlack() const
139 {
140 for (std::size_t c=0; c<m_colors.size(); ++c)
141 if (getEntry(c) != rgba(0, 0, 0, 255))
142 return false;
143
144 return true;
145 }
146
makeBlack()147 void Palette::makeBlack()
148 {
149 std::fill(m_colors.begin(), m_colors.end(), rgba(0, 0, 0, 255));
150 ++m_modifications;
151 }
152
153 // Creates a linear ramp in the palette.
makeGradient(int from,int to)154 void Palette::makeGradient(int from, int to)
155 {
156 int r, g, b, a;
157 int r1, g1, b1, a1;
158 int r2, g2, b2, a2;
159 int i, n;
160
161 ASSERT(from >= 0 && from < size());
162 ASSERT(to >= 0 && to < size());
163
164 if (from > to)
165 std::swap(from, to);
166
167 n = to - from;
168 if (n < 2)
169 return;
170
171 r1 = rgba_getr(getEntry(from));
172 g1 = rgba_getg(getEntry(from));
173 b1 = rgba_getb(getEntry(from));
174 a1 = rgba_geta(getEntry(from));
175
176 r2 = rgba_getr(getEntry(to));
177 g2 = rgba_getg(getEntry(to));
178 b2 = rgba_getb(getEntry(to));
179 a2 = rgba_geta(getEntry(to));
180
181 for (i=from+1; i<to; ++i) {
182 r = r1 + (r2-r1) * (i-from) / n;
183 g = g1 + (g2-g1) * (i-from) / n;
184 b = b1 + (b2-b1) * (i-from) / n;
185 a = a1 + (a2-a1) * (i-from) / n;
186
187 setEntry(i, rgba(r, g, b, a));
188 }
189 }
190
findExactMatch(int r,int g,int b,int a,int mask_index) const191 int Palette::findExactMatch(int r, int g, int b, int a, int mask_index) const
192 {
193 for (int i=0; i<(int)m_colors.size(); ++i)
194 if (getEntry(i) == rgba(r, g, b, a) && i != mask_index)
195 return i;
196
197 return -1;
198 }
199
200 //////////////////////////////////////////////////////////////////////
201 // Based on Allegro's bestfit_color
202
203 static std::vector<uint32_t> col_diff;
204 static uint32_t* col_diff_g;
205 static uint32_t* col_diff_r;
206 static uint32_t* col_diff_b;
207 static uint32_t* col_diff_a;
208
initBestfit()209 static void initBestfit()
210 {
211 col_diff.resize(4*128, 0);
212 col_diff_g = &col_diff[128*0];
213 col_diff_r = &col_diff[128*1];
214 col_diff_b = &col_diff[128*2];
215 col_diff_a = &col_diff[128*3];
216
217 for (int i=1; i<64; ++i) {
218 int k = i * i;
219 col_diff_g[i] = col_diff_g[128-i] = k * 59 * 59;
220 col_diff_r[i] = col_diff_r[128-i] = k * 30 * 30;
221 col_diff_b[i] = col_diff_b[128-i] = k * 11 * 11;
222 col_diff_a[i] = col_diff_a[128-i] = k * 8 * 8;
223 }
224 }
225
findBestfit(int r,int g,int b,int a,int mask_index) const226 int Palette::findBestfit(int r, int g, int b, int a, int mask_index) const
227 {
228 ASSERT(r >= 0 && r <= 255);
229 ASSERT(g >= 0 && g <= 255);
230 ASSERT(b >= 0 && b <= 255);
231 ASSERT(a >= 0 && a <= 255);
232
233 if (col_diff.empty())
234 initBestfit();
235
236 r >>= 3;
237 g >>= 3;
238 b >>= 3;
239 a >>= 3;
240
241 // Mask index is like alpha = 0, so we can use it as transparent color.
242 if (a == 0 && mask_index >= 0)
243 return mask_index;
244
245 int bestfit = 0;
246 int lowest = std::numeric_limits<int>::max();
247 int size = MIN(256, m_colors.size());
248
249 for (int i=0; i<size; ++i) {
250 color_t rgb = m_colors[i];
251
252 int coldiff = col_diff_g[((rgba_getg(rgb)>>3) - g) & 127];
253 if (coldiff < lowest) {
254 coldiff += col_diff_r[(((rgba_getr(rgb)>>3) - r) & 127)];
255 if (coldiff < lowest) {
256 coldiff += col_diff_b[(((rgba_getb(rgb)>>3) - b) & 127)];
257 if (coldiff < lowest) {
258 coldiff += col_diff_a[(((rgba_geta(rgb)>>3) - a) & 127)];
259 if (coldiff < lowest && i != mask_index) {
260 if (coldiff == 0)
261 return i;
262
263 bestfit = i;
264 lowest = coldiff;
265 }
266 }
267 }
268 }
269 }
270
271 return bestfit;
272 }
273
applyRemap(const Remap & remap)274 void Palette::applyRemap(const Remap& remap)
275 {
276 Palette original(*this);
277 for (int i=0; i<size(); ++i)
278 setEntry(remap[i], original.getEntry(i));
279 }
280
setEntryName(const int i,const std::string & name)281 void Palette::setEntryName(const int i, const std::string& name)
282 {
283 if (i >= int(m_names.size()))
284 m_names.resize(i+1);
285 m_names[i] = name;
286 }
287
getEntryName(const int i) const288 const std::string& Palette::getEntryName(const int i) const
289 {
290 if (i >= 0 && i < int(m_names.size()))
291 return m_names[i];
292 else {
293 static std::string emptyString;
294 return emptyString;
295 }
296 }
297
298 } // namespace doc
299