1// Aseprite
2// Copyright (c) 2020-2022 Igara Studio S.A.
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/octree_map.h"
12
13#include "doc/palette.h"
14
15#define MIN_LEVEL_OCTREE_DEEP 3
16#define MIN_ALPHA_THRESHOLD 16
17
18namespace doc {
19
20//////////////////////////////////////////////////////////////////////
21// OctreeNode
22
23void OctreeNode::addColor(color_t c, int level, OctreeNode* parent,
24 int paletteIndex, int levelDeep)
25{
26 m_parent = parent;
27 if (level >= levelDeep) {
28 m_leafColor.add(c);
29 m_paletteIndex = paletteIndex;
30 return;
31 }
32 int index = getHextet(c, level);
33 if (!m_children) {
34 m_children.reset(new std::array<OctreeNode, 16>());
35 }
36 (*m_children)[index].addColor(c, level + 1, this, paletteIndex, levelDeep);
37}
38
39int OctreeNode::mapColor(int r, int g, int b, int a, int mask_index, const Palette* palette, int level) const
40{
41 // New behavior: if mapColor do not have an exact rgba match, it must calculate which
42 // color of the current palette is the bestfit and memorize the index in a octree leaf.
43 if (level >= 8) {
44 if (m_paletteIndex == -1)
45 m_paletteIndex = palette->findBestfit(r, g, b, a, mask_index);
46 return m_paletteIndex;
47 }
48 int index = getHextet(r, g, b, a, level);
49 if (!m_children)
50 m_children.reset(new std::array<OctreeNode, 16>());
51 return (*m_children)[index].mapColor(r, g, b, a, mask_index, palette, level + 1);
52}
53
54void OctreeNode::collectLeafNodes(OctreeNodes& leavesVector, int& paletteIndex)
55{
56 for (int i=0; i<16; i++) {
57 OctreeNode& child = (*m_children)[i];
58
59 if (child.isLeaf()) {
60 child.paletteIndex(paletteIndex);
61 leavesVector.push_back(&child);
62 paletteIndex++;
63 }
64 else if (child.hasChildren()) {
65 child.collectLeafNodes(leavesVector, paletteIndex);
66 }
67 }
68}
69
70// removeLeaves(): remove leaves from a common parent
71// auxParentVector: i/o addreess of an auxiliary parent leaf Vector from outside this function.
72// rootLeavesVector: i/o address of the m_root->m_leavesVector
73int OctreeNode::removeLeaves(OctreeNodes& auxParentVector,
74 OctreeNodes& rootLeavesVector)
75{
76 // Apply to OctreeNode which has children which are leaf nodes
77 int result = 0;
78 for (int i=15; i>=0; i--) {
79 OctreeNode& child = (*m_children)[i];
80
81 if (child.isLeaf()) {
82 m_leafColor.add(child.leafColor());
83 result++;
84 if (rootLeavesVector[rootLeavesVector.size()-1] == &child)
85 rootLeavesVector.pop_back();
86 }
87 }
88 auxParentVector.push_back(this);
89 return result - 1;
90}
91
92// static
93int OctreeNode::getHextet(color_t c, int level)
94{
95 return ((c & (0x00000080 >> level)) ? 1 : 0) |
96 ((c & (0x00008000 >> level)) ? 2 : 0) |
97 ((c & (0x00800000 >> level)) ? 4 : 0) |
98 ((c & (0x80000000 >> level)) ? 8 : 0);
99}
100
101int OctreeNode::getHextet(int r, int g, int b, int a, int level)
102{
103 return ((r & (0x80 >> level)) ? 1 : 0) |
104 ((g & (0x80 >> level)) ? 2 : 0) |
105 ((b & (0x80 >> level)) ? 4 : 0) |
106 ((a & (0x80 >> level)) ? 8 : 0);
107}
108
109// static
110color_t OctreeNode::hextetToBranchColor(int hextet, int level)
111{
112 return ((hextet & 1) ? 0x00000080 >> level : 0) |
113 ((hextet & 2) ? 0x00008000 >> level : 0) |
114 ((hextet & 4) ? 0x00800000 >> level : 0) |
115 ((hextet & 8) ? 0x80000000 >> level : 0);
116}
117
118//////////////////////////////////////////////////////////////////////
119// OctreeMap
120
121bool OctreeMap::makePalette(Palette* palette,
122 int colorCount,
123 const int levelDeep)
124{
125 if (m_root.hasChildren()) {
126 // We create paletteIndex to get a "global like" variable, in collectLeafNodes
127 // function, the purpose is having a incremental variable in the stack memory
128 // sharend between all recursive calls of collectLeafNodes.
129 int paletteIndex = 0;
130 m_root.collectLeafNodes(m_leavesVector, paletteIndex);
131 }
132
133 if (m_maskColor != DOC_OCTREE_IS_OPAQUE)
134 colorCount--;
135
136 // If we can improve the octree accuracy, makePalette returns false, then
137 // outside from this function we must re-construct the octreeMap all again with
138 // deep level equal to 8.
139 if (levelDeep == 7 && m_leavesVector.size() < colorCount)
140 return false;
141
142
143 OctreeNodes auxLeavesVector; // auxiliary collapsed node accumulator
144 bool keepReducingMap = true;
145
146 for (int level = levelDeep; level > -1; level--) {
147 for (int i=m_leavesVector.size()-1; i>=0; i--) {
148 if (m_leavesVector.size() + auxLeavesVector.size() <= colorCount) {
149 for (int j=0; j < auxLeavesVector.size(); j++)
150 m_leavesVector.push_back(auxLeavesVector[auxLeavesVector.size() - 1 - j]);
151 keepReducingMap = false;
152 break;
153 }
154 else if (m_leavesVector.size() == 0) {
155 // When colorCount is < 16, auxLeavesVector->size() could reach the 16 size,
156 // if this is true and we don't stop the regular removeLeaves algorithm,
157 // the 16 remains colors will collapse in one.
158 // So, we have to reduce color with other method:
159 // Sort colors by pixelCount (most pixelCount on front of sortedVector),
160 // then:
161 // Blend in pairs from the least pixelCount colors.
162 if (auxLeavesVector.size() <= 16 && colorCount < 16 && colorCount > 0) {
163 // Sort colors:
164 OctreeNodes sortedVector;
165 int auxVectorSize = auxLeavesVector.size();
166 for (int k=0; k < auxVectorSize; k++) {
167 size_t maximumCount = auxLeavesVector[0]->leafColor().pixelCount();
168 int maximumIndex = 0;
169 for (int j=1; j < auxLeavesVector.size(); j++) {
170 if (auxLeavesVector[j]->leafColor().pixelCount() > maximumCount) {
171 maximumCount = auxLeavesVector[j]->leafColor().pixelCount();
172 maximumIndex = j;
173 }
174 }
175 sortedVector.push_back(auxLeavesVector[maximumIndex]);
176 auxLeavesVector.erase(auxLeavesVector.begin() + maximumIndex);
177 }
178 // End Sort colors.
179 // Blend colors:
180 for (;;) {
181 if (sortedVector.size() <= colorCount) {
182 for (int k=0; k<sortedVector.size(); k++)
183 m_leavesVector.push_back(sortedVector[k]);
184 break;
185 }
186 sortedVector[sortedVector.size()-2]->leafColor()
187 .add(sortedVector[sortedVector.size()-1]->leafColor());
188 sortedVector.pop_back();
189 }
190 // End Blend colors:
191 keepReducingMap = false;
192 break;
193 }
194 else
195 break;
196 }
197
198 m_leavesVector.back()->parent()->removeLeaves(auxLeavesVector, m_leavesVector);
199 }
200 if (keepReducingMap) {
201 // Copy collapsed leaves to m_leavesVector
202 int auxLeavesVectorSize = auxLeavesVector.size();
203 for (int i=0; i<auxLeavesVectorSize; i++)
204 m_leavesVector.push_back(auxLeavesVector[auxLeavesVector.size() - 1 - i]);
205 auxLeavesVector.clear();
206 }
207 else
208 break;
209 }
210 int leafCount = m_leavesVector.size();
211 int aux = 0;
212 if (m_maskColor == DOC_OCTREE_IS_OPAQUE)
213 palette->resize(leafCount);
214 else {
215 palette->resize(leafCount + 1);
216 palette->setEntry(0, m_maskColor);
217 aux = 1;
218 }
219
220 for (int i=0; i<leafCount; i++)
221 palette->setEntry(i+aux,
222 m_leavesVector[i]->leafColor().rgbaColor());
223
224 return true;
225}
226
227void OctreeMap::feedWithImage(const Image* image,
228 const bool withAlpha,
229 const color_t maskColor,
230 const int levelDeep)
231{
232 ASSERT(image);
233 ASSERT(image->pixelFormat() == IMAGE_RGB || image->pixelFormat() == IMAGE_GRAYSCALE);
234 color_t forceFullOpacity;
235 const bool imageIsRGBA = (image->pixelFormat() == IMAGE_RGB);
236
237 auto add_color_to_octree =
238 [this, &forceFullOpacity, levelDeep, imageIsRGBA](color_t color) {
239 const int alpha = (imageIsRGBA ? rgba_geta(color) : graya_geta(color));
240 if (alpha >= MIN_ALPHA_THRESHOLD) { // Colors which alpha is less than
241 // MIN_ALPHA_THRESHOLD will not registered
242 color |= forceFullOpacity;
243 color = (imageIsRGBA ? color : rgba(graya_getv(color),
244 graya_getv(color),
245 graya_getv(color),
246 alpha));
247 addColor(color, levelDeep);
248 }
249 };
250
251 switch (image->pixelFormat()) {
252 case IMAGE_RGB: {
253 forceFullOpacity = (withAlpha ? 0 : rgba_a_mask);
254 doc::for_each_pixel<RgbTraits>(image, add_color_to_octree);
255 break;
256 }
257 case IMAGE_GRAYSCALE: {
258 forceFullOpacity = (withAlpha ? 0 : graya_a_mask);
259 doc::for_each_pixel<GrayscaleTraits>(image, add_color_to_octree);
260 break;
261 }
262 }
263 m_maskColor = maskColor;
264}
265
266int OctreeMap::mapColor(color_t rgba) const
267{
268 return m_root.mapColor(rgba_getr(rgba),
269 rgba_getg(rgba),
270 rgba_getb(rgba),
271 rgba_geta(rgba),
272 m_maskIndex,
273 m_palette, 0);
274}
275
276void OctreeMap::regenerateMap(const Palette* palette, const int maskIndex)
277{
278 ASSERT(palette);
279 if (!palette)
280 return;
281
282 // Skip useless regenerations
283 if (m_palette == palette &&
284 m_modifications == palette->getModifications() &&
285 m_maskIndex == maskIndex)
286 return;
287
288 m_root = OctreeNode();
289 m_leavesVector.clear();
290 m_maskIndex = maskIndex;
291 int maskColorBestFitIndex;
292 if (maskIndex < 0) {
293 m_maskColor = DOC_OCTREE_IS_OPAQUE;
294 maskColorBestFitIndex = -1;
295 }
296 else {
297 m_maskColor = palette->getEntry(maskIndex);
298 maskColorBestFitIndex = palette->findBestfit(rgba_getr(m_maskColor),
299 rgba_getg(m_maskColor),
300 rgba_getb(m_maskColor),
301 rgba_geta(m_maskColor), maskIndex);
302 }
303
304 for (int i=0; i<palette->size(); i++) {
305 if (i == maskIndex) {
306 m_root.addColor(palette->entry(i), 0, &m_root, maskColorBestFitIndex, 8);
307 continue;
308 }
309 m_root.addColor(palette->entry(i), 0, &m_root, i, 8);
310 }
311
312 m_palette = palette;
313 m_modifications = palette->getModifications();
314}
315
316} // namespace doc
317