Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
quantize.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Quantization for Inkscape
4 *
5 * Authors:
6 * Stéphane Gimenez <dev@gim.name>
7 *
8 * Copyright (C) 2006 Authors
9 *
10 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
11 */
12#include <memory>
13#include <cassert>
14#include <cstdio>
15#include <glib.h>
16
17#include "pool.h"
18#include "imagemap.h"
19#include "quantize.h"
20
21namespace Inkscape {
22namespace Trace {
23
24namespace {
25
29struct Ocnode
30{
31 Ocnode *parent; // parent node
32 Ocnode **ref; // node's reference
33 Ocnode *child[8]; // children
34 int nchild; // number of children
35 int width; // width level of this node
36 RGB rgb; // rgb's prefix of that node
37 unsigned long weight; // number of pixels this node accounts for
38 unsigned long rs, gs, bs; // sum of pixels colors this node accounts for
39 int nleaf; // number of leaves under this node
40 unsigned long mi; // minimum impact
41};
42
43/*
44-- algorithm principle:
45
46- inspired by the octree method, we associate a tree to a given color map
47
48- nodes in those trees have this shape:
49
50 parent
51 |
52 color_prefix(stored in rgb):width
53 colors_sum(stored in rs,gs,bs)/weight
54 / | \
55 child1 child2 child3
56
57- (grayscale) trees associated to pixels with colors 87 = 0b1010111 and
58 69 = 0b1000101 are:
59
60 . . <-- roots of the trees
61 | |
62 1010111:0 and 1000101:0 <-- color prefixes, written in binary form
63 87/1 69/1 <-- color sums, written in decimal form
64
65- the result of merging the two trees is:
66
67 .
68 |
69 10:5 <----- longest common prefix and binary width
70 156/2 <---. of the covered color range.
71 / \ |
72 1000101:0 1010111:0 '- sum of colors and quantity of pixels
73 69/1 87/1 this node accounts for
74
75 one should consider three cases when two trees are to be merged:
76 - one tree range is included in the range of the other one, and the first
77 tree has to be inserted as a child (or merged with the corresponding
78 child) of the other.
79 - their ranges are the same, and their children have to be merged under
80 a single root.
81 - ranges have no intersection, and a fork node has to be created (like in
82 the given example).
83
84- a tree for an image is built dividing the image in 2 parts and merging
85 the trees obtained recursively for the two parts. a tree for a one pixel
86 part is a leaf like one of those which were given above.
87
88- last, this tree is reduced a specified number of leaves, deleting first
89 leaves with minimal impact i.e. [ weight * 2^(2*parentwidth) ] value :
90 a fair approximation of the impact a leaf removal would have on the final
91 result : it's the corresponding covered area times the square of the
92 introduced color distance.
93
94 deletion of a node A below a node with only two children is done as
95 follows :
96
97 - when the sibling is a leaf, the sibling is deleted as well, both nodes
98 are then represented by their parent.
99
100 | |
101 . ==> .
102 / \
103 A .
104
105 - otherwise the deletion of A deletes also its parent, which plays no
106 role anymore:
107
108 | |
109 . ==> \
110 / \ |
111 A . .
112 / \ / \
113
114 in that way, every leaf removal operation really decreases the remaining
115 total number of leaves by one.
116
117- very last, color indexes are attributed to leaves; associated colors are
118 averages, computed from weight and color components sums.
119
120-- improvements to the usual octree method:
121
122- since this algorithm shall often be used to perform quantization using a
123 very low (2-16) set of colors and not with a usual 256 value, we choose
124 more carefully which nodes are to be deleted.
125
126- depth of leaves is not fixed to an arbitrary number (which should be 8
127 when color components are in 0-255), so there is no need to go down to a
128 depth of 8 for each pixel (at full precision), unless it is really
129 required.
130
131- tree merging also fastens the overall tree building, and intermediate
132 processing could be done.
133
134- a huge optimization against the stupid removal algorithm (i.e. find a best
135 match over the whole tree, remove it and do it again) was implemented:
136 nodes are marked with the minimal impact of the removal of a leaf below
137 it. we proceed to the removal recursively. we stop when current removal
138 level is above the current node minimal, otherwise reached leaves are
139 removed, and every change over minimal impacts is propagated back to the
140 whole tree when the recursion ends.
141
142-- specific optimizations
143
144- pool allocation is used to allocate nodes (increased performance on large
145 images).
146
147*/
148
149RGB operator>>(RGB rgb, int s)
150{
151 RGB res;
152 res.r = rgb.r >> s;
153 res.g = rgb.g >> s;
154 res.b = rgb.b >> s;
155 return res;
156}
157
158bool operator==(RGB rgb1, RGB rgb2)
159{
160 return rgb1.r == rgb2.r && rgb1.g == rgb2.g && rgb1.b == rgb2.b;
161}
162
163int childIndex(RGB rgb)
164{
165 return ((rgb.r & 1) << 2) | ((rgb.g & 1) << 1) | (rgb.b & 1);
166}
167
171Ocnode *ocnodeNew(Pool<Ocnode> &pool)
172{
173 Ocnode *node = pool.draw();
174 node->ref = nullptr;
175 node->parent = nullptr;
176 node->nchild = 0;
177 for (auto &i : node->child) {
178 i = nullptr;
179 }
180 node->mi = 0;
181 return node;
182}
183
184void ocnodeFree(Pool<Ocnode> &pool, Ocnode *node)
185{
186 pool.drop(node);
187}
188
192void octreeDelete(Pool<Ocnode> &pool, Ocnode *node)
193{
194 if (!node) return;
195 for (auto &i : node->child) {
196 octreeDelete(pool, i);
197 }
198 ocnodeFree(pool, node);
199}
200
204#if 0
205void ocnodePrint(Ocnode *node, int indent)
206{
207 if (!node) return;
208 printf("width:%d weight:%lu rgb:%6x nleaf:%d mi:%lu\n",
209 node->width,
210 node->weight,
211 (unsigned int)(
212 ((node->rs / node->weight) << 16) +
213 ((node->gs / node->weight) << 8) +
214 (node->bs / node->weight)),
215 node->nleaf,
216 node->mi
217 );
218 for (int i = 0; i < 8; i++) if (node->child[i])
219 {
220 for (int k = 0; k < indent; k++) printf(" ");//indentation
221 printf("[%d:%p] ", i, node->child[i]);
222 ocnodePrint(node->child[i], indent+2);
223 }
224}
225
226void octreePrint(Ocnode *node)
227{
228 printf("<<octree>>\n");
229 if (node) printf("[r:%p] ", node); ocnodePrint(node, 2);
230}
231#endif
232
236void ocnodeLeaf(Pool<Ocnode> &pool, Ocnode **ref, RGB rgb)
237{
238 assert(ref);
239 Ocnode *node = ocnodeNew(pool);
240 node->width = 0;
241 node->rgb = rgb;
242 node->rs = rgb.r; node->gs = rgb.g; node->bs = rgb.b;
243 node->weight = 1;
244 node->nleaf = 1;
245 node->mi = 0;
246 node->ref = ref;
247 *ref = node;
248}
249
253int octreeMerge(Pool<Ocnode> &pool, Ocnode *parent, Ocnode **ref, Ocnode *node1, Ocnode *node2)
254{
255 assert(ref);
256 if (!node1 && !node2) return 0;
257 assert(node1 != node2);
258 if (parent && !*ref) parent->nchild++;
259 if (!node1) {
260 *ref = node2; node2->ref = ref; node2->parent = parent;
261 return node2->nleaf;
262 }
263 if (!node2) {
264 *ref = node1; node1->ref = ref; node1->parent = parent;
265 return node1->nleaf;
266 }
267 int dwitdth = node1->width - node2->width;
268 if (dwitdth > 0 && node1->rgb == node2->rgb >> dwitdth) {
269 // place node2 below node1
270 *ref = node1; node1->ref = ref; node1->parent = parent;
271 int i = childIndex(node2->rgb >> (dwitdth - 1));
272 node1->rs += node2->rs; node1->gs += node2->gs; node1->bs += node2->bs;
273 node1->weight += node2->weight;
274 node1->mi = 0;
275 if (node1->child[i]) node1->nleaf -= node1->child[i]->nleaf;
276 node1->nleaf += octreeMerge(pool, node1, &node1->child[i], node1->child[i], node2);
277 return node1->nleaf;
278 } else if (dwitdth < 0 && node2->rgb == node1->rgb >> (-dwitdth)) {
279 // place node1 below node2
280 *ref = node2; node2->ref = ref; node2->parent = parent;
281 int i = childIndex(node1->rgb >> (-dwitdth - 1));
282 node2->rs += node1->rs; node2->gs += node1->gs; node2->bs += node1->bs;
283 node2->weight += node1->weight;
284 node2->mi = 0;
285 if (node2->child[i]) node2->nleaf -= node2->child[i]->nleaf;
286 node2->nleaf += octreeMerge(pool, node2, &node2->child[i], node2->child[i], node1);
287 return node2->nleaf;
288 } else {
289 // nodes have either no intersection or the same root
290 Ocnode *newnode;
291 newnode = ocnodeNew(pool);
292 newnode->rs = node1->rs + node2->rs;
293 newnode->gs = node1->gs + node2->gs;
294 newnode->bs = node1->bs + node2->bs;
295 newnode->weight = node1->weight + node2->weight;
296 *ref = newnode; newnode->ref = ref; newnode->parent = parent;
297 if (dwitdth == 0 && node1->rgb == node2->rgb) {
298 // merge the nodes in <newnode>
299 newnode->width = node1->width; // == node2->width
300 newnode->rgb = node1->rgb; // == node2->rgb
301 newnode->nchild = 0;
302 newnode->nleaf = 0;
303 if (node1->nchild == 0 && node2->nchild == 0) {
304 newnode->nleaf = 1;
305 } else {
306 for (int i = 0; i < 8; i++) {
307 if (node1->child[i] || node2->child[i]) {
308 newnode->nleaf += octreeMerge(pool, newnode, &newnode->child[i], node1->child[i], node2->child[i]);
309 }
310 }
311 }
312 ocnodeFree(pool, node1); ocnodeFree(pool, node2);
313 return newnode->nleaf;
314 } else {
315 // use <newnode> as a fork node with children <node1> and <node2>
316 int newwidth = std::max(node1->width, node2->width);
317 RGB rgb1 = node1->rgb >> (newwidth - node1->width);
318 RGB rgb2 = node2->rgb >> (newwidth - node2->width);
319 // according to the previous tests <rgb1> != <rgb2> before the loop
320 while (!(rgb1 == rgb2)) {
321 rgb1 = rgb1 >> 1;
322 rgb2 = rgb2 >> 1;
323 newwidth++;
324 }
325 newnode->width = newwidth;
326 newnode->rgb = rgb1; // == rgb2
327 newnode->nchild = 2;
328 newnode->nleaf = node1->nleaf + node2->nleaf;
329 int i1 = childIndex(node1->rgb >> (newwidth - node1->width - 1));
330 int i2 = childIndex(node2->rgb >> (newwidth - node2->width - 1));
331 node1->parent = newnode;
332 node1->ref = &newnode->child[i1];
333 newnode->child[i1] = node1;
334 node2->parent = newnode;
335 node2->ref = &newnode->child[i2];
336 newnode->child[i2] = node2;
337 return newnode->nleaf;
338 }
339 }
340}
341
345void ocnodeMi(Ocnode *node)
346{
347 node->mi = node->parent ? node->weight << (2 * node->parent->width) : 0;
348}
349
355void ocnodeStrip(Pool<Ocnode> &pool, Ocnode **ref, int &count, unsigned long lvl)
356{
357 Ocnode *node = *ref;
358 if (!node) return;
359 assert(ref == node->ref);
360 if (node->nchild == 0) { // leaf node
361 if (!node->mi) ocnodeMi(node); // mi generation may be required
362 if (node->mi > lvl) return; // leaf is above strip level
363 ocnodeFree(pool, node);
364 *ref = nullptr;
365 count--;
366 } else {
367 if (node->mi && node->mi > lvl) return; // node is above strip level
368 node->nchild = 0;
369 node->nleaf = 0;
370 node->mi = 0;
371 Ocnode **lonelychild = nullptr;
372 for (auto & i : node->child) {
373 if (i) {
374 ocnodeStrip(pool, &i, count, lvl);
375 if (i) {
376 lonelychild = &i;
377 node->nchild++;
378 node->nleaf += i->nleaf;
379 if (!node->mi || node->mi > i->mi) {
380 node->mi = i->mi;
381 }
382 }
383 }
384 }
385 // tree adjustments
386 if (node->nchild == 0) {
387 count++;
388 node->nleaf = 1;
389 ocnodeMi(node);
390 } else if (node->nchild == 1) {
391 if ((*lonelychild)->nchild == 0) {
392 // remove the <lonelychild> leaf under a 1 child node
393 node->nchild = 0;
394 node->nleaf = 1;
395 ocnodeMi(node);
396 ocnodeFree(pool, *lonelychild);
397 *lonelychild = nullptr;
398 } else {
399 // make a bridge to <lonelychild> over a 1 child node
400 (*lonelychild)->parent = node->parent;
401 (*lonelychild)->ref = ref;
402 ocnodeFree(pool, node);
403 *ref = *lonelychild;
404 }
405 }
406 }
407}
408
412void octreePrune(Pool<Ocnode> &pool, Ocnode **ref, int ncolor)
413{
414 assert(ref);
415 assert(ncolor > 0);
416 int n = (*ref)->nleaf - ncolor;
417 if (!*ref || n <= 0) return;
418 while (n > 0) {
419 ocnodeStrip(pool, ref, n, (*ref)->mi);
420 }
421}
422
427void octreeBuildArea(Pool<Ocnode> &pool, RgbMap const &rgbmap, Ocnode **ref, int x1, int y1, int x2, int y2, int ncolor)
428{
429 int dx = x2 - x1, dy = y2 - y1;
430 int xm = x1 + dx / 2, ym = y1 + dy / 2;
431 Ocnode *ref1 = nullptr;
432 Ocnode *ref2 = nullptr;
433 if (dx == 1 && dy == 1) {
434 ocnodeLeaf(pool, ref, rgbmap.getPixel(x1, y1));
435 } else if (dx > dy) {
436 octreeBuildArea(pool, rgbmap, &ref1, x1, y1, xm, y2, ncolor);
437 octreeBuildArea(pool, rgbmap, &ref2, xm, y1, x2, y2, ncolor);
438 octreeMerge(pool, nullptr, ref, ref1, ref2);
439 } else {
440 octreeBuildArea(pool, rgbmap, &ref1, x1, y1, x2, ym, ncolor);
441 octreeBuildArea(pool, rgbmap, &ref2, x1, ym, x2, y2, ncolor);
442 octreeMerge(pool, nullptr, ref, ref1, ref2);
443 }
444
445 // octreePrune(ref, 2 * ncolor);
446 // affects result quality for almost same performance :/
447}
448
453Ocnode *octreeBuild(Pool<Ocnode> &pool, RgbMap const &rgbmap, int ncolor)
454{
455 // create the octree
456 Ocnode *node = nullptr;
457 octreeBuildArea(pool,
458 rgbmap, &node,
459 0, 0, rgbmap.width, rgbmap.height, ncolor);
460
461 // prune the octree
462 octreePrune(pool, &node, ncolor);
463
464 return node;
465}
466
470void octreeIndex(Ocnode *node, RGB *rgbpal, int &index)
471{
472 if (!node) return;
473 if (node->nchild == 0) {
474 rgbpal[index].r = node->rs / node->weight;
475 rgbpal[index].g = node->gs / node->weight;
476 rgbpal[index].b = node->bs / node->weight;
477 index++;
478 } else {
479 for (auto &i : node->child) {
480 if (i) {
481 octreeIndex(i, rgbpal, index);
482 }
483 }
484 }
485}
486
490int distRGB(RGB rgb1, RGB rgb2)
491{
492 return (rgb1.r - rgb2.r) * (rgb1.r - rgb2.r)
493 + (rgb1.g - rgb2.g) * (rgb1.g - rgb2.g)
494 + (rgb1.b - rgb2.b) * (rgb1.b - rgb2.b);
495}
496
500int findRGB(RGB const *rgbs, int ncolor, RGB rgb)
501{
502 int index = -1, dist = 0;
503 for (int k = 0; k < ncolor; k++) {
504 int d = distRGB(rgbs[k], rgb);
505 if (index == -1 || d < dist) { dist = d; index = k; }
506 }
507 return index;
508}
509
510} // namespace
511
515IndexedMap rgbMapQuantize(RgbMap const &rgbmap, int ncolor)
516{
517 assert(ncolor > 0);
518
519 auto imap = IndexedMap(rgbmap.width, rgbmap.height);
520
521 Pool<Ocnode> pool;
522 auto tree = octreeBuild(pool, rgbmap, ncolor);
523
524 auto rgbs = std::make_unique<RGB[]>(ncolor);
525 int index = 0;
526 octreeIndex(tree, rgbs.get(), index);
527
528 octreeDelete(pool, tree);
529
530 // stacking with increasing contrasts
531 std::sort(rgbs.get(), rgbs.get() + ncolor, [] (auto &ra, auto &rb) {
532 return (ra.r + ra.g + ra.b) < (rb.r + rb.g + rb.b);
533 });
534
535 // make the new map
536 // fill in the color lookup table
537 for (int i = 0; i < index; i++) {
538 imap.clut[i] = rgbs[i];
539 }
540 imap.nrColors = index;
541
542 // fill in new map pixels
543 for (int y = 0; y < rgbmap.height; y++) {
544 for (int x = 0; x < rgbmap.width; x++) {
545 auto rgb = rgbmap.getPixel(x, y);
546 int index = findRGB(rgbs.get(), ncolor, rgb);
547 imap.setPixel(x, y, index);
548 }
549 }
550
551 return imap;
552}
553
554} // namespace Trace
555} // namespace Inkscape
virtual Node * parent()=0
Get the parent of this node.
Definition pool.h:62
T * draw()
Definition pool.h:81
void drop(T *p)
Definition pool.h:89
vector< vpsc::Rectangle * > rs
static char const *const parent
Definition dir-util.cpp:70
Inkscape::XML::Node * node
TODO: insert short description here.
std::istream & operator>>(std::istream &is, MTRand &mtrand)
double dist(const Point &a, const Point &b)
Definition geometry.cpp:310
bool operator==(D2< T > const &a, D2< T > const &b)
Definition d2.h:177
IndexedMap rgbMapQuantize(RgbMap const &rgbmap, int ncolor)
quantize an RGB image to a reduced number of colors.
Definition quantize.cpp:515
Helper class to stream background task notifications as a series of messages.
int n
Definition spiro.cpp:66
unsigned long gs
Definition quantize.cpp:38
unsigned long weight
Definition quantize.cpp:37
unsigned long mi
Definition quantize.cpp:40
Ocnode * child[8]
Definition quantize.cpp:33
RGB rgb
Definition quantize.cpp:36
Ocnode * parent
Definition quantize.cpp:31
Ocnode ** ref
Definition quantize.cpp:32
unsigned long bs
Definition quantize.cpp:38
int nchild
Definition quantize.cpp:34
int nleaf
Definition quantize.cpp:39
SpatialIndex::ISpatialIndex * tree
T getPixel(int x, int y) const
Definition imagemap.h:35
unsigned char g
Definition imagemap.h:60
unsigned char b
Definition imagemap.h:61
unsigned char r
Definition imagemap.h:59
int index
double width