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