From: buliabyak Date: Mon, 20 Nov 2006 05:26:29 +0000 (+0000) Subject: patch 1598684: better trace quantization X-Git-Url: https://git.tokkee.org/?a=commitdiff_plain;h=e8767bd3cabb360b1307698236a7778044950900;p=inkscape.git patch 1598684: better trace quantization --- diff --git a/src/trace/Makefile_insert b/src/trace/Makefile_insert index 2c8edcb15..5ef59b3c3 100644 --- a/src/trace/Makefile_insert +++ b/src/trace/Makefile_insert @@ -6,13 +6,16 @@ trace/clean: rm -f trace/libtrace.a $(trace_libtrace_a_OBJECTS) trace_libtrace_a_SOURCES = \ + trace/pool.h \ trace/trace.h \ trace/trace.cpp \ trace/imagemap-gdk.cpp \ trace/imagemap-gdk.h \ trace/imagemap.cpp \ trace/imagemap.h \ - trace/filterset.h \ + trace/quantize.h \ + trace/quantize.cpp \ + trace/filterset.h \ trace/filterset.cpp \ trace/siox.h \ trace/siox.cpp \ diff --git a/src/trace/filterset.cpp b/src/trace/filterset.cpp index 2dace61ce..e82ad4ed9 100644 --- a/src/trace/filterset.cpp +++ b/src/trace/filterset.cpp @@ -3,8 +3,9 @@ * * Authors: * Bob Jamison + * Stéphane Gimenez * - * Copyright (C) 2004 Bob Jamison + * Copyright (C) 2004-2006 Authors * * Released under GNU GPL, read the file 'COPYING' for more information */ @@ -14,7 +15,7 @@ #include "imagemap-gdk.h" #include "filterset.h" - +#include "quantize.h" /*######################################################################### ### G A U S S I A N (smoothing) @@ -405,500 +406,9 @@ gdkCanny(GdkPixbuf *img, double lowThreshold, double highThreshold) return newImg; } - - - /*######################################################################### ### Q U A N T I Z A T I O N #########################################################################*/ -typedef struct OctreeNode_def OctreeNode; - -/** - * The basic octree node - */ -struct OctreeNode_def -{ - unsigned long r; - unsigned long g; - unsigned long b; - unsigned int index; - unsigned long nrPixels; - unsigned int nrChildren; - OctreeNode *parent; - OctreeNode *children[8]; -}; - - -/** - * create an octree node, and initialize it - */ -OctreeNode *octreeNodeCreate() -{ - OctreeNode *node = (OctreeNode *)malloc(sizeof(OctreeNode)); - if (!node) - return NULL; - node->r = 0; - node->g = 0; - node->b = 0; - node->index = 0; - node->nrPixels = 0; - node->nrChildren = 0; - node->parent = NULL; - for (int i=0 ; i<8 ; i++) - node->children[i] = NULL; - return node; -} - -/** - * delete an octree node and its children - */ -void octreeNodeDelete(OctreeNode *node) -{ - if (!node) - return; - for (int i=0 ; i<8 ; i++) - octreeNodeDelete(node->children[i]); - free(node); -} - - -/** - * delete the children of an octree node - */ -void octreeNodeDeleteChildren(OctreeNode *node) -{ - if (!node) - return; - node->nrChildren = 0; - for (int i=0 ; i<8 ; i++) - { - octreeNodeDelete(node->children[i]); - node->children[i]=NULL; - } -} - - - - -/** - * insert an RGB value into an octree node according to its - * high-order rgb vector bits - */ -int octreeNodeInsert(OctreeNode *root, RGB rgb, int bitsPerSample) -{ - OctreeNode *node = root; - int newColor = FALSE; - int r = rgb.r; - int g = rgb.g; - int b = rgb.b; - - int shift = 7; - for (int bit=0 ; bitr += r; - node->g += g; - node->b += b; - node->nrPixels++; - int index = (((r >> shift) & 1) << 2) | - (((g >> shift) & 1) << 1) | - (((b >> shift) & 1) ) ; - - OctreeNode *child = node->children[index]; - if (!child) - { - child = octreeNodeCreate(); - node->children[index] = child; - child->parent = node; - node->nrChildren++; - newColor = TRUE; - } - node = child; /*next level*/ - shift--; - } - return newColor; -} - - - - - -/** - * find an exact match for an RGB value, at the given bits - * per sample. if not found, return -1 - */ -int octreeNodeFind(OctreeNode *root, RGB rgb, int bitsPerSample) -{ - OctreeNode *node = root; - int r = rgb.r; - int g = rgb.g; - int b = rgb.b; - - int shift = 7; - for (int bit=0 ; bit> shift) & 1) << 2) | - (((g >> shift) & 1) << 1) | - (((b >> shift) & 1) ) ; - - OctreeNode *child = node->children[index]; - if (!child) - return -1; - node = child; /*next level*/ - shift--; - } - printf("error. this should not happen\n"); - return -1; -} - - - -static void spaces(int nr) -{ - for (int i=0; ir); - spaces(indent); printf("g :%lu\n", node->g); - spaces(indent); printf("b :%lu\n", node->b); - spaces(indent); printf("i :%d\n", node->index); - for (unsigned int i=0; i<8; i++) - { - OctreeNode *child = node->children[i]; - if (!child) - continue; - spaces(indent); printf(" child %d :", i); - octreeNodePrint(child, indent+4); - } -} - -/** - * Count how many nodes in this (sub)tree - */ -static int octreeNodeCount(OctreeNode *node) -{ - int count = 1; - for (unsigned int i=0; i<8; i++) - { - OctreeNode *child = node->children[i]; - if (!child) - continue; - count += octreeNodeCount(child); - } - return count; -} - - - - -/** - * Count all of the leaf nodes in the octree - */ -static void octreeLeafArray(OctreeNode *node, OctreeNode **array, int arraySize, int *len) -{ - if (!node) - return; - if (node->nrChildren == 0 && *len < arraySize) - { - array[*len] = node; - *len = *len + 1; - } - for (int i=0 ; i<8 ; i++) - octreeLeafArray(node->children[i], array, arraySize, len); -} - - - -/** - * Count all of the leaf nodes in the octree - */ -static int octreeLeafCount(OctreeNode *node) -{ - if (!node) - return 0; - if (node->nrChildren == 0) - return 1; - int leaves = 0; - for (int i=0 ; i<8 ; i++) - leaves += octreeLeafCount(node->children[i]); - return leaves; -} - -/** - * Mark all of the leaf nodes in the octree with an index nr - */ -static void octreeLeafIndex(OctreeNode *node, int *index) -{ - if (!node) - return; - if (node->nrChildren == 0) - { - node->index = *index; - *index = *index + 1; - return; - } - for (int i=0 ; i<8 ; i++) - octreeLeafIndex(node->children[i], index); -} - - - -/** - * Find a node that has children, and that also - * has the lowest pixel count - */ -static void octreefindLowestLeaf(OctreeNode *node, OctreeNode **lowestLeaf) -{ - if (!node) - return; - if (node->nrChildren == 0) - { - if (node->nrPixels < (*lowestLeaf)->nrPixels) - *lowestLeaf = node; - return; - } - - for (int i=0 ; i<8 ; i++) - octreefindLowestLeaf(node->children[i], lowestLeaf); -} - - -/** - * reduce the leaves on an octree to a given number - */ -int octreePrune(OctreeNode *root, int nrColors) -{ - int leafCount = octreeLeafCount(root); - - while (leafCount > nrColors) - { - OctreeNode *lowestLeaf = root; - octreefindLowestLeaf(root, &lowestLeaf); - - if (!lowestLeaf) - break; //should never happen - - if (lowestLeaf==root) - { - printf("found no leaves\n"); - continue; - } - - OctreeNode *parent = lowestLeaf->parent; - if (!parent) - continue; - - for (int i=0 ; i<8 ; i++) - { - OctreeNode *child = parent->children[i]; - if (child == lowestLeaf) - { - parent->nrChildren--; - octreeNodeDelete(child); - parent->children[i] = NULL; - break; - } - } - /*printf("leafCount:%d lowPixels:%lu\n", - leafCount, lowestLeaf->nrPixels);*/ - leafCount = octreeLeafCount(root); - } - int index = 0; - octreeLeafIndex(root, &index); - - //printf("leafCount:%d\n", leafCount); - //octreeNodePrint(root, 0); - - return leafCount; -} - - - -/** - * - */ -OctreeNode *octreeBuild(RgbMap *rgbMap, int bitsPerSample, int nrColors) -{ - OctreeNode *root = octreeNodeCreate(); - if (!root) - return NULL; - for (int y=0 ; yheight ; y++) - { - for (int x=0 ; xwidth ; x++) - { - RGB rgb = rgbMap->getPixel(rgbMap, x, y); - octreeNodeInsert(root, rgb, bitsPerSample); - } - } - - if (octreePrune(root, nrColors)<0) - { - octreeNodeDelete(root); - return NULL; - } - - /* octreeNodePrint(root, 0); */ - - return root; -} - - -/* Compare two rgb's for brightness, for the qsort() below */ -static int compRGB(const void *a, const void *b) -{ - RGB *ra = (RGB *)a; - RGB *rb = (RGB *)b; - int ba = ra->r + ra->g + ra->b; - int bb = rb->r + rb->g + rb->b; - int comp = ba - bb; - return comp; -} - -/** - * - */ -RGB *makeRGBPalette(OctreeNode *root, int nrColors) -{ - - OctreeNode **palette = (OctreeNode **)malloc(nrColors * sizeof(OctreeNode *)); - if (!palette) - { - return NULL; - } - int len = 0; - octreeLeafArray(root, palette, nrColors, &len); - - RGB *rgbpal = (RGB *)malloc(len * sizeof(RGB)); - if (!rgbpal) - { - free(palette); - return NULL; - } - - for (int i=0; inrPixels == 0) - { - continue; - } - rgb.r = (unsigned char)( (node->r / node->nrPixels) & 0xff); - rgb.g = (unsigned char)( (node->g / node->nrPixels) & 0xff); - rgb.b = (unsigned char)( (node->b / node->nrPixels) & 0xff); - int index = node->index; - //printf("Pal: %d %d %d %d\n", rgb.r, rgb.g, rgb.b, index); - rgbpal[index]=rgb; - } - - free(palette); - - /* sort by brightness, to avoid high contrasts */ - qsort((void *)rgbpal, len, sizeof(RGB), compRGB); - - return rgbpal; -} - - -/** - * Return the closest color in the palette to the request - */ -RGB lookupQuantizedRGB(RGB *rgbpalette, int paletteSize, RGB candidate, int *closestIndex) -{ - /* slow method */ - unsigned long closestMatch = 10000000; - RGB closestRGB = { 0 , 0, 0 }; - *closestIndex = 0; - for (int i=0 ; iwidth, rgbMap->height); - if (!newMap) - { - free(rgbpal); - octreeNodeDelete(otree); - return NULL; - } - - /* fill in the color lookup table */ - for (int i=0 ; i< nrColors ; i++) - { - newMap->clut[i] = rgbpal[i]; - } - newMap->nrColors = nrColors; - - for (int y=0 ; yheight ; y++) - { - for (int x=0 ; xwidth ; x++) - { - RGB rgb = rgbMap->getPixel(rgbMap, x, y); - //int indexNr = octreeNodeFind(otree, rgb, bitsPerSample); - //printf("i:%d\n", indexNr); - //RGB quantRgb = rgbpal[indexNr]; - int closestIndex; - RGB quantRgb = lookupQuantizedRGB(rgbpal, nrColors, rgb, &closestIndex); - newMap->setPixel(newMap, x, y, (unsigned int)closestIndex); - } - } - - free(rgbpal); - octreeNodeDelete(otree); - - return newMap; -} - - /** * Experimental. Work on this later @@ -906,18 +416,16 @@ IndexedMap *rgbMapQuantize(RgbMap *rgbMap, int bitsPerSample, int nrColors) GrayMap *quantizeBand(RgbMap *rgbMap, int nrColors) { - int bitsPerSample = 4; - RgbMap *gaussMap = rgbMapGaussian(rgbMap); //gaussMap->writePPM(gaussMap, "rgbgauss.ppm"); - IndexedMap *qMap = rgbMapQuantize(gaussMap, bitsPerSample, nrColors); + IndexedMap *qMap = rgbMapQuantize(gaussMap, nrColors); //qMap->writePPM(qMap, "rgbquant.ppm"); gaussMap->destroy(gaussMap); GrayMap *gm = GrayMapCreate(rgbMap->width, rgbMap->height); - /* RGB is quantized. There should now be a small set of (R+G+B) */ + // RGB is quantized. There should now be a small set of (R+G+B) for (int y=0 ; yheight ; y++) { for (int x=0 ; xwidth ; x++) @@ -928,7 +436,7 @@ GrayMap *quantizeBand(RgbMap *rgbMap, int nrColors) sum = 765; else sum = 0; - /*printf("%d %d %d : %d\n", rgb.r, rgb.g, rgb.b, index);*/ + // printf("%d %d %d : %d\n", rgb.r, rgb.g, rgb.b, index); gm->setPixel(gm, x, y, sum); } } @@ -939,12 +447,6 @@ GrayMap *quantizeBand(RgbMap *rgbMap, int nrColors) } - - - - - - /*######################################################################### ### E N D O F F I L E #########################################################################*/ diff --git a/src/trace/filterset.h b/src/trace/filterset.h index 5e73847bd..eeafc079f 100644 --- a/src/trace/filterset.h +++ b/src/trace/filterset.h @@ -3,8 +3,9 @@ * * Authors: * Bob Jamison + * Stéphane Gimenez * - * Copyright (C) 2004 Bob Jamison + * Copyright (C) 2004-2006 Authors * * Released under GNU GPL, read the file 'COPYING' for more information */ @@ -16,39 +17,24 @@ #include -#ifndef TRUE -#define TRUE 1 -#endif - -#ifndef FALSE -#define FALSE 0 -#endif - -/*######################################################################### -### C A N N Y E D G E D E T E C T I O N -#########################################################################*/ - - - #ifdef __cplusplus extern "C" { #endif - /** - * + * Apply gaussian blur to an GrayMap */ -GrayMap *grayMapGaussian(GrayMap *me); +GrayMap *grayMapGaussian(GrayMap *gmap); /** - * + * Apply gaussian bluf to an RgbMap */ -RgbMap *rgbMapGaussian(RgbMap *me); +RgbMap *rgbMapGaussian(RgbMap *rgbmap); /** * */ -GrayMap *grayMapCanny(GrayMap *gm, +GrayMap *grayMapCanny(GrayMap *gmap, double lowThreshold, double highThreshold); /** @@ -57,12 +43,6 @@ GrayMap *grayMapCanny(GrayMap *gm, GdkPixbuf *gdkCanny(GdkPixbuf *img, double lowThreshold, double highThreshold); -/** - * Quantize an RGB image to a reduced number of colors. bitsPerSample - * is usually 3 - 5 out of 8 to conserve cpu and memory - */ -IndexedMap *rgbMapQuantize(RgbMap *rgbMap, int bitsPerSample, int nrColors); - /** * */ diff --git a/src/trace/pool.h b/src/trace/pool.h new file mode 100644 index 000000000..3f722f538 --- /dev/null +++ b/src/trace/pool.h @@ -0,0 +1,114 @@ +/* + * Pool memory allocation + * + * Authors: + * Stéphane Gimenez + * + * Copyright (C) 2004-2006 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +// not thread safe (a pool cannot be shared by threads safely) + +/* +-- principle: + + - user operations on a pool of objects of type T are: + - T *draw() : obtain a unused slot to store an object T + - void drop(T *) : realease a slot + +-- implementation: + + - a pool for objects T is: + + * blocks[64] : an array of allocated blocks of memory: + |---0--> block with capacity 64 + |---1--> block with capacity 64 + |---2--> block with capacity 128 + |---3--> block with capacity 128 + |---4--> block with capacity 256 + |---5--> block with capacity 256 + |---6--> block with capacity 512 + |---7--> not yet allocated + : + |---k--> not yet allocated (future capacity ~ 2^(6+k/2)) + : + '--63--> not yet allocated + * cblock : the index of the next unallocated block (here 7). + * next : a pointer to an unused slot inside an allocated bloc + + - the first bytes of an unallocated slot inside a bloc are used to store a + pointer to some other unallocated slot. (this way, we keep a list of all + unused slots starting at ) + + - insertions and deletions in this list are done at the root . + if points to NULL (no slots are availlable) when a draw() + operation is performed a new block is allocated, and the unused slots + list is filled with the allocated slots. + + - memory is freed only at pool's deletion. + +*/ + +#include + +template +class pool { + + public: + + pool() + { + cblock = 0; + size = sizeof(T) > sizeof(void *) ? sizeof(T) : sizeof(void *); + next = NULL; + } + + ~pool() + { + for (int k = 0; k < cblock; k++) + free(block[k]); + } + + T *draw() + { + if (!next) addblock(); + void *p = next; + next = *(void **)p; + return (T *) p; + } + + void drop(T *p) + { + *(void **)p = next; + next = (void *) p; + } + + private: + + int size; + int cblock; + void *block[64]; //enough to store unlimited number of objects + void *next; + + void addblock() + { + int i = cblock++; + int blocksize = 1 << (6 + (i/2)); + //printf("pool allocating block: %d (size:%d)...", i, blocksize);//debug + block[i] = (void *)malloc(blocksize * size); + if (!block[i]) throw std::bad_alloc(); + void *p = block[i]; + for (int k = 0; k < blocksize - 1; k++) + { + *(void**)p = (void *)((int)p + size); + p = (void *)((int)p + size); + } + *(void **)p = next; + next = block[i]; + //printf("done\n");//debug + } + +}; + diff --git a/src/trace/potrace/inkscape-potrace.cpp b/src/trace/potrace/inkscape-potrace.cpp index d1aa357fc..c27309eb9 100644 --- a/src/trace/potrace/inkscape-potrace.cpp +++ b/src/trace/potrace/inkscape-potrace.cpp @@ -20,6 +20,7 @@ #include #include "trace/filterset.h" +#include "trace/quantize.h" #include "trace/imagemap-gdk.h" #include @@ -284,39 +285,21 @@ filterIndexed(PotraceTracingEngine &engine, GdkPixbuf * pixbuf) IndexedMap *newGm = NULL; - /*### Color quant multiscan ###*/ - if (engine.getTraceType() == TRACE_QUANT_COLOR) + RgbMap *gm = gdkPixbufToRgbMap(pixbuf); + if (engine.getMultiScanSmooth()) { - RgbMap *gm = gdkPixbufToRgbMap(pixbuf); - if (engine.getMultiScanSmooth()) - { - RgbMap *gaussMap = rgbMapGaussian(gm); - newGm = rgbMapQuantize(gaussMap, (int)log2(engine.getMultiScanNrColors())+2, engine.getMultiScanNrColors()); - gaussMap->destroy(gaussMap); - } - else - { - newGm = rgbMapQuantize(gm, (int)log2(engine.getMultiScanNrColors())+2, engine.getMultiScanNrColors()); - } - gm->destroy(gm); - } - - /*### Quant multiscan ###*/ - else if (engine.getTraceType() == TRACE_QUANT_MONO) + RgbMap *gaussMap = rgbMapGaussian(gm); + newGm = rgbMapQuantize(gaussMap, engine.getMultiScanNrColors()); + gaussMap->destroy(gaussMap); + } + else { - RgbMap *gm = gdkPixbufToRgbMap(pixbuf); - if (engine.getMultiScanSmooth()) - { - RgbMap *gaussMap = rgbMapGaussian(gm); - newGm = rgbMapQuantize(gaussMap, (int)log2(engine.getMultiScanNrColors())+2, engine.getMultiScanNrColors()); - gaussMap->destroy(gaussMap); - } - else - { - newGm = rgbMapQuantize(gm, (int)log2(engine.getMultiScanNrColors())+2, engine.getMultiScanNrColors()); - } - gm->destroy(gm); + newGm = rgbMapQuantize(gm, engine.getMultiScanNrColors()); + } + gm->destroy(gm); + if (engine.getTraceType() == TRACE_QUANT_MONO) + { //Turn to grays for (int i=0 ; inrColors ; i++) { @@ -571,14 +554,9 @@ PotraceTracingEngine::traceQuant(GdkPixbuf * thePixbuf) { int indx = (int) iMap->getPixel(iMap, col, row); if (indx == colorIndex) - { gm->setPixel(gm, col, row, GRAYMAP_BLACK); //black - } - else - { - if (!multiScanStack) - gm->setPixel(gm, col, row, GRAYMAP_WHITE);//white - } + else if (!multiScanStack) + gm->setPixel(gm, col, row, GRAYMAP_WHITE); //white } } diff --git a/src/trace/quantize.cpp b/src/trace/quantize.cpp new file mode 100644 index 000000000..8acfe0c34 --- /dev/null +++ b/src/trace/quantize.cpp @@ -0,0 +1,588 @@ +/* + * Quantization for Inkscape + * + * Authors: + * Stéphane Gimenez + * + * Copyright (C) 2006 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include +#include +#include + +#include "pool.h" +#include "imagemap.h" + +typedef struct Ocnode_def Ocnode; + +/** + * an octree node datastructure + */ +struct Ocnode_def +{ + Ocnode *parent; // parent node + Ocnode **ref; // node's reference + Ocnode *child[8]; // children + int nchild; // number of children + int width; // width level of this node + RGB rgb; // rgb's prefix of that node + unsigned long weight; // number of pixels this node accounts for + unsigned long rs, gs, bs; // sum of pixels colors this node accounts for + int nleaf; // number of leaves under this node + unsigned long mi; // minimum impact +}; + +/* +-- algorithm principle: + +- inspired by the octree method, we associate a tree to a given color map + +- nodes in those trees have this shape: + + parent + | + color_prefix(stored in rgb):width + colors_sum(stored in rs,gs,bs)/weight + / | \ + child1 child2 child3 + +- (grayscale) trees associated to pixels with colors 87 = 0b1010111 and + 69 = 0b1000101 are: + + . . <-- roots of the trees + | | + 1010111:0 and 1000101:0 <-- color prefixes, written in binary form + 87/1 69/1 <-- color sums, written in decimal form + +- the result of merging the two trees is: + + . + | + 10:5 <----- longest common prefix and binary width + 156/2 <---. of the covered color range. + / \ | + 1000101:0 1010111:0 '- sum of colors and quantity of pixels + 69/1 87/1 this node accounts for + + one should consider three cases when two trees are to be merged: + - one tree range is included in the range of the other one, and the first + tree has to be inserted as a child (or merged with the corresponding + child) of the other. + - their ranges are the same, and their children have to be merged under + a single root. + - ranges have no intersection, and a fork node has to be created (like in + the given example). + +- a tree for an image is built dividing the image in 2 parts and merging + the trees obtained recursively for the two parts. a tree for a one pixel + part is a leaf like one of those which were given above. + +- last, this tree is reduced a specified number of leaves, deleting first + leaves with minimal impact i.e. [ weight * 2^(2*parentwidth) ] value : + a fair approximation of the impact a leaf removal would have on the final + result : it's the corresponding covered area times the square of the + introduced color distance. + + deletion of a node A below a node with only two children is done as + follows : + + - when the brother is a leaf, the brother is deleted as well, both nodes + are then represented by their father. + + | | + . ==> . + / \ + A . + + - otherwise the deletion of A deletes also his father, which plays no + role anymore: + + | | + . ==> \ + / \ | + A . . + / \ / \ + + in that way, every leaf removal operation really decreases the remaining + total number of leaves by one. + +- very last, color indexes are attributed to leaves; associated colors are + averages, computed from weight and color components sums. + +-- improvements to the usual octree method: + +- since this algorithm shall often be used to perform quantization using a + very low (2-16) set of colors and not with a usual 256 value, we choose + more carefully which nodes are to be deleted. + +- depth of leaves is not fixed to an arbitrary number (which should be 8 + when color components are in 0-255), so there is no need to go down to a + depth of 8 for each pixel (at full precision), unless it is really + required. + +- tree merging also fastens the overall tree building, and intermediate + processing could be done. + +- a huge optimization against the stupid removal algorithm (i.e. find a best + match over the whole tree, remove it and do it again) was implemented: + nodes are marked with the minimal impact of the removal of a leaf below + it. we proceed to the removal recursively. we stop when current removal + level is above the current node minimal, otherwise reached leaves are + removed, and every change over minimal impacts is propagated back to the + whole tree when the recursion ends. + +-- specific optimizations + +- pool allocation is used to allocate nodes (increased performance on large + images). + +*/ + +inline RGB operator>>(RGB rgb, int s) +{ + RGB res; + res.r = rgb.r >> s; res.g = rgb.g >> s; res.b = rgb.b >> s; + return res; +} +inline bool operator==(RGB rgb1, RGB rgb2) +{ + return (rgb1.r == rgb2.r && rgb1.g == rgb2.g && rgb1.b == rgb2.b); +} + +inline int childIndex(RGB rgb) +{ + return (((rgb.r)&1)<<2) | (((rgb.g)&1)<<1) | (((rgb.b)&1)); +} + +/** + * allocate a new node + */ +inline Ocnode *ocnodeNew(pool *pool) +{ + Ocnode *node = pool->draw(); + node->ref = NULL; + node->parent = NULL; + node->nchild = 0; + for (int i = 0; i < 8; i++) node->child[i] = NULL; + node->mi = 0; + return node; +} + +inline void ocnodeFree(pool *pool, Ocnode *node) { + pool->drop(node); +} + + +/** + * free a full octree + */ +static void octreeDelete(pool *pool, Ocnode *node) +{ + if (!node) return; + for (int i = 0; i < 8; i++) + octreeDelete(pool, node->child[i]); + ocnodeFree(pool, node); +} + +/** + * pretty-print an octree, debugging purposes + */ +static void ocnodePrint(Ocnode *node, int indent) +{ + if (!node) return; + printf("width:%d weight:%lu rgb:%6x nleaf:%d mi:%lu\n", + node->width, + node->weight, + (unsigned int)( + ((node->rs / node->weight) << 16) + + ((node->gs / node->weight) << 8) + + (node->bs / node->weight)), + node->nleaf, + node->mi + ); + for (int i = 0; i < 8; i++) if (node->child[i]) + { + for (int k = 0; k < indent; k++) printf(" ");//indentation + printf("[%d:%p] ", i, node->child[i]); + ocnodePrint(node->child[i], indent+2); + } +} +void octreePrint(Ocnode *node) +{ + printf("<>\n"); + if (node) printf("[r:%p] ", node); ocnodePrint(node, 2); +} + +/** + * builds a single color leaf at location + */ +void ocnodeLeaf(pool *pool, Ocnode **ref, RGB rgb) +{ + assert(ref); + Ocnode *node = ocnodeNew(pool); + node->width = 0; + node->rgb = rgb; + node->rs = rgb.r; node->gs = rgb.g; node->bs = rgb.b; + node->weight = 1; + node->nleaf = 1; + node->mi = 0; + node->ref = ref; + *ref = node; +} + +/** + * merge nodes and at location with parent + */ +int octreeMerge(pool *pool, Ocnode *parent, Ocnode **ref, Ocnode *node1, Ocnode *node2) +{ + assert(ref); + if (!node1 && !node2) return 0; + assert(node1 != node2); + if (parent && !*ref) parent->nchild++; + if (!node1) + { + *ref = node2; node2->ref = ref; node2->parent = parent; + return node2->nleaf; + } + if (!node2) + { + *ref = node1; node1->ref = ref; node1->parent = parent; + return node1->nleaf; + } + int dwitdth = node1->width - node2->width; + if (dwitdth > 0 && node1->rgb == node2->rgb >> dwitdth) + { + //place node2 below node1 + { *ref = node1; node1->ref = ref; node1->parent = parent; } + int i = childIndex(node2->rgb >> (dwitdth - 1)); + node1->rs += node2->rs; node1->gs += node2->gs; node1->bs += node2->bs; + node1->weight += node2->weight; + node1->mi = 0; + if (node1->child[i]) node1->nleaf -= node1->child[i]->nleaf; + node1->nleaf += + octreeMerge(pool, node1, &node1->child[i], node1->child[i], node2); + return node1->nleaf; + } + else if (dwitdth < 0 && node2->rgb == node1->rgb >> (-dwitdth)) + { + //place node1 below node2 + { *ref = node2; node2->ref = ref; node2->parent = parent; } + int i = childIndex(node1->rgb >> (-dwitdth - 1)); + node2->rs += node1->rs; node2->gs += node1->gs; node2->bs += node1->bs; + node2->weight += node1->weight; + node2->mi = 0; + if (node2->child[i]) node2->nleaf -= node2->child[i]->nleaf; + node2->nleaf += + octreeMerge(pool, node2, &node2->child[i], node2->child[i], node1); + return node2->nleaf; + } + else + { + //nodes have either no intersection or the same root + Ocnode *newnode; + newnode = ocnodeNew(pool); + newnode->rs = node1->rs + node2->rs; + newnode->gs = node1->gs + node2->gs; + newnode->bs = node1->bs + node2->bs; + newnode->weight = node1->weight + node2->weight; + { *ref = newnode; newnode->ref = ref; newnode->parent = parent; } + if (dwitdth == 0 && node1->rgb == node2->rgb) + { + //merge the nodes in + newnode->width = node1->width; // == node2->width + newnode->rgb = node1->rgb; // == node2->rgb + newnode->nchild = 0; + newnode->nleaf = 0; + if (node1->nchild == 0 && node2->nchild == 0) + newnode->nleaf = 1; + else + for (int i = 0; i < 8; i++) + if (node1->child[i] || node2->child[i]) + newnode->nleaf += + octreeMerge(pool, newnode, &newnode->child[i], + node1->child[i], node2->child[i]); + ocnodeFree(pool, node1); ocnodeFree(pool, node2); + return newnode->nleaf; + } + else + { + //use as a fork node with children and + int newwidth = + node1->width > node2->width ? node1->width : node2->width; + RGB rgb1 = node1->rgb >> (newwidth - node1->width); + RGB rgb2 = node2->rgb >> (newwidth - node2->width); + //according to the previous tests != before the loop + while (!(rgb1 == rgb2)) + { rgb1 = rgb1 >> 1; rgb2 = rgb2 >> 1; newwidth++; }; + newnode->width = newwidth; + newnode->rgb = rgb1; // == rgb2 + newnode->nchild = 2; + newnode->nleaf = node1->nleaf + node2->nleaf; + int i1 = childIndex(node1->rgb >> (newwidth - node1->width - 1)); + int i2 = childIndex(node2->rgb >> (newwidth - node2->width - 1)); + node1->parent = newnode; + node1->ref = &newnode->child[i1]; + newnode->child[i1] = node1; + node2->parent = newnode; + node2->ref = &newnode->child[i2]; + newnode->child[i2] = node2; + return newnode->nleaf; + } + } +} + +/** + * upatade mi value for leaves + */ +static inline void ocnodeMi(Ocnode *node) +{ + node->mi = node->parent ? + node->weight << (2 * node->parent->width) : 0; +} + +/** + * remove leaves whose prune impact value is lower than . at most + * leaves are removed, and is decreased on each removal. + * all parameters including minimal impact values are regenerated. + */ +static void ocnodeStrip(pool *pool, Ocnode **ref, int *count, unsigned long lvl) +{ + Ocnode *node = *ref; + if (!count || !node) return; + assert(ref == node->ref); + if (node->nchild == 0) // leaf node + { + if (!node->mi) ocnodeMi(node); //mi generation may be required + if (node->mi > lvl) return; //leaf is above strip level + ocnodeFree(pool, node); + *ref = NULL; + (*count)--; + } + else + { + if (node->mi && node->mi > lvl) //node is above strip level + return; + node->nchild = 0; + node->nleaf = 0; + node->mi = 0; + Ocnode **lonelychild = NULL; + for (int i = 0; i < 8; i++) if (node->child[i]) + { + ocnodeStrip(pool, &node->child[i], count, lvl); + if (node->child[i]) + { + lonelychild = &node->child[i]; + node->nchild++; + node->nleaf += node->child[i]->nleaf; + if (!node->mi || node->mi > node->child[i]->mi) + node->mi = node->child[i]->mi; + } + } + // tree adjustments + if (node->nchild == 0) + { + (*count)++; + node->nleaf = 1; + ocnodeMi(node); + } + else if (node->nchild == 1) + { + if ((*lonelychild)->nchild == 0) + { + //remove the leaf under a 1 child node + node->nchild = 0; + node->nleaf = 1; + ocnodeMi(node); + ocnodeFree(pool, *lonelychild); + *lonelychild = NULL; + } + else + { + //make a bridge to over a 1 child node + (*lonelychild)->parent = node->parent; + (*lonelychild)->ref = ref; + ocnodeFree(pool, node); + *ref = *lonelychild; + } + } + } +} + +/** + * reduce the leaves of an octree to a given number + */ +void octreePrune(pool *pool, Ocnode **ref, int ncolor) +{ + assert(ref); + assert(ncolor > 0); + //printf("pruning down to %d colors:\n", ncolor);//debug + int n = (*ref)->nleaf - ncolor; + if (!*ref || n <= 0) return; + while (n > 0) + { + //printf("removals to go: %10d\t", n);//debug + //printf("current prune impact: %10lu\n", (*ref)->mi);//debug + //calling strip with global minimum impact of the tree + ocnodeStrip(pool, ref, &n, (*ref)->mi); + } +} + +/** + * build an octree associated to the area of a color map , + * included in the specified (x1,y1)--(x2,y2) rectangle. + */ +void octreeBuildArea(pool *pool, RgbMap *rgbmap, Ocnode **ref, + int x1, int y1, int x2, int y2, int ncolor) +{ + int dx = x2 - x1, dy = y2 - y1; + int xm = x1 + dx/2, ym = y1 + dy/2; + Ocnode *ref1 = NULL; + Ocnode *ref2 = NULL; + if (dx == 1 && dy == 1) + ocnodeLeaf(pool, ref, rgbmap->getPixel(rgbmap, x1, y1)); + else if (dx > dy) + { + octreeBuildArea(pool, rgbmap, &ref1, x1, y1, xm, y2, ncolor); + octreeBuildArea(pool, rgbmap, &ref2, xm, y1, x2, y2, ncolor); + octreeMerge(pool, NULL, ref, ref1, ref2); + } + else + { + octreeBuildArea(pool, rgbmap, &ref1, x1, y1, x2, ym, ncolor); + octreeBuildArea(pool, rgbmap, &ref2, x1, ym, x2, y2, ncolor); + octreeMerge(pool, NULL, ref, ref1, ref2); + } + + //octreePrune(ref, 2*ncolor); + //affects result quality for almost same performance :/ +} + +/** + * build an octree associated to the color map, + * pruned to colors. + */ +Ocnode *octreeBuild(pool *pool, RgbMap *rgbmap, int ncolor) +{ + //create the octree + Ocnode *node = NULL; + octreeBuildArea(pool, + rgbmap, &node, + 0, 0, rgbmap->width, rgbmap->height, ncolor + ); + + //prune the octree + octreePrune(pool, &node, ncolor); + + //octreePrint(node);//debug + + return node; +} + +/** + * compute the color palette associated to an octree. + */ +static void octreeIndex(Ocnode *node, RGB *rgbpal, int *index) +{ + if (!node) return; + if (node->nchild == 0) + { + rgbpal[*index].r = node->rs / node->weight; + rgbpal[*index].g = node->gs / node->weight; + rgbpal[*index].b = node->bs / node->weight; + (*index)++; + } + else + for (int i = 0; i < 8; i++) + if (node->child[i]) + octreeIndex(node->child[i], rgbpal, index); +} + +/** + * compute the squared distance between two colors + */ +static int distRGB(RGB rgb1, RGB rgb2) +{ + return + (rgb1.r - rgb2.r) * (rgb1.r - rgb2.r) + + (rgb1.g - rgb2.g) * (rgb1.g - rgb2.g) + + (rgb1.b - rgb2.b) * (rgb1.b - rgb2.b); +} + +/** + * find the index of closest color in a palette + */ +static int findRGB(RGB *rgbpal, int ncolor, RGB rgb) +{ + //assert(ncolor > 0); + //assert(rgbpal); + int index = -1, dist = 0; + for (int k = 0; k < ncolor; k++) + { + int d = distRGB(rgbpal[k], rgb); + if (index == -1 || d < dist) { dist = d; index = k; } + } + return index; +} + +/** + * (qsort) compare two colors for brightness + */ +static int compRGB(const void *a, const void *b) +{ + RGB *ra = (RGB *)a, *rb = (RGB *)b; + return (ra->r + ra->g + ra->b) - (rb->r + rb->g + rb->b); +} + +/** + * quantize an RGB image to a reduced number of colors. + */ +IndexedMap *rgbMapQuantize(RgbMap *rgbmap, int ncolor) +{ + assert(rgbmap); + assert(ncolor > 0); + + pool pool; + + Ocnode *tree; + try { + tree = octreeBuild(&pool, rgbmap, ncolor); + } + catch (std::bad_alloc& ex) { + return NULL; //should do smthg else? + } + + RGB *rgbpal = new RGB[ncolor]; + int indexes = 0; + octreeIndex(tree, rgbpal, &indexes); + + octreeDelete(&pool, tree); + + // stacking with increasing contrasts + qsort((void *)rgbpal, indexes, sizeof(RGB), compRGB); + + // make the new map + IndexedMap *newmap = IndexedMapCreate(rgbmap->width, rgbmap->height); + if (!newmap) { delete rgbpal; return NULL; } + + // fill in the color lookup table + for (int i = 0; i < indexes; i++) newmap->clut[i] = rgbpal[i]; + newmap->nrColors = indexes; + + // fill in new map pixels + for (int y = 0; y < rgbmap->height; y++) + { + for (int x = 0; x < rgbmap->width; x++) + { + RGB rgb = rgbmap->getPixel(rgbmap, x, y); + int index = findRGB(rgbpal, ncolor, rgb); + newmap->setPixel(newmap, x, y, index); + } + } + + delete rgbpal; + return newmap; +} diff --git a/src/trace/quantize.h b/src/trace/quantize.h new file mode 100644 index 000000000..fa4340877 --- /dev/null +++ b/src/trace/quantize.h @@ -0,0 +1,22 @@ +/* + * Quantization for Inkscape + * + * Authors: + * Stéphane Gimenez + * + * Copyright (C) 2006 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef __QUANTIZE_H__ +#define __QUANTIZE_H__ + +#include "imagemap.h" + +/** + * Quantize an RGB image to a reduced number of colors. + */ +IndexedMap *rgbMapQuantize(RgbMap *rgbmap, int nrColors); + +#endif /* __QUANTIZE_H__ */