diff options
| author | MenTaLguY <mental@rydia.net> | 2006-01-16 02:36:01 +0000 |
|---|---|---|
| committer | mental <mental@users.sourceforge.net> | 2006-01-16 02:36:01 +0000 |
| commit | 179fa413b047bede6e32109e2ce82437c5fb8d34 (patch) | |
| tree | a5a6ac2c1708bd02288fbd8edb2ff500ff2e0916 /src/trace | |
| download | inkscape-179fa413b047bede6e32109e2ce82437c5fb8d34.tar.gz inkscape-179fa413b047bede6e32109e2ce82437c5fb8d34.zip | |
moving trunk for module inkscape
(bzr r1)
Diffstat (limited to 'src/trace')
30 files changed, 7389 insertions, 0 deletions
diff --git a/src/trace/.cvsignore b/src/trace/.cvsignore new file mode 100644 index 000000000..47c85e026 --- /dev/null +++ b/src/trace/.cvsignore @@ -0,0 +1,4 @@ +.deps +.dirstamp +makefile + diff --git a/src/trace/Makefile_insert b/src/trace/Makefile_insert new file mode 100644 index 000000000..92686be0b --- /dev/null +++ b/src/trace/Makefile_insert @@ -0,0 +1,34 @@ +## Makefile.am fragment sourced by src/Makefile.am. + +trace/all: trace/libtrace.a + +trace/clean: + rm -f trace/libtrace.a $(trace_libtrace_a_OBJECTS) + +trace_libtrace_a_SOURCES = \ + trace/trace.h \ + trace/trace.cpp \ + trace/imagemap-gdk.cpp \ + trace/imagemap-gdk.h \ + trace/imagemap.cpp \ + trace/imagemap.h \ + trace/filterset.cpp \ + trace/filterset.h \ + trace/potrace/auxiliary.h \ + trace/potrace/bitmap.h \ + trace/potrace/curve.cpp \ + trace/potrace/curve.h \ + trace/potrace/decompose.cpp \ + trace/potrace/decompose.h \ + trace/potrace/greymap.cpp \ + trace/potrace/greymap.h \ + trace/potrace/lists.h \ + trace/potrace/potracelib.cpp \ + trace/potrace/potracelib.h \ + trace/potrace/progress.h \ + trace/potrace/render.cpp \ + trace/potrace/render.h \ + trace/potrace/trace.cpp \ + trace/potrace/trace.h \ + trace/potrace/inkscape-potrace.cpp \ + trace/potrace/inkscape-potrace.h diff --git a/src/trace/filterset.cpp b/src/trace/filterset.cpp new file mode 100644 index 000000000..13a0d9b66 --- /dev/null +++ b/src/trace/filterset.cpp @@ -0,0 +1,959 @@ +/* + * Some filters for Potrace in Inkscape + * + * Authors: + * Bob Jamison <rjamison@titan.com> + * + * Copyright (C) 2004 Bob Jamison + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include <stdlib.h> + +#include "imagemap-gdk.h" +#include "filterset.h" + + +/*######################################################################### +### G A U S S I A N (smoothing) +#########################################################################*/ + +/** + * + */ +static int gaussMatrix[] = +{ + 2, 4, 5, 4, 2, + 4, 9, 12, 9, 4, + 5, 12, 15, 12, 5, + 4, 9, 12, 9, 4, + 2, 4, 5, 4, 2 +}; + + +/** + * + */ +GrayMap *grayMapGaussian(GrayMap *me) +{ + int width = me->width; + int height = me->height; + int firstX = 2; + int lastX = width-3; + int firstY = 2; + int lastY = height-3; + + GrayMap *newGm = GrayMapCreate(width, height); + if (!newGm) + return NULL; + + for (int y = 0 ; y<height ; y++) + { + for (int x = 0 ; x<width ; x++) + { + /* image boundaries */ + if (x<firstX || x>lastX || y<firstY || y>lastY) + { + newGm->setPixel(newGm, x, y, me->getPixel(me, x, y)); + continue; + } + + /* all other pixels */ + int gaussIndex = 0; + unsigned long sum = 0; + for (int i= y-2 ; i<=y+2 ; i++) + { + for (int j= x-2; j<=x+2 ; j++) + { + int weight = gaussMatrix[gaussIndex++]; + sum += me->getPixel(me, j, i) * weight; + } + } + sum /= 159; + newGm->setPixel(newGm, x, y, sum); + } + } + + return newGm; +} + + + + + +/** + * + */ +RgbMap *rgbMapGaussian(RgbMap *me) +{ + int width = me->width; + int height = me->height; + int firstX = 2; + int lastX = width-3; + int firstY = 2; + int lastY = height-3; + + RgbMap *newGm = RgbMapCreate(width, height); + if (!newGm) + return NULL; + + for (int y = 0 ; y<height ; y++) + { + for (int x = 0 ; x<width ; x++) + { + /* image boundaries */ + if (x<firstX || x>lastX || y<firstY || y>lastY) + { + newGm->setPixelRGB(newGm, x, y, me->getPixel(me, x, y)); + continue; + } + + /* all other pixels */ + int gaussIndex = 0; + int sumR = 0; + int sumG = 0; + int sumB = 0; + for (int i= y-2 ; i<=y+2 ; i++) + { + for (int j= x-2; j<=x+2 ; j++) + { + int weight = gaussMatrix[gaussIndex++]; + RGB rgb = me->getPixel(me, j, i); + sumR += weight * (int)rgb.r; + sumG += weight * (int)rgb.g; + sumB += weight * (int)rgb.b; + } + } + RGB rout; + rout.r = ( sumR / 159 ) & 0xff; + rout.g = ( sumG / 159 ) & 0xff; + rout.b = ( sumB / 159 ) & 0xff; + newGm->setPixelRGB(newGm, x, y, rout); + } + } + + return newGm; + +} + + + + +/*######################################################################### +### C A N N Y E D G E D E T E C T I O N +#########################################################################*/ + + +static int sobelX[] = +{ + -1, 0, 1 , + -2, 0, 2 , + -1, 0, 1 +}; + +static int sobelY[] = +{ + 1, 2, 1 , + 0, 0, 0 , + -1, -2, -1 +}; + + + +/** + * Perform Sobel convolution on a GrayMap + */ +static GrayMap *grayMapSobel(GrayMap *gm, + double dLowThreshold, double dHighThreshold) +{ + int width = gm->width; + int height = gm->height; + int firstX = 1; + int lastX = width-2; + int firstY = 1; + int lastY = height-2; + + GrayMap *newGm = GrayMapCreate(width, height); + if (!newGm) + return NULL; + + for (int y = 0 ; y<height ; y++) + { + for (int x = 0 ; x<width ; x++) + { + unsigned long sum = 0; + /* image boundaries */ + if (x<firstX || x>lastX || y<firstY || y>lastY) + { + sum = 0; + } + else + { + /* ### SOBEL FILTERING #### */ + long sumX = 0; + long sumY = 0; + int sobelIndex = 0; + for (int i= y-1 ; i<=y+1 ; i++) + { + for (int j= x-1; j<=x+1 ; j++) + { + sumX += gm->getPixel(gm, j, i) * + sobelX[sobelIndex++]; + } + } + + sobelIndex = 0; + for (int i= y-1 ; i<=y+1 ; i++) + { + for (int j= x-1; j<=x+1 ; j++) + { + sumY += gm->getPixel(gm, j, i) * + sobelY[sobelIndex++]; + } + } + /*### GET VALUE ### */ + sum = abs(sumX) + abs(sumY); + + if (sum > 765) + sum = 765; + +#if 0 + /*### GET ORIENTATION (slow, pedantic way) ### */ + double orient = 0.0; + if (sumX==0) + { + if (sumY==0) + orient = 0.0; + else if (sumY<0) + { + sumY = -sumY; + orient = 90.0; + } + else + orient = 90.0; + } + else + { + orient = 57.295779515 * atan2( ((double)sumY),((double)sumX) ); + if (orient < 0.0) + orient += 180.0; + } + + /*### GET EDGE DIRECTION ### */ + int edgeDirection = 0; + if (orient < 22.5) + edgeDirection = 0; + else if (orient < 67.5) + edgeDirection = 45; + else if (orient < 112.5) + edgeDirection = 90; + else if (orient < 157.5) + edgeDirection = 135; +#else + /*### GET EDGE DIRECTION (fast way) ### */ + int edgeDirection = 0; /*x,y=0*/ + if (sumX==0) + { + if (sumY!=0) + edgeDirection = 90; + } + else + { + /*long slope = sumY*1024/sumX;*/ + long slope = (sumY << 10)/sumX; + if (slope > 2472 || slope< -2472) /*tan(67.5)*1024*/ + edgeDirection = 90; + else if (slope > 414) /*tan(22.5)*1024*/ + edgeDirection = 45; + else if (slope < -414) /*-tan(22.5)*1024*/ + edgeDirection = 135; + } + +#endif + /* printf("%ld %ld %f %d\n", sumX, sumY, orient, edgeDirection); */ + + /*### Get two adjacent pixels in edge direction ### */ + unsigned long leftPixel; + unsigned long rightPixel; + if (edgeDirection == 0) + { + leftPixel = gm->getPixel(gm, x-1, y); + rightPixel = gm->getPixel(gm, x+1, y); + } + else if (edgeDirection == 45) + { + leftPixel = gm->getPixel(gm, x-1, y+1); + rightPixel = gm->getPixel(gm, x+1, y-1); + } + else if (edgeDirection == 90) + { + leftPixel = gm->getPixel(gm, x, y-1); + rightPixel = gm->getPixel(gm, x, y+1); + } + else /*135 */ + { + leftPixel = gm->getPixel(gm, x-1, y-1); + rightPixel = gm->getPixel(gm, x+1, y+1); + } + + /*### Compare current value to adjacent pixels ### */ + /*### if less that either, suppress it ### */ + if (sum < leftPixel || sum < rightPixel) + sum = 0; + else + { + unsigned long highThreshold = + (unsigned long)(dHighThreshold * 765.0); + unsigned long lowThreshold = + (unsigned long)(dLowThreshold * 765.0); + if (sum >= highThreshold) + sum = 765; /* EDGE. 3*255 this needs to be settable */ + else if (sum < lowThreshold) + sum = 0; /* NONEDGE */ + else + { + if ( gm->getPixel(gm, x-1, y-1)> highThreshold || + gm->getPixel(gm, x , y-1)> highThreshold || + gm->getPixel(gm, x+1, y-1)> highThreshold || + gm->getPixel(gm, x-1, y )> highThreshold || + gm->getPixel(gm, x+1, y )> highThreshold || + gm->getPixel(gm, x-1, y+1)> highThreshold || + gm->getPixel(gm, x , y+1)> highThreshold || + gm->getPixel(gm, x+1, y+1)> highThreshold) + sum = 765; /* EDGE fix me too */ + else + sum = 0; /* NONEDGE */ + } + } + + + }/* else */ + if (sum==0) /* invert light & dark */ + sum = 765; + else + sum = 0; + newGm->setPixel(newGm, x, y, sum); + }/* for (x) */ + }/* for (y) */ + + return newGm; +} + + + + + +/** + * + */ +GrayMap * +grayMapCanny(GrayMap *gm, double lowThreshold, double highThreshold) +{ + if (!gm) + return NULL; + GrayMap *gaussGm = grayMapGaussian(gm); + if (!gaussGm) + return NULL; + /*gaussGm->writePPM(gaussGm, "gauss.ppm");*/ + + GrayMap *cannyGm = grayMapSobel(gaussGm, lowThreshold, highThreshold); + if (!cannyGm) + return NULL; + /*cannyGm->writePPM(cannyGm, "canny.ppm");*/ + + gaussGm->destroy(gaussGm); + + return cannyGm; +} + + + + + + + +/** + * + */ +GdkPixbuf * +gdkCanny(GdkPixbuf *img, double lowThreshold, double highThreshold) +{ + if (!img) + return NULL; + + + GrayMap *grayMap = gdkPixbufToGrayMap(img); + if (!grayMap) + return NULL; + + /*grayMap->writePPM(grayMap, "gbefore.ppm");*/ + + GrayMap *cannyGm = grayMapCanny(grayMap,lowThreshold, highThreshold); + + grayMap->destroy(grayMap); + + if (!cannyGm) + return NULL; + + /*grayMap->writePPM(grayMap, "gafter.ppm");*/ + + GdkPixbuf *newImg = grayMapToGdkPixbuf(cannyGm); + + + 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 ; bit<bitsPerSample ; bit++) + { + /* update values of all nodes from the root to the leaf */ + node->r += 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<bitsPerSample ; bit++) + { + int index = (((r >> 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; i<nr ; i++) + printf(" "); +} + +/** + * pretty-print an octree node and its children + */ +void octreeNodePrint(OctreeNode *node, int indent) +{ + spaces(indent); printf("####Node###\n"); + spaces(indent); printf("r :%lu\n", node->r); + 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 ; y<rgbMap->height ; y++) + { + for (int x=0 ; x<rgbMap->width ; 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; i<len ; i++) + { + OctreeNode *node = palette[i]; + RGB rgb; + if (node->nrPixels == 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 ; i<paletteSize ; i++) + { + RGB entry = rgbpalette[i]; + unsigned long dr = candidate.r - entry.r; + unsigned long dg = candidate.g - entry.g; + unsigned long db = candidate.b - entry.b; + unsigned long match = dr * dr + dg * dg + db * db; + if (match < closestMatch) + { + *closestIndex = i; + closestMatch = match; + closestRGB = entry; + } + } + + return closestRGB; +} + + +/** + * 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) +{ + if (!rgbMap) + return NULL; + + OctreeNode *otree = octreeBuild(rgbMap, bitsPerSample, nrColors); + if (!otree) + { + return NULL; + } + + /*Make sure we don't request more colors than actually exist*/ + int nodeCount = octreeLeafCount(otree); + if (nodeCount < nrColors) + nrColors = nodeCount; + + RGB *rgbpal = makeRGBPalette(otree, nrColors); + if (!rgbpal) + { + octreeNodeDelete(otree); + return NULL; + } + + /*We have our original and palette. Make the new one*/ + IndexedMap *newMap = IndexedMapCreate(rgbMap->width, 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 ; y<rgbMap->height ; y++) + { + for (int x=0 ; x<rgbMap->width ; 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 + */ +GrayMap *quantizeBand(RgbMap *rgbMap, int nrColors) +{ + + int bitsPerSample = 4; + + RgbMap *gaussMap = rgbMapGaussian(rgbMap); + //gaussMap->writePPM(gaussMap, "rgbgauss.ppm"); + + IndexedMap *qMap = rgbMapQuantize(gaussMap, bitsPerSample, 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) */ + for (int y=0 ; y<qMap->height ; y++) + { + for (int x=0 ; x<qMap->width ; x++) + { + RGB rgb = qMap->getPixelValue(qMap, x, y); + int sum = rgb.r + rgb.g + rgb.b; + if (sum & 1) + sum = 765; + else + sum = 0; + /*printf("%d %d %d : %d\n", rgb.r, rgb.g, rgb.b, index);*/ + gm->setPixel(gm, x, y, sum); + } + } + + qMap->destroy(qMap); + + return gm; +} + + + + + + + + +/*######################################################################### +### E N D O F F I L E +#########################################################################*/ + + + + + + + + + + diff --git a/src/trace/filterset.h b/src/trace/filterset.h new file mode 100644 index 000000000..5e73847bd --- /dev/null +++ b/src/trace/filterset.h @@ -0,0 +1,82 @@ +/* + * Some filters for Potrace in Inkscape + * + * Authors: + * Bob Jamison <rjamison@titan.com> + * + * Copyright (C) 2004 Bob Jamison + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef __FILTERSET_H__ +#define __FILTERSET_H__ + +#include "imagemap.h" + +#include <gdk-pixbuf/gdk-pixbuf.h> + +#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 + + +/** + * + */ +GrayMap *grayMapGaussian(GrayMap *me); + +/** + * + */ +RgbMap *rgbMapGaussian(RgbMap *me); + +/** + * + */ +GrayMap *grayMapCanny(GrayMap *gm, + double lowThreshold, double highThreshold); + +/** + * + */ +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); + +/** + * + */ +GrayMap *quantizeBand(RgbMap *rgbmap, int nrColors); + + + +#ifdef __cplusplus +} +#endif + + +#endif /* __FILTERSET_H__ */ + +/*######################################################################### +### E N D O F F I L E +#########################################################################*/ diff --git a/src/trace/imagemap-gdk.cpp b/src/trace/imagemap-gdk.cpp new file mode 100644 index 000000000..93a1d05aa --- /dev/null +++ b/src/trace/imagemap-gdk.cpp @@ -0,0 +1,206 @@ +#include <stdlib.h> + +#include "imagemap-gdk.h" + + +/*######################################################################### +## G R A Y M A P +#########################################################################*/ + +GrayMap *gdkPixbufToGrayMap(GdkPixbuf *buf) +{ + if (!buf) + return NULL; + + int width = gdk_pixbuf_get_width(buf); + int height = gdk_pixbuf_get_height(buf); + guchar *pixdata = gdk_pixbuf_get_pixels(buf); + int rowstride = gdk_pixbuf_get_rowstride(buf); + int n_channels = gdk_pixbuf_get_n_channels(buf); + + GrayMap *grayMap = GrayMapCreate(width, height); + if (!grayMap) + return NULL; + + //### Fill in the odd cells with RGB values + int x,y; + int row = 0; + for (y=0 ; y<height ; y++) + { + guchar *p = pixdata + row; + for (x=0 ; x<width ; x++) + { + int alpha = (int)p[3]; + int white = 3 * (255-alpha); + unsigned long sample = (int)p[0] + (int)p[1] +(int)p[2]; + unsigned long bright = sample * alpha / 256 + white; + grayMap->setPixel(grayMap, x, y, bright); + p += n_channels; + } + row += rowstride; + } + + return grayMap; +} + +GdkPixbuf *grayMapToGdkPixbuf(GrayMap *grayMap) +{ + if (!grayMap) + return NULL; + + guchar *pixdata = (guchar *) + malloc(sizeof(guchar) * grayMap->width * grayMap->height * 3); + if (!pixdata) + return NULL; + + int n_channels = 3; + int rowstride = grayMap->width * 3; + + GdkPixbuf *buf = gdk_pixbuf_new_from_data(pixdata, GDK_COLORSPACE_RGB, + 0, 8, grayMap->width, grayMap->height, + rowstride, NULL, NULL); + + //### Fill in the odd cells with RGB values + int x,y; + int row = 0; + for (y=0 ; y<grayMap->height ; y++) + { + guchar *p = pixdata + row; + for (x=0 ; x<grayMap->width ; x++) + { + unsigned long pix = grayMap->getPixel(grayMap, x, y) / 3; + p[0] = p[1] = p[2] = (guchar)(pix & 0xff); + p += n_channels; + } + row += rowstride; + } + + return buf; +} + + + +/*######################################################################### +## R G B M A P +#########################################################################*/ + +RgbMap *gdkPixbufToRgbMap(GdkPixbuf *buf) +{ + if (!buf) + return NULL; + + int width = gdk_pixbuf_get_width(buf); + int height = gdk_pixbuf_get_height(buf); + guchar *pixdata = gdk_pixbuf_get_pixels(buf); + int rowstride = gdk_pixbuf_get_rowstride(buf); + int n_channels = gdk_pixbuf_get_n_channels(buf); + + RgbMap *rgbMap = RgbMapCreate(width, height); + if (!rgbMap) + return NULL; + + //### Fill in the cells with RGB values + int x,y; + int row = 0; + for (y=0 ; y<height ; y++) + { + guchar *p = pixdata + row; + for (x=0 ; x<width ; x++) + { + int alpha = (int)p[3]; + int white = 255 - alpha; + int r = (int)p[0]; r = r * alpha / 256 + white; + int g = (int)p[1]; g = g * alpha / 256 + white; + int b = (int)p[2]; b = b * alpha / 256 + white; + + rgbMap->setPixel(rgbMap, x, y, r, g, b); + p += n_channels; + } + row += rowstride; + } + + return rgbMap; +} + +GdkPixbuf *rgbMapToGdkPixbuf(RgbMap *rgbMap) +{ + if (!rgbMap) + return NULL; + + guchar *pixdata = (guchar *) + malloc(sizeof(guchar) * rgbMap->width * rgbMap->height * 3); + if (!pixdata) + return NULL; + + int n_channels = 3; + int rowstride = rgbMap->width * 3; + + GdkPixbuf *buf = gdk_pixbuf_new_from_data(pixdata, GDK_COLORSPACE_RGB, + 0, 8, rgbMap->width, rgbMap->height, + rowstride, NULL, NULL); + + //### Fill in the cells with RGB values + int x,y; + int row = 0; + for (y=0 ; y<rgbMap->height ; y++) + { + guchar *p = pixdata + row; + for (x=0 ; x<rgbMap->width ; x++) + { + RGB rgb = rgbMap->getPixel(rgbMap, x, y); + p[0] = rgb.r & 0xff; + p[1] = rgb.g & 0xff; + p[2] = rgb.b & 0xff; + p += n_channels; + } + row += rowstride; + } + + return buf; +} + +/*######################################################################### +## I N D E X E D M A P +#########################################################################*/ + + +GdkPixbuf *indexedMapToGdkPixbuf(IndexedMap *iMap) +{ + if (!iMap) + return NULL; + + guchar *pixdata = (guchar *) + malloc(sizeof(guchar) * iMap->width * iMap->height * 3); + if (!pixdata) + return NULL; + + int n_channels = 3; + int rowstride = iMap->width * 3; + + GdkPixbuf *buf = gdk_pixbuf_new_from_data(pixdata, GDK_COLORSPACE_RGB, + 0, 8, iMap->width, iMap->height, + rowstride, NULL, NULL); + + //### Fill in the cells with RGB values + int x,y; + int row = 0; + for (y=0 ; y<iMap->height ; y++) + { + guchar *p = pixdata + row; + for (x=0 ; x<iMap->width ; x++) + { + RGB rgb = iMap->getPixelValue(iMap, x, y); + p[0] = rgb.r & 0xff; + p[1] = rgb.g & 0xff; + p[2] = rgb.b & 0xff; + p += n_channels; + } + row += rowstride; + } + + return buf; +} + +/*######################################################################### +## E N D O F F I L E +#########################################################################*/ diff --git a/src/trace/imagemap-gdk.h b/src/trace/imagemap-gdk.h new file mode 100644 index 000000000..5eaf78faa --- /dev/null +++ b/src/trace/imagemap-gdk.h @@ -0,0 +1,46 @@ +#ifndef __GRAYMAP_GDK_H__ +#define __GRAYMAP_GDK_H__ + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#include "imagemap.h" + +#include <gdk-pixbuf/gdk-pixbuf.h> + +/*######################################################################### +### I M A G E M A P --- GDK +#########################################################################*/ + + + +#ifdef __cplusplus +extern "C" { +#endif + +GrayMap *gdkPixbufToGrayMap(GdkPixbuf *buf); + +GdkPixbuf *grayMapToGdkPixbuf(GrayMap *grayMap); + +RgbMap *gdkPixbufToRgbMap(GdkPixbuf *buf); + +GdkPixbuf *rgbMapToGdkPixbuf(RgbMap *rgbMap); + +GdkPixbuf *indexedMapToGdkPixbuf(IndexedMap *iMap); + + +#ifdef __cplusplus +} +#endif + + +#endif /* __GRAYMAP_GDK_H__ */ + +/*######################################################################### +### E N D O F F I L E +#########################################################################*/ diff --git a/src/trace/imagemap.cpp b/src/trace/imagemap.cpp new file mode 100644 index 000000000..1e65ddf5f --- /dev/null +++ b/src/trace/imagemap.cpp @@ -0,0 +1,331 @@ +#include <stdlib.h> + +#include "imagemap.h" + +#include "io/sys.h" + +/*######################################################################### +### G R A Y M A P +#########################################################################*/ + + +static void gSetPixel(GrayMap *me, int x, int y, unsigned long val) +{ + if (val>765) + val = 765; + unsigned long *pix = me->rows[y] + x; + *pix = val; +} + +static unsigned long gGetPixel(GrayMap *me, int x, int y) +{ + unsigned long *pix = me->rows[y] + x; + return *pix; +} + + +static int gWritePPM(GrayMap *me, char *fileName) +{ + if (!fileName) + return FALSE; + + Inkscape::IO::dump_fopen_call( fileName, "C" ); + FILE *f = Inkscape::IO::fopen_utf8name(fileName, "wb"); + if (!f) + return FALSE; + + fprintf(f, "P6 %d %d 255\n", me->width, me->height); + + for (int y=0 ; y<me->height; y++) + { + for (int x=0 ; x<me->width ; x++) + { + unsigned long pix = me->getPixel(me, x, y) / 3; + unsigned char pixb = (unsigned char) (pix & 0xff); + fwrite(&pixb, 1, 1, f); + fwrite(&pixb, 1, 1, f); + fwrite(&pixb, 1, 1, f); + } + } + fclose(f); + return TRUE; +} + + +static void gDestroy(GrayMap *me) +{ + if (me->pixels) + free(me->pixels); + if (me->rows) + free(me->rows); + free(me); +} + +GrayMap *GrayMapCreate(int width, int height) +{ + + GrayMap *me = (GrayMap *)malloc(sizeof(GrayMap)); + if (!me) + return NULL; + + /** methods **/ + me->setPixel = gSetPixel; + me->getPixel = gGetPixel; + me->writePPM = gWritePPM; + me->destroy = gDestroy; + + /** fields **/ + me->width = width; + me->height = height; + me->pixels = (unsigned long *) + malloc(sizeof(unsigned long) * width * height); + me->rows = (unsigned long **) + malloc(sizeof(unsigned long *) * height); + if (!me->pixels || !me->rows) + { + free(me); + return NULL; + } + + unsigned long *row = me->pixels; + for (int i=0 ; i<height ; i++) + { + me->rows[i] = row; + row += width; + } + + return me; +} + + + + + +/*######################################################################### +### R G B M A P +#########################################################################*/ + + + +static void rSetPixel(RgbMap *me, int x, int y, int r, int g, int b) +{ + RGB *pix = me->rows[y] + x; + pix->r = r; + pix->g = g; + pix->b = b; +} + +static void rSetPixelRGB(RgbMap *me, int x, int y, RGB rgb) +{ + RGB *pix = me->rows[y] + x; + *pix = rgb; +} + +static RGB rGetPixel(RgbMap *me, int x, int y) +{ + RGB *pix = me->rows[y] + x; + return *pix; +} + + + +static int rWritePPM(RgbMap *me, char *fileName) +{ + if (!fileName) + return FALSE; + + Inkscape::IO::dump_fopen_call(fileName, "D"); + FILE *f = Inkscape::IO::fopen_utf8name(fileName, "wb"); + if (!f) + return FALSE; + + fprintf(f, "P6 %d %d 255\n", me->width, me->height); + + for (int y=0 ; y<me->height; y++) + { + for (int x=0 ; x<me->width ; x++) + { + RGB rgb = me->getPixel(me, x, y); + fwrite(&(rgb.r), 1, 1, f); + fwrite(&(rgb.g), 1, 1, f); + fwrite(&(rgb.b), 1, 1, f); + } + } + fclose(f); + return TRUE; +} + + +static void rDestroy(RgbMap *me) +{ + if (me->pixels) + free(me->pixels); + if (me->rows) + free(me->rows); + free(me); +} + + + +RgbMap *RgbMapCreate(int width, int height) +{ + + RgbMap *me = (RgbMap *)malloc(sizeof(RgbMap)); + if (!me) + return NULL; + + /** methods **/ + me->setPixel = rSetPixel; + me->setPixelRGB = rSetPixelRGB; + me->getPixel = rGetPixel; + me->writePPM = rWritePPM; + me->destroy = rDestroy; + + + /** fields **/ + me->width = width; + me->height = height; + me->pixels = (RGB *) + malloc(sizeof(RGB) * width * height); + me->rows = (RGB **) + malloc(sizeof(RGB *) * height); + if (!me->pixels) + { + free(me); + return NULL; + } + + RGB *row = me->pixels; + for (int i=0 ; i<height ; i++) + { + me->rows[i] = row; + row += width; + } + + return me; +} + + + + +/*######################################################################### +### I N D E X E D M A P +#########################################################################*/ + + + +static void iSetPixel(IndexedMap *me, int x, int y, unsigned int index) +{ + unsigned int *pix = me->rows[y] + x; + *pix = index; +} + + +static unsigned int iGetPixel(IndexedMap *me, int x, int y) +{ + unsigned int *pix = me->rows[y] + x; + return *pix; +} + +static RGB iGetPixelValue(IndexedMap *me, int x, int y) +{ + unsigned int *pix = me->rows[y] + x; + RGB rgb = me->clut[((*pix)&0xff)]; + return rgb; +} + + + +static int iWritePPM(IndexedMap *me, char *fileName) +{ + if (!fileName) + return FALSE; + + Inkscape::IO::dump_fopen_call(fileName, "D"); + FILE *f = Inkscape::IO::fopen_utf8name(fileName, "wb"); + if (!f) + return FALSE; + + fprintf(f, "P6 %d %d 255\n", me->width, me->height); + + for (int y=0 ; y<me->height; y++) + { + for (int x=0 ; x<me->width ; x++) + { + RGB rgb = me->getPixelValue(me, x, y); + fwrite(&(rgb.r), 1, 1, f); + fwrite(&(rgb.g), 1, 1, f); + fwrite(&(rgb.b), 1, 1, f); + } + } + fclose(f); + return TRUE; +} + + +static void iDestroy(IndexedMap *me) +{ + if (me->pixels) + free(me->pixels); + if (me->rows) + free(me->rows); + free(me); +} + + + +IndexedMap *IndexedMapCreate(int width, int height) +{ + + IndexedMap *me = (IndexedMap *)malloc(sizeof(IndexedMap)); + if (!me) + return NULL; + + /** methods **/ + me->setPixel = iSetPixel; + me->getPixel = iGetPixel; + me->getPixelValue = iGetPixelValue; + me->writePPM = iWritePPM; + me->destroy = iDestroy; + + + /** fields **/ + me->width = width; + me->height = height; + me->pixels = (unsigned int *) + malloc(sizeof(unsigned int) * width * height); + me->rows = (unsigned int **) + malloc(sizeof(unsigned int *) * height); + if (!me->pixels) + { + free(me); + return NULL; + } + + unsigned int *row = me->pixels; + for (int i=0 ; i<height ; i++) + { + me->rows[i] = row; + row += width; + } + + me->nrColors = 0; + + RGB rgb; + rgb.r = rgb.g = rgb.b = 0; + for (int i=0; i<256 ; i++) + { + me->clut[i] = rgb; + } + + return me; +} + + + + + + +/*######################################################################### +### E N D O F F I L E +#########################################################################*/ diff --git a/src/trace/imagemap.h b/src/trace/imagemap.h new file mode 100644 index 000000000..d69adefd7 --- /dev/null +++ b/src/trace/imagemap.h @@ -0,0 +1,292 @@ +#ifndef __IMAGEMAP_H__ +#define __IMAGEMAP_H__ + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + + +/*######################################################################### +### G R A Y M A P +#########################################################################*/ + + +typedef struct GrayMap_def GrayMap; + +#define GRAYMAP_BLACK 0 +#define GRAYMAP_WHITE 765 + +/** + * + */ +struct GrayMap_def +{ + + /*################# + ### METHODS + #################*/ + + /** + * + */ + void (*setPixel)(GrayMap *me, int x, int y, unsigned long val); + + /** + * + */ + unsigned long (*getPixel)(GrayMap *me, int x, int y); + + /** + * + */ + int (*writePPM)(GrayMap *me, char *fileName); + + + + /** + * + */ + void (*destroy)(GrayMap *me); + + + + /*################# + ### FIELDS + #################*/ + + /** + * + */ + int width; + + /** + * + */ + int height; + + /** + * The pixel array + */ + unsigned long *pixels; + + /** + * Pointer to the beginning of each row + */ + unsigned long **rows; + +}; + +#ifdef __cplusplus +extern "C" { +#endif + +GrayMap *GrayMapCreate(int width, int height); + +#ifdef __cplusplus +} +#endif + + + + +/*######################################################################### +### R G B M A P +#########################################################################*/ + +typedef struct +{ + unsigned char r; + unsigned char g; + unsigned char b; +} RGB; + + + +typedef struct RgbMap_def RgbMap; + +/** + * + */ +struct RgbMap_def +{ + + /*################# + ### METHODS + #################*/ + + /** + * + */ + void (*setPixel)(RgbMap *me, int x, int y, int r, int g, int b); + + + /** + * + */ + void (*setPixelRGB)(RgbMap *me, int x, int y, RGB rgb); + + /** + * + */ + RGB (*getPixel)(RgbMap *me, int x, int y); + + /** + * + */ + int (*writePPM)(RgbMap *me, char *fileName); + + + + /** + * + */ + void (*destroy)(RgbMap *me); + + + + /*################# + ### FIELDS + #################*/ + + /** + * + */ + int width; + + /** + * + */ + int height; + + /** + * The allocated array of pixels + */ + RGB *pixels; + + /** + * Pointers to the beginning of each row of pixels + */ + RGB **rows; + +}; + + + +#ifdef __cplusplus +extern "C" { +#endif + +RgbMap *RgbMapCreate(int width, int height); + +#ifdef __cplusplus +} +#endif + + + + +/*######################################################################### +### I N D E X E D M A P +#########################################################################*/ + + +typedef struct IndexedMap_def IndexedMap; + +/** + * + */ +struct IndexedMap_def +{ + + /*################# + ### METHODS + #################*/ + + /** + * + */ + void (*setPixel)(IndexedMap *me, int x, int y, unsigned int index); + + + /** + * + */ + unsigned int (*getPixel)(IndexedMap *me, int x, int y); + + /** + * + */ + RGB (*getPixelValue)(IndexedMap *me, int x, int y); + + /** + * + */ + int (*writePPM)(IndexedMap *me, char *fileName); + + + + /** + * + */ + void (*destroy)(IndexedMap *me); + + + + /*################# + ### FIELDS + #################*/ + + /** + * + */ + int width; + + /** + * + */ + int height; + + /** + * The allocated array of pixels + */ + unsigned int *pixels; + + /** + * Pointers to the beginning of each row of pixels + */ + unsigned int **rows; + + /** + * + */ + int nrColors; + + /** + * Color look up table + */ + RGB clut[256]; + +}; + + + +#ifdef __cplusplus +extern "C" { +#endif + +IndexedMap *IndexedMapCreate(int width, int height); + +#ifdef __cplusplus +} +#endif + + +#endif /* __IMAGEMAP_H__ */ + +/*######################################################################### +### E N D O F F I L E +#########################################################################*/ diff --git a/src/trace/makefile.in b/src/trace/makefile.in new file mode 100644 index 000000000..3b08e58ba --- /dev/null +++ b/src/trace/makefile.in @@ -0,0 +1,17 @@ +# Convenience stub makefile to call the real Makefile. + +@SET_MAKE@ + +# Explicit so that it's the default rule. +all: + cd .. && $(MAKE) trace/all + +clean %.a %.o: + cd .. && $(MAKE) trace/$@ + +.PHONY: all clean + +OBJEXT = @OBJEXT@ + +.SUFFIXES: +.SUFFIXES: .a .$(OBJEXT) diff --git a/src/trace/potrace/.cvsignore b/src/trace/potrace/.cvsignore new file mode 100644 index 000000000..16e013378 --- /dev/null +++ b/src/trace/potrace/.cvsignore @@ -0,0 +1,3 @@ +.deps +.dirstamp + diff --git a/src/trace/potrace/auxiliary.h b/src/trace/potrace/auxiliary.h new file mode 100644 index 000000000..18573ad53 --- /dev/null +++ b/src/trace/potrace/auxiliary.h @@ -0,0 +1,60 @@ +/* Copyright (C) 2001-2005 Peter Selinger. + This file is part of potrace. It is free software and it is covered + by the GNU General Public License. See the file COPYING for details. */ + +/* This header file collects some general-purpose macros (and static + inline functions) that are used in various places. */ + +#ifndef AUXILIARY_H +#define AUXILIARY_H + +/* ---------------------------------------------------------------------- */ +/* point arithmetic */ + +#include "potracelib.h" + +struct point_s { + long x; + long y; +}; +typedef struct point_s point_t; + +typedef potrace_dpoint_t dpoint_t; + +/* convert point_t to dpoint_t */ +static inline dpoint_t dpoint(point_t p) { + dpoint_t res; + res.x = p.x; + res.y = p.y; + return res; +} + +/* range over the straight line segment [a,b] when lambda ranges over [0,1] */ +static inline dpoint_t interval(double lambda, dpoint_t a, dpoint_t b) { + dpoint_t res; + + res.x = a.x + lambda * (b.x - a.x); + res.y = a.y + lambda * (b.y - a.y); + return res; +} + +/* ---------------------------------------------------------------------- */ +/* integer arithmetic */ + +static inline int mod(int a, int n) { + return a>=n ? a%n : a>=0 ? a : n-1-(-1-a)%n; +} + +static inline int floordiv(int a, int n) { + return a>=0 ? a/n : -1-(-1-a)/n; +} + +/* Note: the following work for integers and other numeric types. */ +#define sign(x) ((x)>0 ? 1 : (x)<0 ? -1 : 0) +#define abs(a) ((a)>0 ? (a) : -(a)) +#define min(a,b) ((a)<(b) ? (a) : (b)) +#define max(a,b) ((a)>(b) ? (a) : (b)) +#define sq(a) ((a)*(a)) +#define cu(a) ((a)*(a)*(a)) + +#endif /* AUXILIARY_H */ diff --git a/src/trace/potrace/bitmap.h b/src/trace/potrace/bitmap.h new file mode 100644 index 000000000..f55762f28 --- /dev/null +++ b/src/trace/potrace/bitmap.h @@ -0,0 +1,97 @@ +/* Copyright (C) 2001-2005 Peter Selinger. + This file is part of potrace. It is free software and it is covered + by the GNU General Public License. See the file COPYING for details. */ + +#ifndef BITMAP_H +#define BITMAP_H + +#include <string.h> +#include <stdlib.h> + +/* The bitmap type is defined in potracelib.h */ +#include "potracelib.h" + +/* The present file defines some convenient macros and static inline + functions for accessing bitmaps. Since they only produce inline + code, they can be conveniently shared by the library and frontends, + if desired */ + +/* ---------------------------------------------------------------------- */ +/* some measurements */ + +#define BM_WORDSIZE ((int)sizeof(potrace_word)) +#define BM_WORDBITS (8*BM_WORDSIZE) +#define BM_HIBIT (((potrace_word)1)<<(BM_WORDBITS-1)) +#define BM_ALLBITS (~(potrace_word)0) + +/* macros for accessing pixel at index (x,y). U* macros omit the + bounds check. */ + +#define bm_scanline(bm, y) ((bm)->map + (y)*(bm)->dy) +#define bm_index(bm, x, y) (&bm_scanline(bm, y)[(x)/BM_WORDBITS]) +#define bm_mask(x) (BM_HIBIT >> ((x) & (BM_WORDBITS-1))) +#define bm_range(x, a) ((int)(x) >= 0 && (int)(x) < (a)) +#define bm_safe(bm, x, y) (bm_range(x, (bm)->w) && bm_range(y, (bm)->h)) +#define BM_UGET(bm, x, y) ((*bm_index(bm, x, y) & bm_mask(x)) != 0) +#define BM_USET(bm, x, y) (*bm_index(bm, x, y) |= bm_mask(x)) +#define BM_UCLR(bm, x, y) (*bm_index(bm, x, y) &= ~bm_mask(x)) +#define BM_UINV(bm, x, y) (*bm_index(bm, x, y) ^= bm_mask(x)) +#define BM_UPUT(bm, x, y, b) ((b) ? BM_USET(bm, x, y) : BM_UCLR(bm, x, y)) +#define BM_GET(bm, x, y) (bm_safe(bm, x, y) ? BM_UGET(bm, x, y) : 0) +#define BM_SET(bm, x, y) (bm_safe(bm, x, y) ? BM_USET(bm, x, y) : 0) +#define BM_CLR(bm, x, y) (bm_safe(bm, x, y) ? BM_UCLR(bm, x, y) : 0) +#define BM_INV(bm, x, y) (bm_safe(bm, x, y) ? BM_UINV(bm, x, y) : 0) +#define BM_PUT(bm, x, y, b) (bm_safe(bm, x, y) ? BM_UPUT(bm, x, y, b) : 0) + +/* free the given bitmap. Leaves errno untouched. */ +static inline void bm_free(potrace_bitmap_t *bm) { + if (bm) { + free(bm->map); + } + free(bm); +} + +/* return new un-initialized bitmap. NULL with errno on error */ +static inline potrace_bitmap_t *bm_new(int w, int h) { + potrace_bitmap_t *bm; + int dy = (w + BM_WORDBITS - 1) / BM_WORDBITS; + + bm = (potrace_bitmap_t *) malloc(sizeof(potrace_bitmap_t)); + if (!bm) { + return NULL; + } + bm->w = w; + bm->h = h; + bm->dy = dy; + bm->map = (potrace_word *) malloc(dy * h * BM_WORDSIZE); + if (!bm->map) { + free(bm); + return NULL; + } + return bm; +} + +/* clear the given bitmap. Set all bits to c. */ +static inline void bm_clear(potrace_bitmap_t *bm, int c) { + memset(bm->map, c ? -1 : 0, bm->dy * bm->h * BM_WORDSIZE); +} + +/* duplicate the given bitmap. Return NULL on error with errno set. */ +static inline potrace_bitmap_t *bm_dup(const potrace_bitmap_t *bm) { + potrace_bitmap_t *bm1 = bm_new(bm->w, bm->h); + if (!bm1) { + return NULL; + } + memcpy(bm1->map, bm->map, bm->dy * bm->h * BM_WORDSIZE); + return bm1; +} + +/* invert the given bitmap. */ +static inline void bm_invert(potrace_bitmap_t *bm) { + int i; + for (i = 0; i < bm->dy * bm->h; i++) { + bm->map[i] ^= BM_ALLBITS; + } +} + +#endif /* BITMAP_H */ diff --git a/src/trace/potrace/curve.cpp b/src/trace/potrace/curve.cpp new file mode 100644 index 000000000..89b6ee5fd --- /dev/null +++ b/src/trace/potrace/curve.cpp @@ -0,0 +1,107 @@ +/* Copyright (C) 2001-2005 Peter Selinger. + This file is part of potrace. It is free software and it is covered + by the GNU General Public License. See the file COPYING for details. */ + +/* $Id$ */ +/* private part of the path and curve data structures */ + +#include <stdlib.h> +#include <string.h> + +#include "lists.h" +#include "curve.h" + +#define SAFE_MALLOC(var, n, typ) \ + if ((var = (typ *)malloc((n)*sizeof(typ))) == NULL) goto malloc_error + +/* ---------------------------------------------------------------------- */ +/* allocate and free path objects */ + +path_t *path_new(void) { + path_t *p = NULL; + privpath_t *priv = NULL; + + SAFE_MALLOC(p, 1, path_t); + memset(p, 0, sizeof(path_t)); + SAFE_MALLOC(priv, 1, privpath_t); + memset(priv, 0, sizeof(privpath_t)); + p->priv = priv; + return p; + + malloc_error: + free(p); + free(priv); + return NULL; +} + +/* free a path. Leave errno untouched. */ +void path_free(path_t *p) { + if (p) { + if (p->priv) { + free(p->priv->pt); + free(p->priv->lon); + free(p->priv->sums); + free(p->priv->po); + privcurve_free_members(&p->priv->curve); + privcurve_free_members(&p->priv->ocurve); + } + free(p->priv); + /* do not free p->fcurve ! */ + } + free(p); +} + +/* free a pathlist, leaving errno untouched. */ +void pathlist_free(path_t *plist) { + path_t *p; + + list_forall_unlink(p, plist) { + path_free(p); + } +} + +/* ---------------------------------------------------------------------- */ +/* initialize and finalize curve structures */ + +typedef dpoint_t dpoint3_t[3]; + +/* initialize the members of the given curve structure to size m. + Return 0 on success, 1 on error with errno set. */ +int privcurve_init(privcurve_t *curve, int n) { + memset(curve, 0, sizeof(privcurve_t)); + curve->n = n; + SAFE_MALLOC(curve->tag, n, int); + SAFE_MALLOC(curve->c, n, dpoint3_t); + SAFE_MALLOC(curve->vertex, n, dpoint_t); + SAFE_MALLOC(curve->alpha, n, double); + SAFE_MALLOC(curve->alpha0, n, double); + SAFE_MALLOC(curve->beta, n, double); + return 0; + + malloc_error: + free(curve->tag); + free(curve->c); + free(curve->vertex); + free(curve->alpha); + free(curve->alpha0); + free(curve->beta); + return 1; +} + +/* free the members of the given curve structure */ +void privcurve_free_members(privcurve_t *curve) { + free(curve->tag); + free(curve->c); + free(curve->vertex); + free(curve->alpha); + free(curve->alpha0); + free(curve->beta); +} + +/* copy private to public curve structure */ +void privcurve_to_curve(privcurve_t *pc, potrace_curve_t *c) { + c->n = pc->n; + c->tag = pc->tag; + c->c = pc->c; +} + diff --git a/src/trace/potrace/curve.h b/src/trace/potrace/curve.h new file mode 100644 index 000000000..9b08f2d37 --- /dev/null +++ b/src/trace/potrace/curve.h @@ -0,0 +1,74 @@ +/* Copyright (C) 2001-2005 Peter Selinger. + This file is part of potrace. It is free software and it is covered + by the GNU General Public License. See the file COPYING for details. */ + +#ifndef CURVE_H +#define CURVE_H + +#include "auxiliary.h" + +/* vertex is c[1] for tag=POTRACE_CORNER, and the intersection of + .c[-1][2]..c[0] and c[1]..c[2] for tag=POTRACE_CURVETO. alpha is only + defined for tag=POTRACE_CURVETO and is the alpha parameter of the curve: + .c[-1][2]..c[0] = alpha*(.c[-1][2]..vertex), and + c[2]..c[1] = alpha*(c[2]..vertex). + Beta is so that (.beta[i])[.vertex[i],.vertex[i+1]] = .c[i][2]. +*/ + +struct privcurve_s { + int n; /* number of segments */ + int *tag; /* tag[n]: POTRACE_CORNER or POTRACE_CURVETO */ + dpoint_t (*c)[3]; /* c[n][i]: control points. + c[n][0] is unused for tag[n]=POTRACE_CORNER */ + dpoint_t *vertex; /* for POTRACE_CORNER, this equals c[1] */ + double *alpha; /* only for POTRACE_CURVETO */ + double *alpha0; /* "uncropped" alpha parameter - for debug output only */ + double *beta; +}; +typedef struct privcurve_s privcurve_t; + +struct sums_s { + double x; + double y; + double x2; + double xy; + double y2; +}; +typedef struct sums_s sums_t; + +/* the path structure is filled in with information about a given path + as it is accumulated and passed through the different stages of the + potrace algorithm. Backends only need to read the fcurve and fm + fields of this data structure, but debugging backends may read + other fields. */ +struct potrace_privpath_s { + int len; + point_t *pt; /* pt[len]: path as extracted from bitmap */ + int *lon; /* lon[len]: (i,lon[i]) = longest straight line from i */ + + int x0, y0; /* origin for sums */ + sums_t *sums; /* sums[len+1]: cache for fast summing */ + + int m; /* length of optimal polygon */ + int *po; /* po[m]: optimal polygon */ + + privcurve_t curve; /* curve[m]: array of curve elements */ + privcurve_t ocurve; /* ocurve[om]: array of curve elements */ + privcurve_t *fcurve; /* final curve: this points to either curve or + ocurve. Do not free this separately. */ +}; +typedef struct potrace_privpath_s potrace_privpath_t; + +/* shorter names */ +typedef potrace_privpath_t privpath_t; +typedef potrace_path_t path_t; + +path_t *path_new(void); +void path_free(path_t *p); +void pathlist_free(path_t *plist); +int privcurve_init(privcurve_t *curve, int n); +void privcurve_free_members(privcurve_t *curve); +void privcurve_to_curve(privcurve_t *pc, potrace_curve_t *c); + +#endif /* CURVE_H */ + diff --git a/src/trace/potrace/decompose.cpp b/src/trace/potrace/decompose.cpp new file mode 100644 index 000000000..7c88068f0 --- /dev/null +++ b/src/trace/potrace/decompose.cpp @@ -0,0 +1,481 @@ +/* Copyright (C) 2001-2005 Peter Selinger. + This file is part of potrace. It is free software and it is covered + by the GNU General Public License. See the file COPYING for details. */ + +/* $Id$ */ + +#include <stdlib.h> +#include <limits.h> + +#include "lists.h" +#include "bitmap.h" +#include "decompose.h" + +/* ---------------------------------------------------------------------- */ +/* auxiliary bitmap manipulations */ + +/* set the excess padding to 0 */ +static void bm_clearexcess(potrace_bitmap_t *bm) { + potrace_word mask; + int y; + + if (bm->w % BM_WORDBITS != 0) { + mask = BM_ALLBITS << (BM_WORDBITS - (bm->w % BM_WORDBITS)); + for (y=0; y<bm->h; y++) { + *bm_index(bm, bm->w, y) &= mask; + } + } +} + +struct bbox_s { + int x0, x1, y0, y1; /* bounding box */ +}; +typedef struct bbox_s bbox_t; + +/* clear the bm, assuming the bounding box is set correctly (faster + than clearing the whole bitmap) */ +static void clear_bm_with_bbox(potrace_bitmap_t *bm, bbox_t *bbox) { + int imin = (bbox->x0 / BM_WORDBITS); + int imax = ((bbox->x1 + BM_WORDBITS-1) / BM_WORDBITS); + int i, y; + + for (y=bbox->y0; y<bbox->y1; y++) { + for (i=imin; i<imax; i++) { + bm_scanline(bm, y)[i] = 0; + } + } +} + +/* ---------------------------------------------------------------------- */ +/* auxiliary functions */ + +/* return a "random" value which deterministically depends on x,y */ +static inline int detrand(int x, int y) { + srand(x+123456789*y); + return rand(); +} + +/* return the "majority" value of bitmap bm at intersection (x,y). We + assume that the bitmap is balanced at "radius" 1. */ +static int majority(potrace_bitmap_t *bm, int x, int y) { + int i, a, ct; + + for (i=2; i<5; i++) { /* check at "radius" i */ + ct = 0; + for (a=-i+1; a<=i-1; a++) { + ct += BM_GET(bm, x+a, y+i-1) ? 1 : -1; + ct += BM_GET(bm, x+i-1, y+a-1) ? 1 : -1; + ct += BM_GET(bm, x+a-1, y-i) ? 1 : -1; + ct += BM_GET(bm, x-i, y+a) ? 1 : -1; + } + if (ct>0) { + return 1; + } else if (ct<0) { + return 0; + } + } + return 0; +} + +/* ---------------------------------------------------------------------- */ +/* decompose image into paths */ + +/* efficiently invert bits [x,infty) and [xa,infty) in line y. Here xa + must be a multiple of BM_WORDBITS. */ +static void xor_to_ref(potrace_bitmap_t *bm, int x, int y, int xa) { + int xhi = x & -BM_WORDBITS; + int xlo = x & (BM_WORDBITS-1); /* = x % BM_WORDBITS */ + int i; + + if (xhi<xa) { + for (i = xhi; i < xa; i+=BM_WORDBITS) { + *bm_index(bm, i, y) ^= BM_ALLBITS; + } + } else { + for (i = xa; i < xhi; i+=BM_WORDBITS) { + *bm_index(bm, i, y) ^= BM_ALLBITS; + } + } + /* note: the following "if" is needed because x86 treats a<<b as + a<<(b&31). I spent hours looking for this bug. */ + if (xlo) { + *bm_index(bm, xhi, y) ^= (BM_ALLBITS << (BM_WORDBITS - xlo)); + } +} + +/* a path is represented as an array of points, which are thought to + lie on the corners of pixels (not on their centers). The path point + (x,y) is the lower left corner of the pixel (x,y). Paths are + represented by the len/pt components of a path_t object (which + also stores other information about the path) */ + +/* xor the given pixmap with the interior of the given path. Note: the + path must be within the dimensions of the pixmap. */ +static void xor_path(potrace_bitmap_t *bm, path_t *p) { + int xa, x, y, k, y1; + + if (p->priv->len <= 0) { /* a path of length 0 is silly, but legal */ + return; + } + + y1 = p->priv->pt[p->priv->len-1].y; + + xa = p->priv->pt[0].x & -BM_WORDBITS; + for (k=0; k<p->priv->len; k++) { + x = p->priv->pt[k].x; + y = p->priv->pt[k].y; + + if (y != y1) { + /* efficiently invert the rectangle [x,xa] x [y,y1] */ + xor_to_ref(bm, x, min(y,y1), xa); + y1 = y; + } + } +} + +/* Find the bounding box of a given path. Path is assumed to be of + non-zero length. */ +static void setbbox_path(bbox_t *bbox, path_t *p) { + int x, y; + int k; + + bbox->y0 = INT_MAX; + bbox->y1 = 0; + bbox->x0 = INT_MAX; + bbox->x1 = 0; + + for (k=0; k<p->priv->len; k++) { + x = p->priv->pt[k].x; + y = p->priv->pt[k].y; + + if (x < bbox->x0) { + bbox->x0 = x; + } + if (x > bbox->x1) { + bbox->x1 = x; + } + if (y < bbox->y0) { + bbox->y0 = y; + } + if (y > bbox->y1) { + bbox->y1 = y; + } + } +} + +/* compute a path in the given pixmap, separating black from white. + Start path at the point (x0,x1), which must be an upper left corner + of the path. Also compute the area enclosed by the path. Return a + new path_t object, or NULL on error (note that a legitimate path + cannot have length 0). Sign is required for correct interpretation + of turnpolicies. */ +static path_t *findpath(potrace_bitmap_t *bm, int x0, int y0, int sign, int turnpolicy) { + int x, y, dirx, diry, len, size, area; + int c, d, tmp; + point_t *pt, *pt1; + path_t *p = NULL; + + x = x0; + y = y0; + dirx = 0; + diry = -1; + + len = size = 0; + pt = NULL; + area = 0; + + while (1) { + /* add point to path */ + if (len>=size) { + size+=100; + pt1 = (point_t *)realloc(pt, size * sizeof(point_t)); + if (!pt1) { + goto error; + } + pt = pt1; + } + pt[len].x = x; + pt[len].y = y; + len++; + + /* move to next point */ + x += dirx; + y += diry; + area += x*diry; + + /* path complete? */ + if (x==x0 && y==y0) { + break; + } + + /* determine next direction */ + c = BM_GET(bm, x + (dirx+diry-1)/2, y + (diry-dirx-1)/2); + d = BM_GET(bm, x + (dirx-diry-1)/2, y + (diry+dirx-1)/2); + + if (c && !d) { /* ambiguous turn */ + if (turnpolicy == POTRACE_TURNPOLICY_RIGHT + || (turnpolicy == POTRACE_TURNPOLICY_BLACK && sign == '+') + || (turnpolicy == POTRACE_TURNPOLICY_WHITE && sign == '-') + || (turnpolicy == POTRACE_TURNPOLICY_RANDOM && (detrand(x,y) & 1)) + || (turnpolicy == POTRACE_TURNPOLICY_MAJORITY && majority(bm, x, y)) + || (turnpolicy == POTRACE_TURNPOLICY_MINORITY && !majority(bm, x, y))) { + tmp = dirx; /* right turn */ + dirx = diry; + diry = -tmp; + } else { + tmp = dirx; /* left turn */ + dirx = -diry; + diry = tmp; + } + } else if (c) { /* right turn */ + tmp = dirx; + dirx = diry; + diry = -tmp; + } else if (!d) { /* left turn */ + tmp = dirx; + dirx = -diry; + diry = tmp; + } + } /* while this path */ + + /* allocate new path object */ + p = path_new(); + if (!p) { + goto error; + } + + p->priv->pt = pt; + p->priv->len = len; + p->area = area; + p->sign = sign; + + return p; + + error: + free(pt); + return NULL; +} + +/* Give a tree structure to the given path list, based on "insideness" + testing. I.e., path A is considered "below" path B if it is inside + path B. The input pathlist is assumed to be ordered so that "outer" + paths occur before "inner" paths. The tree structure is stored in + the "childlist" and "sibling" components of the path_t + structure. The linked list structure is also changed so that + negative path components are listed immediately after their + positive parent. Note: some backends may ignore the tree + structure, others may use it e.g. to group path components. We + assume that in the input, point 0 of each path is an "upper left" + corner of the path, as returned by bm_to_pathlist. This makes it + easy to find an "interior" point. The bm argument should be a + bitmap of the correct size (large enough to hold all the paths), + and will be used as scratch space. Return 0 on success or -1 on + error with errno set. */ + +static void pathlist_to_tree(path_t *plist, potrace_bitmap_t *bm) { + path_t *p, *p1; + path_t *heap, *heap1; + path_t *cur; + path_t *head; + path_t **hook, **hook_in, **hook_out; /* for fast appending to linked list */ + bbox_t bbox; + + bm_clear(bm, 0); + + /* save original "next" pointers */ + list_forall(p, plist) { + p->sibling = p->next; + p->childlist = NULL; + } + + heap = plist; + + /* the heap holds a list of lists of paths. Use "childlist" field + for outer list, "next" field for inner list. Each of the sublists + is to be turned into a tree. This code is messy, but it is + actually fast. Each path is rendered exactly once. We use the + heap to get a tail recursive algorithm: the heap holds a list of + pathlists which still need to be transformed. */ + + while (heap) { + /* unlink first sublist */ + cur = heap; + heap = heap->childlist; + cur->childlist = NULL; + + /* unlink first path */ + head = cur; + cur = cur->next; + head->next = NULL; + + /* render path */ + xor_path(bm, head); + setbbox_path(&bbox, head); + + /* now do insideness test for each element of cur; append it to + head->childlist if it's inside head, else append it to + head->next. */ + hook_in=&head->childlist; + hook_out=&head->next; + list_forall_unlink(p, cur) { + if (p->priv->pt[0].y <= bbox.y0) { + list_insert_beforehook(p, hook_out); + /* append the remainder of the list to hook_out */ + *hook_out = cur; + break; + } + if (BM_GET(bm, p->priv->pt[0].x, p->priv->pt[0].y-1)) { + list_insert_beforehook(p, hook_in); + } else { + list_insert_beforehook(p, hook_out); + } + } + + /* clear bm */ + clear_bm_with_bbox(bm, &bbox); + + /* now schedule head->childlist and head->next for further + processing */ + if (head->next) { + head->next->childlist = heap; + heap = head->next; + } + if (head->childlist) { + head->childlist->childlist = heap; + heap = head->childlist; + } + } + + /* copy sibling structure from "next" to "sibling" component */ + p = plist; + while (p) { + p1 = p->sibling; + p->sibling = p->next; + p = p1; + } + + /* reconstruct a new linked list ("next") structure from tree + ("childlist", "sibling") structure. This code is slightly messy, + because we use a heap to make it tail recursive: the heap + contains a list of childlists which still need to be + processed. */ + heap = plist; + if (heap) { + heap->next = NULL; /* heap is a linked list of childlists */ + } + plist = NULL; + hook = &plist; + while (heap) { + heap1 = heap->next; + for (p=heap; p; p=p->sibling) { + /* p is a positive path */ + /* append to linked list */ + list_insert_beforehook(p, hook); + + /* go through its children */ + for (p1=p->childlist; p1; p1=p1->sibling) { + /* append to linked list */ + list_insert_beforehook(p1, hook); + /* append its childlist to heap, if non-empty */ + if (p1->childlist) { + list_append(path_t, heap1, p1->childlist); + } + } + } + heap = heap1; + } + + return; +} + +/* find the next set pixel in a row <= y. Pixels are searched first + left-to-right, then top-down. In other words, (x,y)<(x',y') if y>y' + or y=y' and x<x'. If found, return 0 and store pixel in + (*xp,*yp). Else return 1. Note that this function assumes that + excess bytes have been cleared with bm_clearexcess. */ +static int findnext(potrace_bitmap_t *bm, int *xp, int *yp) { + int x; + int y; + + for (y=*yp; y>=0; y--) { + for (x=0; x<bm->w; x+=BM_WORDBITS) { + if (*bm_index(bm, x, y)) { + while (!BM_GET(bm, x, y)) { + x++; + } + /* found */ + *xp = x; + *yp = y; + return 0; + } + } + } + /* not found */ + return 1; +} + +/* Decompose the given bitmap into paths. Returns a linked list of + path_t objects with the fields len, pt, area, sign filled + in. Returns 0 on success with plistp set, or -1 on error with errno + set. */ + +int bm_to_pathlist(const potrace_bitmap_t *bm, path_t **plistp, const potrace_param_t *param, progress_t *progress) { + int x; + int y; + path_t *p; + path_t *plist = NULL; /* linked list of path objects */ + path_t **hook = &plist; /* used to speed up appending to linked list */ + potrace_bitmap_t *bm1 = NULL; + int sign; + + bm1 = bm_dup(bm); + if (!bm1) { + goto error; + } + + /* be sure the byte padding on the right is set to 0, as the fast + pixel search below relies on it */ + bm_clearexcess(bm1); + + /* iterate through components */ + y = bm1->h - 1; + while (findnext(bm1, &x, &y) == 0) { + /* calculate the sign by looking at the original */ + sign = BM_GET(bm, x, y) ? '+' : '-'; + + /* calculate the path */ + p = findpath(bm1, x, y+1, sign, param->turnpolicy); + if (p==NULL) { + goto error; + } + + /* update buffered image */ + xor_path(bm1, p); + + /* if it's a turd, eliminate it, else append it to the list */ + if (p->area <= param->turdsize) { + path_free(p); + } else { + list_insert_beforehook(p, hook); + } + + if (bm1->h > 0) { /* to be sure */ + progress_update(1-y/(double)bm1->h, progress); + } + } + + pathlist_to_tree(plist, bm1); + bm_free(bm1); + *plistp = plist; + + progress_update(1.0, progress); + + return 0; + + error: + bm_free(bm1); + list_forall_unlink(p, plist) { + path_free(p); + } + return -1; +} diff --git a/src/trace/potrace/decompose.h b/src/trace/potrace/decompose.h new file mode 100644 index 000000000..346b7866a --- /dev/null +++ b/src/trace/potrace/decompose.h @@ -0,0 +1,17 @@ +/* Copyright (C) 2001-2005 Peter Selinger. + This file is part of potrace. It is free software and it is covered + by the GNU General Public License. See the file COPYING for details. */ + +/* $Id$ */ + +#ifndef DECOMPOSE_H +#define DECOMPOSE_H + +#include "potracelib.h" +#include "curve.h" +#include "progress.h" + +int bm_to_pathlist(const potrace_bitmap_t *bm, path_t **plistp, const potrace_param_t *param, progress_t *progress); + +#endif /* DECOMPOSE_H */ + diff --git a/src/trace/potrace/greymap.cpp b/src/trace/potrace/greymap.cpp new file mode 100644 index 000000000..7d46304cd --- /dev/null +++ b/src/trace/potrace/greymap.cpp @@ -0,0 +1,889 @@ +/* Copyright (C) 2001-2005 Peter Selinger. + This file is part of potrace. It is free software and it is covered + by the GNU General Public License. See the file COPYING for details. */ + +/* $Id$ */ + +/* Routines for manipulating greymaps, including reading pgm files. We + only deal with greymaps of depth 8 bits. */ + +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include <math.h> + +#include "greymap.h" + +#define INTBITS (8*sizeof(int)) + +#define mod(a,n) ((a)>=(n) ? (a)%(n) : (a)>=0 ? (a) : (n)-1-(-1-(a))%(n)) + +static int gm_readbody_pnm(FILE *f, greymap_t **gmp, int magic); +static int gm_readbody_bmp(FILE *f, greymap_t **gmp); + +/* ---------------------------------------------------------------------- */ +/* basic greymap routines */ + +/* return new un-initialized greymap. NULL with errno on error */ + +greymap_t *gm_new(int w, int h) { + greymap_t *gm; + int errno_save; + + gm = (greymap_t *) malloc(sizeof(greymap_t)); + if (!gm) { + return NULL; + } + gm->w = w; + gm->h = h; + gm->map = (signed short int *) malloc(w*h*sizeof(signed short int)); + if (!gm->map) { + errno_save = errno; + free(gm); + errno = errno_save; + return NULL; + } + return gm; +} + +/* free the given greymap */ +void gm_free(greymap_t *gm) { + if (gm) { + free(gm->map); + } + free(gm); +} + +/* duplicate the given greymap. Return NULL on error with errno set. */ +greymap_t *gm_dup(greymap_t *gm) { + greymap_t *gm1 = gm_new(gm->w, gm->h); + if (!gm1) { + return NULL; + } + memcpy(gm1->map, gm->map, gm->w*gm->h*2); + return gm1; +} + +/* clear the given greymap to color b. */ +void gm_clear(greymap_t *gm, int b) { + int i; + + if (b==0) { + memset(gm->map, 0, gm->w*gm->h*2); + } else { + for (i=0; i<gm->w*gm->h; i++) { + gm->map[i] = b; + } + } +} + +/* ---------------------------------------------------------------------- */ +/* routines for reading pnm streams */ + +/* read next character after whitespace and comments. Return EOF on + end of file or error. */ +static int fgetc_ws(FILE *f) { + int c; + + while (1) { + c = fgetc(f); + if (c=='#') { + while (1) { + c = fgetc(f); + if (c=='\n' || c==EOF) { + break; + } + } + } + /* space, tab, line feed, carriage return, form-feed */ + if (c!=' ' && c!='\t' && c!='\r' && c!='\n' && c!=12) { + return c; + } + } +} + +/* skip whitespace and comments, then read a non-negative decimal + number from a stream. Return -1 on EOF. Tolerate other errors (skip + bad characters). Do not the read any characters following the + number (put next character back into the stream) */ + +static int readnum(FILE *f) { + int c; + int acc; + + /* skip whitespace and comments */ + while (1) { + c = fgetc_ws(f); + if (c==EOF) { + return -1; + } + if (c>='0' && c<='9') { + break; + } + } + + /* first digit is already in c */ + acc = c-'0'; + while (1) { + c = fgetc(f); + if (c==EOF) { + break; + } + if (c<'0' || c>'9') { + ungetc(c, f); + break; + } + acc *= 10; + acc += c-'0'; + } + return acc; +} + +/* similar to readnum, but read only a single 0 or 1, and do not read + any characters after it. */ + +static int readbit(FILE *f) { + int c; + + /* skip whitespace and comments */ + while (1) { + c = fgetc_ws(f); + if (c==EOF) { + return -1; + } + if (c>='0' && c<='1') { + break; + } + } + + return c-'0'; +} + +/* ---------------------------------------------------------------------- */ + +/* read a PNM stream: P1-P6 format (see pnm(5)), or a BMP stream, and + convert the output to a greymap. Return greymap in *gmp. Return 0 + on success, -1 on error with errno set, -2 on bad file format (with + error message in gm_read_error), and 1 on premature end of file, -3 + on empty file (including files with only whitespace and comments), + -4 if wrong magic number. If the return value is >=0, *gmp is + valid. */ + +char *gm_read_error = NULL; + +int gm_read(FILE *f, greymap_t **gmp) { + int magic[2]; + + /* read magic number. We ignore whitespace and comments before the + magic, for the benefit of concatenated files in P1-P3 format. + Multiple P1-P3 images in a single file are not formally allowed + by the PNM standard, but there is no harm in being lenient. */ + + magic[0] = fgetc_ws(f); + if (magic[0] == EOF) { + /* files which contain only comments and whitespace count as "empty" */ + return -3; + } + magic[1] = fgetc(f); + if (magic[0] == 'P' && magic[1] >= '1' && magic[1] <= '6') { + return gm_readbody_pnm(f, gmp, magic[1]); + } + if (magic[0] == 'B' && magic[1] == 'M') { + return gm_readbody_bmp(f, gmp); + } + return -4; +} + +/* ---------------------------------------------------------------------- */ +/* read PNM format */ + +/* read PNM stream after magic number. Return values as for gm_read */ +static int gm_readbody_pnm(FILE *f, greymap_t **gmp, int magic) { + greymap_t *gm; + int x, y, i, j, b, b1, sum; + int bpr; /* bytes per row (as opposed to 4*gm->c) */ + int w, h, max; + + gm = NULL; + + w = readnum(f); + if (w<0) { + goto format_error; + } + + h = readnum(f); + if (h<0) { + goto format_error; + } + + /* allocate greymap */ + gm = gm_new(w, h); + if (!gm) { + return -1; + } + + /* zero it out */ + gm_clear(gm, 0); + + switch (magic) { + default: + /* not reached */ + goto format_error; + + case '1': + /* read P1 format: PBM ascii */ + + for (y=h-1; y>=0; y--) { + for (x=0; x<w; x++) { + b = readbit(f); + if (b<0) { + goto eof; + } + GM_UPUT(gm, x, y, b ? 0 : 255); + } + } + break; + + case '2': + /* read P2 format: PGM ascii */ + + max = readnum(f); + if (max<1) { + goto format_error; + } + + for (y=h-1; y>=0; y--) { + for (x=0; x<w; x++) { + b = readnum(f); + if (b<0) { + goto eof; + } + GM_UPUT(gm, x, y, b*255/max); + } + } + break; + + case '3': + /* read P3 format: PPM ascii */ + + max = readnum(f); + if (max<1) { + goto format_error; + } + + for (y=h-1; y>=0; y--) { + for (x=0; x<w; x++) { + sum = 0; + for (i=0; i<3; i++) { + b = readnum(f); + if (b<0) { + goto eof; + } + sum += b; + } + GM_UPUT(gm, x, y, sum*(255/3)/max); + } + } + break; + + case '4': + /* read P4 format: PBM raw */ + + b = fgetc(f); /* read single white-space character after height */ + if (b==EOF) { + goto format_error; + } + + bpr = (w+7)/8; + + for (y=h-1; y>=0; y--) { + for (i=0; i<bpr; i++) { + b = fgetc(f); + if (b==EOF) { + goto eof; + } + for (j=0; j<8; j++) { + GM_PUT(gm, i*8+j, y, b & (0x80 >> j) ? 0 : 255); + } + } + } + break; + + case '5': + /* read P5 format: PGM raw */ + + max = readnum(f); + if (max<1) { + goto format_error; + } + + b = fgetc(f); /* read single white-space character after max */ + if (b==EOF) { + goto format_error; + } + + for (y=h-1; y>=0; y--) { + for (x=0; x<w; x++) { + b = fgetc(f); + if (b==EOF) + goto eof; + if (max>=256) { + b <<= 8; + b1 = fgetc(f); + if (b1==EOF) + goto eof; + b |= b1; + } + GM_UPUT(gm, x, y, b*255/max); + } + } + break; + + case '6': + /* read P6 format: PPM raw */ + + max = readnum(f); + if (max<1) { + goto format_error; + } + + b = fgetc(f); /* read single white-space character after max */ + if (b==EOF) { + goto format_error; + } + + for (y=h-1; y>=0; y--) { + for (x=0; x<w; x++) { + sum = 0; + for (i=0; i<3; i++) { + b = fgetc(f); + if (b==EOF) { + goto eof; + } + if (max>=256) { + b <<= 8; + b1 = fgetc(f); + if (b1==EOF) + goto eof; + b |= b1; + } + sum += b; + } + GM_UPUT(gm, x, y, sum*(255/3)/max); + } + } + break; + } + + *gmp = gm; + return 0; + + eof: + *gmp = gm; + return 1; + + format_error: + gm_free(gm); + if (magic == '1' || magic == '4') { + gm_read_error = "invalid pbm file"; + } else if (magic == '2' || magic == '5') { + gm_read_error = "invalid pgm file"; + } else { + gm_read_error = "invalid ppm file"; + } + return -2; +} + +/* ---------------------------------------------------------------------- */ +/* read BMP format */ + +struct bmp_info_s { + unsigned int FileSize; + unsigned int reserved; + unsigned int DataOffset; + unsigned int InfoSize; + unsigned int w; /* width */ + unsigned int h; /* height */ + unsigned int Planes; + unsigned int bits; /* bits per sample */ + unsigned int comp; /* compression mode */ + unsigned int ImageSize; + unsigned int XpixelsPerM; + unsigned int YpixelsPerM; + unsigned int ncolors; /* number of colors in palette */ + unsigned int ColorsImportant; + unsigned int ctbits; /* sample size for color table */ +}; +typedef struct bmp_info_s bmp_info_t; + +/* auxiliary */ + +static int bmp_count = 0; /* counter for byte padding */ +static int bmp_pos = 0; /* counter from start of BMP data */ + +/* read n-byte little-endian integer. Return 1 on EOF or error, else + 0. Assume n<=4. */ +static int bmp_readint(FILE *f, int n, unsigned int *p) { + int i; + unsigned int sum = 0; + int b; + + for (i=0; i<n; i++) { + b = fgetc(f); + if (b==EOF) { + return 1; + } + sum += b << (8*i); + } + bmp_count += n; + bmp_pos += n; + *p = sum; + return 0; +} + +/* reset padding boundary */ +static void bmp_pad_reset() { + bmp_count = 0; +} + +/* read padding bytes to 4-byte boundary. Return 1 on EOF or error, + else 0. */ +static int bmp_pad(FILE *f) { + int c, i, b; + + c = (-bmp_count) & 3; + for (i=0; i<c; i++) { + b = fgetc(f); + if (b==EOF) { + return 1; + } + } + bmp_pos += c; + bmp_count = 0; + return 0; +} + +/* forward to the new file position. Return 1 on EOF or error, else 0 */ +static int bmp_forward(FILE *f, int pos) { + int b; + + while (bmp_pos < pos) { + b = fgetc(f); + if (b==EOF) { + return 1; + } + bmp_pos++; + bmp_count++; + } + return 0; +} + +#define TRY(x) if (x) goto try_error +#define TRY_EOF(x) if (x) goto eof + +/* read BMP stream after magic number. Return values as for gm_read. + We choose to be as permissive as possible, since there are many + programs out there which produce BMP. For instance, ppmtobmp can + produce codings with anywhere from 1-8 or 24 bits per sample, + although most specifications only allow 1,4,8,24,32. We can also + read both the old and new OS/2 BMP formats in addition to the + Windows BMP format. */ +int gm_readbody_bmp(FILE *f, greymap_t **gmp) { + bmp_info_t bmpinfo; + int *coltable; + unsigned int b, c; + unsigned int i, j; + greymap_t *gm; + unsigned int x, y; + int col[2]; + unsigned int bitbuf; + unsigned int n; + + gm_read_error = NULL; + gm = NULL; + coltable = NULL; + + bmp_pos = 2; /* set file position */ + + /* file header (minus magic number) */ + TRY(bmp_readint(f, 4, &bmpinfo.FileSize)); + TRY(bmp_readint(f, 4, &bmpinfo.reserved)); + TRY(bmp_readint(f, 4, &bmpinfo.DataOffset)); + + /* info header */ + TRY(bmp_readint(f, 4, &bmpinfo.InfoSize)); + if (bmpinfo.InfoSize == 40 || bmpinfo.InfoSize == 64) { + /* Windows or new OS/2 format */ + bmpinfo.ctbits = 32; /* sample size in color table */ + TRY(bmp_readint(f, 4, &bmpinfo.w)); + TRY(bmp_readint(f, 4, &bmpinfo.h)); + TRY(bmp_readint(f, 2, &bmpinfo.Planes)); + TRY(bmp_readint(f, 2, &bmpinfo.bits)); + TRY(bmp_readint(f, 4, &bmpinfo.comp)); + TRY(bmp_readint(f, 4, &bmpinfo.ImageSize)); + TRY(bmp_readint(f, 4, &bmpinfo.XpixelsPerM)); + TRY(bmp_readint(f, 4, &bmpinfo.YpixelsPerM)); + TRY(bmp_readint(f, 4, &bmpinfo.ncolors)); + TRY(bmp_readint(f, 4, &bmpinfo.ColorsImportant)); + } else if (bmpinfo.InfoSize == 12) { + /* old OS/2 format */ + bmpinfo.ctbits = 24; /* sample size in color table */ + TRY(bmp_readint(f, 2, &bmpinfo.w)); + TRY(bmp_readint(f, 2, &bmpinfo.h)); + TRY(bmp_readint(f, 2, &bmpinfo.Planes)); + TRY(bmp_readint(f, 2, &bmpinfo.bits)); + bmpinfo.comp = 0; + bmpinfo.ncolors = 0; + } else { + goto format_error; + } + + /* forward to color table (i.e., if bmpinfo.InfoSize == 64) */ + TRY(bmp_forward(f, 14+bmpinfo.InfoSize)); + + if (bmpinfo.Planes != 1) { + gm_read_error = "cannot handle bmp planes"; + goto format_error; /* can't handle planes */ + } + + if (bmpinfo.ncolors == 0) { + bmpinfo.ncolors = 1 << bmpinfo.bits; + } + + /* color table, present only if bmpinfo.bits <= 8. */ + if (bmpinfo.bits <= 8) { + coltable = (int *) malloc(bmpinfo.ncolors * sizeof(int)); + if (!coltable) { + goto std_error; + } + /* NOTE: since we are reading a greymap, we can immediately convert + the color table entries to grey values. */ + for (i=0; i<bmpinfo.ncolors; i++) { + TRY(bmp_readint(f, bmpinfo.ctbits/8, &c)); + c = ((c>>16) & 0xff) + ((c>>8) & 0xff) + (c & 0xff); + coltable[i] = c/3; + } + } + + /* forward to data */ + if (bmpinfo.InfoSize != 12) { /* not old OS/2 format */ + TRY(bmp_forward(f, bmpinfo.DataOffset)); + } + + /* allocate greymap */ + gm = gm_new(bmpinfo.w, bmpinfo.h); + if (!gm) { + goto std_error; + } + + /* zero it out */ + gm_clear(gm, 0); + + switch (bmpinfo.bits + 0x100*bmpinfo.comp) { + + default: + goto format_error; + break; + + case 0x001: /* monochrome palette */ + + /* raster data */ + for (y=0; y<bmpinfo.h; y++) { + bmp_pad_reset(); + for (i=0; 8*i<bmpinfo.w; i++) { + TRY_EOF(bmp_readint(f, 1, &b)); + for (j=0; j<8; j++) { + GM_PUT(gm, i*8+j, y, b & (0x80 >> j) ? coltable[1] : coltable[0]); + } + } + TRY(bmp_pad(f)); + } + break; + + case 0x002: /* 2-bit to 8-bit palettes */ + case 0x003: + case 0x004: + case 0x005: + case 0x006: + case 0x007: + case 0x008: + for (y=0; y<bmpinfo.h; y++) { + bmp_pad_reset(); + bitbuf = 0; /* bit buffer: bits in buffer are high-aligned */ + n = 0; /* number of bits currently in bitbuffer */ + for (x=0; x<bmpinfo.w; x++) { + if (n < bmpinfo.bits) { + TRY_EOF(bmp_readint(f, 1, &b)); + bitbuf |= b << (INTBITS - 8 - n); + n += 8; + } + b = bitbuf >> (INTBITS - bmpinfo.bits); + bitbuf <<= bmpinfo.bits; + n -= bmpinfo.bits; + GM_UPUT(gm, x, y, coltable[b]); + } + TRY(bmp_pad(f)); + } + break; + + case 0x010: /* 16-bit encoding */ + /* can't do this format because it is not well-documented and I + don't have any samples */ + gm_read_error = "cannot handle bmp 16-bit coding"; + goto format_error; + break; + + case 0x018: /* 24-bit encoding */ + case 0x020: /* 32-bit encoding */ + for (y=0; y<bmpinfo.h; y++) { + bmp_pad_reset(); + for (x=0; x<bmpinfo.w; x++) { + TRY_EOF(bmp_readint(f, bmpinfo.bits/8, &c)); + c = ((c>>16) & 0xff) + ((c>>8) & 0xff) + (c & 0xff); + GM_UPUT(gm, x, y, c/3); + } + TRY(bmp_pad(f)); + } + break; + + case 0x204: /* 4-bit runlength compressed encoding (RLE4) */ + x = 0; + y = 0; + while (1) { + TRY_EOF(bmp_readint(f, 1, &b)); /* opcode */ + TRY_EOF(bmp_readint(f, 1, &c)); /* argument */ + if (b>0) { + /* repeat count */ + col[0] = coltable[(c>>4) & 0xf]; + col[1] = coltable[c & 0xf]; + for (i=0; i<b && x<bmpinfo.w; i++) { + if (x>=bmpinfo.w) { + x=0; + y++; + } + if (y>=bmpinfo.h) { + break; + } + GM_UPUT(gm, x, y, col[i&1]); + x++; + } + } else if (c == 0) { + /* end of line */ + y++; + x = 0; + } else if (c == 1) { + /* end of greymap */ + break; + } else if (c == 2) { + /* "delta": skip pixels in x and y directions */ + TRY_EOF(bmp_readint(f, 1, &b)); /* x offset */ + TRY_EOF(bmp_readint(f, 1, &c)); /* y offset */ + x += b; + y += c; + } else { + /* verbatim segment */ + for (i=0; i<c; i++) { + if ((i&1)==0) { + TRY_EOF(bmp_readint(f, 1, &b)); + } + if (x>=bmpinfo.w) { + x=0; + y++; + } + if (y>=bmpinfo.h) { + break; + } + GM_PUT(gm, x, y, coltable[(b>>(4-4*(i&1))) & 0xf]); + x++; + } + if ((c+1) & 2) { + /* pad to 16-bit boundary */ + TRY_EOF(bmp_readint(f, 1, &b)); + } + } + } + break; + + case 0x108: /* 8-bit runlength compressed encoding (RLE8) */ + x = 0; + y = 0; + while (1) { + TRY_EOF(bmp_readint(f, 1, &b)); /* opcode */ + TRY_EOF(bmp_readint(f, 1, &c)); /* argument */ + if (b>0) { + /* repeat count */ + for (i=0; i<b; i++) { + if (x>=bmpinfo.w) { + x=0; + y++; + } + if (y>=bmpinfo.h) { + break; + } + GM_UPUT(gm, x, y, coltable[c]); + x++; + } + } else if (c == 0) { + /* end of line */ + y++; + x = 0; + } else if (c == 1) { + /* end of greymap */ + break; + } else if (c == 2) { + /* "delta": skip pixels in x and y directions */ + TRY_EOF(bmp_readint(f, 1, &b)); /* x offset */ + TRY_EOF(bmp_readint(f, 1, &c)); /* y offset */ + x += b; + y += c; + } else { + /* verbatim segment */ + for (i=0; i<c; i++) { + TRY_EOF(bmp_readint(f, 1, &b)); + if (x>=bmpinfo.w) { + x=0; + y++; + } + if (y>=bmpinfo.h) { + break; + } + GM_PUT(gm, x, y, coltable[b]); + x++; + } + if (c & 1) { + /* pad input to 16-bit boundary */ + TRY_EOF(bmp_readint(f, 1, &b)); + } + } + } + break; + + } /* switch */ + + /* skip any potential junk after the data section, but don't + complain in case EOF is encountered */ + bmp_forward(f, bmpinfo.FileSize); + + free(coltable); + *gmp = gm; + return 0; + + eof: + free(coltable); + *gmp = gm; + return 1; + + format_error: + try_error: + free(coltable); + free(gm); + if (!gm_read_error) { + gm_read_error = "invalid bmp file"; + } + return -2; + + std_error: + free(coltable); + free(gm); + return -1; +} + +/* ---------------------------------------------------------------------- */ + +/* write a pgm stream, either P2 or (if raw != 0) P5 format. Include + one-line comment if non-NULL. Mode determines how out-of-range + color values are converted. Gamma is the desired gamma correction, + if any (set to 2.2 if the image is to look optimal on a CRT monitor, + 2.8 for LCD). Set to 1.0 for no gamma correction */ + +int gm_writepgm(FILE *f, greymap_t *gm, char *comment, int raw, int mode, double gamma) { + int x, y, v; + int gammatable[256]; + + /* prepare gamma correction lookup table */ + if (gamma != 1.0) { + gammatable[0] = 0; + for (v=1; v<256; v++) { + gammatable[v] = (int)(255 * exp(log(v/255.0)/gamma) + 0.5); + } + } else { + for (v=0; v<256; v++) { + gammatable[v] = v; + } + } + + fprintf(f, raw ? "P5\n" : "P2\n"); + if (comment && *comment) { + fprintf(f, "# %s\n", comment); + } + fprintf(f, "%d %d 255\n", gm->w, gm->h); + for (y=gm->h-1; y>=0; y--) { + for (x=0; x<gm->w; x++) { + v = GM_UGET(gm, x, y); + if (mode == GM_MODE_NONZERO) { + if (v > 255) { + v = 510 - v; + } + if (v < 0) { + v = 0; + } + } else if (mode == GM_MODE_ODD) { + v = mod(v, 510); + if (v > 255) { + v = 510 - v; + } + } else if (mode == GM_MODE_POSITIVE) { + if (v < 0) { + v = 0; + } else if (v > 255) { + v = 255; + } + } else if (mode == GM_MODE_NEGATIVE) { + v = 510 - v; + if (v < 0) { + v = 0; + } else if (v > 255) { + v = 255; + } + } + v = gammatable[v]; + + if (raw) { + fputc(v, f); + } else { + fprintf(f, x == gm->w-1 ? "%d\n" : "%d ", v); + } + } + } + return 0; +} + +/* ---------------------------------------------------------------------- */ +/* output - for primitive debugging purposes only! */ + +/* print greymap to screen */ +int gm_print(FILE *f, greymap_t *gm) { + int x, y; + int xx, yy; + int d, t; + int sw, sh; + + sw = gm->w < 79 ? gm->w : 79; + sh = gm->w < 79 ? gm->h : gm->h*sw*44/(79*gm->w); + + for (yy=sh-1; yy>=0; yy--) { + for (xx=0; xx<sw; xx++) { + d=0; + t=0; + for (x=xx*gm->w/sw; x<(xx+1)*gm->w/sw; x++) { + for (y=yy*gm->h/sh; y<(yy+1)*gm->h/sh; y++) { + d += GM_GET(gm, x, y); + t += 256; + } + } + fputc("*#=- "[5*d/t], f); /* what a cute trick :) */ + } + fputc('\n', f); + } + return 0; +} diff --git a/src/trace/potrace/greymap.h b/src/trace/potrace/greymap.h new file mode 100644 index 000000000..816566205 --- /dev/null +++ b/src/trace/potrace/greymap.h @@ -0,0 +1,58 @@ +/* Copyright (C) 2001-2005 Peter Selinger. + This file is part of potrace. It is free software and it is covered + by the GNU General Public License. See the file COPYING for details. */ + +/* $Id$ */ + +#ifndef PGM_H +#define PGM_H + +#include <stdio.h> + +/* internal format for greymaps. Note: in this format, rows are + ordered from bottom to top. The pixels in each row are given from + left to right. */ + +struct greymap_s { + int w; /* width, in pixels */ + int h; /* height, in pixels */ + signed short int *map; /* raw data, w*h values */ +}; +typedef struct greymap_s greymap_t; + +/* macros for accessing pixel at index (x,y). Note that the origin is + in the *lower* left corner. U* macros omit the bounds check. */ + +#define gm_index(gm, x, y) (&(gm)->map[(x)+(y)*(gm)->w]) +#define gm_safe(gm, x, y) ((int)(x)>=0 && (int)(x)<(gm)->w && (int)(y)>=0 && (int)(y)<(gm)->h) +#define gm_bound(x, m) ((x)<0 ? 0 : (x)>=(m) ? (m)-1 : (x)) +#define GM_UGET(gm, x, y) (*gm_index(gm, x, y)) +#define GM_UINC(gm, x, y, b) (*gm_index(gm, x, y) += (short int)(b)) +#define GM_UINV(gm, x, y) (*gm_index(gm, x, y) = 255 - *gm_index(gm, x, y)) +#define GM_UPUT(gm, x, y, b) (*gm_index(gm, x, y) = (short int)(b)) +#define GM_GET(gm, x, y) (gm_safe(gm, x, y) ? GM_UGET(gm, x, y) : 0) +#define GM_INC(gm, x, y, b) (gm_safe(gm, x, y) ? GM_UINC(gm, x, y, b) : 0) +#define GM_INV(gm, x, y) (gm_safe(gm, x, y) ? GM_UINV(gm, x, y) : 0) +#define GM_PUT(gm, x, y, b) (gm_safe(gm, x, y) ? GM_UPUT(gm, x, y, b) : 0) +#define GM_BGET(gm, x, y) GM_UGET(gm, gm_bound(x, gm->w), gm_bound(y, gm->h)) + +/* modes for cutting off out-of-range values. The following names + refer to winding numbers. I.e., make a pixel black if winding + number is nonzero, odd, or positive, respectively. We assume that 0 + winding number corresponds to white (255). */ +#define GM_MODE_NONZERO 1 +#define GM_MODE_ODD 2 +#define GM_MODE_POSITIVE 3 +#define GM_MODE_NEGATIVE 4 + +extern char *gm_read_error; + +greymap_t *gm_new(int w, int h); +greymap_t *gm_dup(greymap_t *gm); +void gm_free(greymap_t *gm); +void gm_clear(greymap_t *gm, int b); +int gm_read(FILE *f, greymap_t **gmp); +int gm_writepgm(FILE *f, greymap_t *gm, char *comment, int raw, int mode, double gamma); +int gm_print(FILE *f, greymap_t *gm); + +#endif /* PGM_H */ diff --git a/src/trace/potrace/inkscape-potrace.cpp b/src/trace/potrace/inkscape-potrace.cpp new file mode 100644 index 000000000..59c57a280 --- /dev/null +++ b/src/trace/potrace/inkscape-potrace.cpp @@ -0,0 +1,712 @@ +/* + * This is the C++ glue between Inkscape and Potrace + * + * Authors: + * Bob Jamison <rjamison@titan.com> + * + * Copyright (C) 2004 Bob Jamison + * + * Released under GNU GPL, read the file 'COPYING' for more information + * + * Potrace, the wonderful tracer located at http://potrace.sourceforge.net, + * is provided by the generosity of Peter Selinger, to whom we are grateful. + * + */ + +#include <glibmm/i18n.h> +#include <gtkmm/main.h> + +#include "trace/filterset.h" +#include "trace/imagemap-gdk.h" + +#include <inkscape.h> +#include <desktop-handles.h> +#include "message-stack.h" +#include <sp-path.h> +#include <svg/stringstream.h> +#include "curve.h" +#include "bitmap.h" + +#include "inkscape-potrace.h" + + +static void updateGui() +{ + //## Allow the GUI to update + Gtk::Main::iteration(false); //at least once, non-blocking + while( Gtk::Main::events_pending() ) + Gtk::Main::iteration(); + +} + + +static void potraceStatusCallback(double progress, void *userData) /* callback fn */ +{ + updateGui(); + + if (!userData) + return; + + //g_message("progress: %f\n", progress); + + //Inkscape::Trace::Potrace::PotraceTracingEngine *engine = + // (Inkscape::Trace::Potrace::PotraceTracingEngine *)userData; +} + + + + +//required by potrace +namespace Inkscape { + +namespace Trace { + +namespace Potrace { + + +/** + * + */ +PotraceTracingEngine::PotraceTracingEngine() +{ + + //##### Our defaults + invert = false; + traceType = TRACE_BRIGHTNESS; + + quantizationNrColors = 8; + + brightnessThreshold = 0.45; + + cannyHighThreshold = 0.65; + + +} + + + + +typedef struct +{ + double x; + double y; +} Point; + + +/** + * Check a point against a list of points to see if it + * has already occurred. + */ +static bool +hasPoint(std::vector<Point> &points, double x, double y) +{ + for (unsigned int i=0; i<points.size() ; i++) + { + Point p = points[i]; + if (p.x == x && p.y == y) + return true; + } + return false; +} + + +/** + * Recursively descend the path_t node tree, writing paths in SVG + * format into the output stream. The Point vector is used to prevent + * redundant paths. Returns number of paths processed. + */ +static long +writePaths(PotraceTracingEngine *engine, potrace_path_t *plist, + Inkscape::SVGOStringStream& data, std::vector<Point> &points) +{ + long nodeCount = 0L; + + potrace_path_t *node; + for (node=plist; node ; node=node->sibling) + { + potrace_curve_t *curve = &(node->curve); + //g_message("node->fm:%d\n", node->fm); + if (!curve->n) + continue; + dpoint_t *pt = curve->c[curve->n - 1]; + double x0 = 0.0; + double y0 = 0.0; + double x1 = 0.0; + double y1 = 0.0; + double x2 = pt[2].x; + double y2 = pt[2].y; + //Have we been here already? + if (hasPoint(points, x2, y2)) + { + //g_message("duplicate point: (%f,%f)\n", x2, y2); + continue; + } + else + { + Point p; + p.x = x2; p.y = y2; + points.push_back(p); + } + data << "M " << x2 << " " << y2 << " "; + nodeCount++; + + for (int i=0 ; i<curve->n ; i++) + { + if (!engine->keepGoing) + return 0L; + pt = curve->c[i]; + x0 = pt[0].x; + y0 = pt[0].y; + x1 = pt[1].x; + y1 = pt[1].y; + x2 = pt[2].x; + y2 = pt[2].y; + switch (curve->tag[i]) + { + case POTRACE_CORNER: + data << "L " << x1 << " " << y1 << " " ; + data << "L " << x2 << " " << y2 << " " ; + break; + case POTRACE_CURVETO: + data << "C " << x0 << " " << y0 << " " + << x1 << " " << y1 << " " + << x2 << " " << y2 << " "; + + break; + default: + break; + } + nodeCount++; + } + data << "z"; + + for (path_t *child=node->childlist; child ; child=child->sibling) + { + nodeCount += writePaths(engine, child, data, points); + } + } + + return nodeCount; + +} + + + + + +static GrayMap * +filter(PotraceTracingEngine &engine, GdkPixbuf * pixbuf) +{ + if (!pixbuf) + return NULL; + + GrayMap *newGm = NULL; + + /*### Color quantization -- banding ###*/ + if (engine.getTraceType() == TRACE_QUANT) + { + RgbMap *rgbmap = gdkPixbufToRgbMap(pixbuf); + //rgbMap->writePPM(rgbMap, "rgb.ppm"); + newGm = quantizeBand(rgbmap, + engine.getQuantizationNrColors()); + rgbmap->destroy(rgbmap); + //return newGm; + } + + /*### Brightness threshold ###*/ + else if ( engine.getTraceType() == TRACE_BRIGHTNESS || + engine.getTraceType() == TRACE_BRIGHTNESS_MULTI ) + { + GrayMap *gm = gdkPixbufToGrayMap(pixbuf); + + newGm = GrayMapCreate(gm->width, gm->height); + double floor = 3.0 * + ( engine.getBrightnessFloor() * 256.0 ); + double cutoff = 3.0 * + ( engine.getBrightnessThreshold() * 256.0 ); + for (int y=0 ; y<gm->height ; y++) + { + for (int x=0 ; x<gm->width ; x++) + { + double brightness = (double)gm->getPixel(gm, x, y); + if (brightness >= floor && brightness < cutoff) + newGm->setPixel(newGm, x, y, GRAYMAP_BLACK); //black pixel + else + newGm->setPixel(newGm, x, y, GRAYMAP_WHITE); //white pixel + } + } + + gm->destroy(gm); + //newGm->writePPM(newGm, "brightness.ppm"); + //return newGm; + } + + /*### Canny edge detection ###*/ + else if (engine.getTraceType() == TRACE_CANNY) + { + GrayMap *gm = gdkPixbufToGrayMap(pixbuf); + newGm = grayMapCanny(gm, 0.1, engine.getCannyHighThreshold()); + gm->destroy(gm); + //newGm->writePPM(newGm, "canny.ppm"); + //return newGm; + } + + /*### Do I invert the image? ###*/ + if (newGm && engine.getInvert()) + { + for (int y=0 ; y<newGm->height ; y++) + { + for (int x=0 ; x<newGm->width ; x++) + { + unsigned long brightness = newGm->getPixel(newGm, x, y); + brightness = 765 - brightness; + newGm->setPixel(newGm, x, y, brightness); + } + } + } + + return newGm;//none of the above +} + + +static IndexedMap * +filterIndexed(PotraceTracingEngine &engine, GdkPixbuf * pixbuf) +{ + if (!pixbuf) + return NULL; + + IndexedMap *newGm = NULL; + + /*### Color quant multiscan ###*/ + if (engine.getTraceType() == TRACE_QUANT_COLOR) + { + RgbMap *gm = gdkPixbufToRgbMap(pixbuf); + if (engine.getMultiScanSmooth()) + { + RgbMap *gaussMap = rgbMapGaussian(gm); + newGm = rgbMapQuantize(gaussMap, 4, engine.getMultiScanNrColors()); + gaussMap->destroy(gaussMap); + } + else + { + newGm = rgbMapQuantize(gm, 4, engine.getMultiScanNrColors()); + } + gm->destroy(gm); + } + + /*### Quant multiscan ###*/ + else if (engine.getTraceType() == TRACE_QUANT_MONO) + { + RgbMap *gm = gdkPixbufToRgbMap(pixbuf); + if (engine.getMultiScanSmooth()) + { + RgbMap *gaussMap = rgbMapGaussian(gm); + newGm = rgbMapQuantize(gaussMap, 4, engine.getMultiScanNrColors()); + gaussMap->destroy(gaussMap); + } + else + { + newGm = rgbMapQuantize(gm, 4, engine.getMultiScanNrColors()); + } + gm->destroy(gm); + + //Turn to grays + for (int i=0 ; i<newGm->nrColors ; i++) + { + RGB rgb = newGm->clut[i]; + int grayVal = (rgb.r + rgb.g + rgb.b) / 3; + rgb.r = rgb.g = rgb.b = grayVal; + newGm->clut[i] = rgb; + } + } + + return newGm; +} + + + + +GdkPixbuf * +PotraceTracingEngine::preview(GdkPixbuf * pixbuf) +{ + if ( traceType == TRACE_QUANT_COLOR || + traceType == TRACE_QUANT_MONO ) + { + IndexedMap *gm = filterIndexed(*this, pixbuf); + if (!gm) + return NULL; + + GdkPixbuf *newBuf = indexedMapToGdkPixbuf(gm); + + gm->destroy(gm); + + return newBuf; + } + else + { + GrayMap *gm = filter(*this, pixbuf); + if (!gm) + return NULL; + + GdkPixbuf *newBuf = grayMapToGdkPixbuf(gm); + + gm->destroy(gm); + + return newBuf; + } +} + + +//*This is the core inkscape-to-potrace binding +char *PotraceTracingEngine::grayMapToPath(GrayMap *grayMap, long *nodeCount) +{ + + /* get default parameters */ + potrace_param_t *potraceParams = potrace_param_default(); + + potraceParams->progress.callback = potraceStatusCallback; + potraceParams->progress.data = (void *)this; + + potrace_bitmap_t *potraceBitmap = bm_new(grayMap->width, grayMap->height); + bm_clear(potraceBitmap, 0); + + //##Read the data out of the GrayMap + for (int y=0 ; y<grayMap->height ; y++) + { + for (int x=0 ; x<grayMap->width ; x++) + { + BM_UPUT(potraceBitmap, x, y, + grayMap->getPixel(grayMap, x, y) ? 0 : 1); + } + } + + //##Debug + /* + FILE *f = fopen("poimage.pbm", "wb"); + bm_writepbm(f, bm); + fclose(f); + */ + + if (!keepGoing) + { + g_warning("aborted"); + return NULL; + } + + /* trace a bitmap*/ + potrace_state_t *potraceState = potrace_trace(potraceParams, + potraceBitmap); + + //## Free the Potrace bitmap + bm_free(potraceBitmap); + + if (!keepGoing) + { + g_warning("aborted"); + potrace_state_free(potraceState); + potrace_param_free(potraceParams); + return NULL; + } + + Inkscape::SVGOStringStream data; + + data << ""; + + //## copy the path information into our d="" attribute string + std::vector<Point> points; + long thisNodeCount = writePaths(this, potraceState->plist, data, points); + + /* free a potrace items */ + potrace_state_free(potraceState); + potrace_param_free(potraceParams); + + if (!keepGoing) + return NULL; + + char *d = strdup((char *)data.str().c_str()); + if ( nodeCount) + *nodeCount = thisNodeCount; + + + return d; + +} + + + +/** + * This is called for a single scan + */ +TracingEngineResult * +PotraceTracingEngine::traceSingle(GdkPixbuf * thePixbuf, int *nrPaths) +{ + + if (!thePixbuf) + return NULL; + + brightnessFloor = 0.0; //important to set this + + GrayMap *grayMap = filter(*this, thePixbuf); + if (!grayMap) + return NULL; + + long nodeCount; + char *d = grayMapToPath(grayMap, &nodeCount); + + grayMap->destroy(grayMap); + + if (!d) + { + *nrPaths = 0; + return NULL; + } + char *style = "fill:#000000"; + + //g_message("### GOT '%s' \n", d); + TracingEngineResult *result = new TracingEngineResult(style, d, nodeCount); + + free(d); + + *nrPaths = 1; + + return result; +} + + +/** + * Called for multiple-scanning algorithms + */ +TracingEngineResult * +PotraceTracingEngine::traceBrightnessMulti(GdkPixbuf * thePixbuf, int *nrPaths) +{ + + if (!thePixbuf) + return NULL; + + double low = 0.2; //bottom of range + double high = 0.9; //top of range + double delta = (high - low ) / ((double)multiScanNrColors); + + brightnessFloor = 0.0; //Set bottom to black + + int traceCount = 0; + + TracingEngineResult *results = NULL; + for ( brightnessThreshold = low ; + brightnessThreshold <= high ; + brightnessThreshold += delta) + + { + + GrayMap *grayMap = filter(*this, thePixbuf); + if (!grayMap) + return NULL; + + long nodeCount; + char *d = grayMapToPath(grayMap, &nodeCount); + + grayMap->destroy(grayMap); + + if (!d) + { + *nrPaths = 0; + return NULL; + } + + int grayVal = (int)(256.0 * brightnessThreshold); + char style[31]; + sprintf(style, "fill-opacity:1.0;fill:#%02x%02x%02x", + grayVal, grayVal, grayVal); + + //g_message("### GOT '%s' \n", d); + TracingEngineResult *result = new TracingEngineResult(style, d, nodeCount); + + free(d); + + if (!results) + { + results = result; //first one + } + else + { + //walk to end of list + TracingEngineResult *r; + for (r=results ; r->next ; r=r->next) + {} + r->next = result; + } + + if (!multiScanStack) + brightnessFloor = brightnessThreshold; + + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (desktop) + { + gchar *msg = g_strdup_printf(_("Trace: %d. %ld nodes"), traceCount++, nodeCount); + SP_DT_MSGSTACK(desktop)->flash(Inkscape::NORMAL_MESSAGE, msg); + g_free(msg); + } + } + + //report the count of paths processed + *nrPaths = multiScanNrColors; + + + return results; +} + + +/** + * Quantization + */ +TracingEngineResult * +PotraceTracingEngine::traceQuant(GdkPixbuf * thePixbuf, int *nrPaths) +{ + + if (!thePixbuf) + return NULL; + + IndexedMap *iMap = filterIndexed(*this, thePixbuf); + if (!iMap) + return NULL; + + TracingEngineResult *results = NULL; + + //Create and clear a gray map + GrayMap *gm = GrayMapCreate(iMap->width, iMap->height); + for (int row=0 ; row<gm->height ; row++) + for (int col=0 ; col<gm->width ; col++) + gm->setPixel(gm, col, row, GRAYMAP_WHITE); + + + for (int colorIndex=0 ; colorIndex<iMap->nrColors ; colorIndex++) + { + + /*Make a gray map for each color index */ + for (int row=0 ; row<iMap->height ; row++) + { + for (int col=0 ; col<iMap->width ; col++) + { + 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 + } + } + } + + //## Now we have a traceable graymap + long nodeCount; + char *d = grayMapToPath(gm, &nodeCount); + + if (!d) + { + *nrPaths = 0; + return NULL; + } + + //### get style info + char style[13]; + RGB rgb = iMap->clut[colorIndex]; + sprintf(style, "fill:#%02x%02x%02x", rgb.r, rgb.g, rgb.b); + + //g_message("### GOT '%s' \n", d); + TracingEngineResult *result = new TracingEngineResult(style, d, nodeCount); + + free(d); + + if (!results) + { + results = result; //first one + } + else + { + //prepend + /* + result->next = results; + results = result; + */ + + //append + TracingEngineResult *r; + for (r=results ; r->next ; r=r->next) + {} + r->next = result; + } + + + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (desktop) + { + gchar *msg = g_strdup_printf(_("Trace: %d. %ld nodes"), colorIndex, nodeCount); + SP_DT_MSGSTACK(desktop)->flash(Inkscape::NORMAL_MESSAGE, msg); + g_free(msg); + } + + + } + + //report the count of paths processed + *nrPaths = iMap->nrColors; + + gm->destroy(gm); + iMap->destroy(iMap); + + return results; +} + + +/** + * This is the working method of this interface, and all + * implementing classes. Take a GdkPixbuf, trace it, and + * return the path data that is compatible with the d="" attribute + * of an SVG <path> element. + */ +TracingEngineResult * +PotraceTracingEngine::trace(GdkPixbuf * thePixbuf, int *nrPaths) +{ + + //Set up for messages + keepGoing = 1; + + if ( traceType == TRACE_QUANT_COLOR || + traceType == TRACE_QUANT_MONO ) + { + return traceQuant(thePixbuf, nrPaths); + } + else if ( traceType == TRACE_BRIGHTNESS_MULTI ) + { + return traceBrightnessMulti(thePixbuf, nrPaths); + } + else + { + return traceSingle(thePixbuf, nrPaths); + } +} + + + + + +/** + * Abort the thread that is executing getPathDataFromPixbuf() + */ +void +PotraceTracingEngine::abort() +{ + //g_message("PotraceTracingEngine::abort()\n"); + keepGoing = 0; +} + + + + +} // namespace Potrace +} // namespace Trace +} // namespace Inkscape + diff --git a/src/trace/potrace/inkscape-potrace.h b/src/trace/potrace/inkscape-potrace.h new file mode 100644 index 000000000..7a8d97b2e --- /dev/null +++ b/src/trace/potrace/inkscape-potrace.h @@ -0,0 +1,231 @@ +/* + * This is the C++ glue between Inkscape and Potrace + * + * Authors: + * Bob Jamison <rjamison@titan.com> + * + * Copyright (C) 2004 Bob Jamison + * + * Released under GNU GPL, read the file 'COPYING' for more information + * + * Potrace, the wonderful tracer located at http://potrace.sourceforge.net, + * is provided by the generosity of Peter Selinger, to whom we are grateful. + * + */ + +#ifndef __INKSCAPE_POTRACE_H__ +#define __INKSCAPE_POTRACE_H__ + +#include <glib.h> +#include <trace/trace.h> +#include <trace/imagemap.h> + +namespace Inkscape { + +namespace Trace { + +namespace Potrace { + +typedef enum + { + TRACE_BRIGHTNESS, + TRACE_BRIGHTNESS_MULTI, + TRACE_CANNY, + TRACE_QUANT, + TRACE_QUANT_COLOR, + TRACE_QUANT_MONO + } TraceType; + + +class PotraceTracingEngine : public TracingEngine +{ + + public: + + /** + * + */ + PotraceTracingEngine(); + + /** + * + */ + virtual ~PotraceTracingEngine() + {} + + void setTraceType(TraceType val) + { + traceType = val; + } + TraceType getTraceType() + { + return traceType; + } + + /** + * Sets/gets whether I invert the product of the other filter(s) + */ + void setInvert(bool val) + { + invert = val; + } + bool getInvert() + { + return invert; + } + + /** + * Sets the halfway point for black/white + */ + void setQuantizationNrColors(int val) + { + quantizationNrColors = val; + } + int getQuantizationNrColors() + { + return quantizationNrColors; + } + + /** + * Sets the halfway point for black/white + */ + void setBrightnessThreshold(double val) + { + brightnessThreshold = val; + } + double getBrightnessThreshold() + { + return brightnessThreshold; + } + + /** + * Sets the lower consideration point for black/white + */ + void setBrightnessFloor(double val) + { + brightnessFloor = val; + } + double getBrightnessFloor() + { + return brightnessFloor; + } + + /** + * Sets upper cutoff for canny non-maximalizing + */ + void setCannyHighThreshold(double val) + { + cannyHighThreshold = val; + } + double getCannyHighThreshold() + { + return cannyHighThreshold; + } + + /** + * Sets the number of colors for quant multiscan + */ + void setMultiScanNrColors(int val) + { + multiScanNrColors = val; + } + int getMultiScanNrColors() + { + return multiScanNrColors; + } + + /** + * Sets whether we tile regions side-by-side or stack them + */ + void setMultiScanStack(bool val) + { + multiScanStack = val; + } + bool setMultiScanStack() + { + return multiScanStack; + } + + /** + * Sets whether we want gaussian smoothing of bitmaps before quantizing + */ + void setMultiScanSmooth(bool val) + { + multiScanSmooth = val; + } + bool getMultiScanSmooth() + { + return multiScanSmooth; + } + + + /** + * This is the working method of this implementing class, and all + * implementing classes. Take a GdkPixbuf, trace it, and + * return the path data that is compatible with the d="" attribute + * of an SVG <path> element. + */ + virtual TracingEngineResult *trace(GdkPixbuf *pixbuf, int *nrPaths); + + /** + * Abort the thread that is executing getPathDataFromPixbuf() + */ + virtual void abort(); + + /** + * + */ + GdkPixbuf *preview(GdkPixbuf * pixbuf); + + /** + * + */ + int keepGoing; + + + + private: + + TraceType traceType; + + //## do i invert at the end? + bool invert; + + //## Color-->b&w quantization + int quantizationNrColors; + + //## brightness items + double brightnessThreshold; + double brightnessFloor; //usually 0.0 + + //## canny items + double cannyHighThreshold; + + //## Color-->multiscan quantization + int multiScanNrColors; + bool multiScanStack; //do we tile or stack? + bool multiScanSmooth;//do we use gaussian filter? + + /** + * This is the actual wrapper of the call to Potrace. nodeCount + * returns the count of nodes created. May be NULL if ignored. + */ + char *grayMapToPath(GrayMap *gm, long *nodeCount); + + TracingEngineResult *traceBrightnessMulti(GdkPixbuf *pixbuf, int *nrPaths); + TracingEngineResult *traceQuant(GdkPixbuf *pixbuf, int *nrPaths); + TracingEngineResult *traceSingle(GdkPixbuf *pixbuf, int *nrPaths); + + +};//class PotraceTracingEngine + + + +} // namespace Potrace +} // namespace Trace +} // namespace Inkscape + + +#endif //__INKSCAPE_POTRACE_H__ + + diff --git a/src/trace/potrace/lists.h b/src/trace/potrace/lists.h new file mode 100644 index 000000000..fe689e4c5 --- /dev/null +++ b/src/trace/potrace/lists.h @@ -0,0 +1,286 @@ +/* Copyright (C) 2001-2005 Peter Selinger. + This file is part of potrace. It is free software and it is covered + by the GNU General Public License. See the file COPYING for details. */ + +/* $Id$ */ + +#ifndef _PS_LISTS_H +#define _PS_LISTS_H + +/* here we define some general list macros. Because they are macros, + they should work on any datatype with a "->next" component. Some of + them use a "hook". If elt and list are of type t* then hook is of + type t**. A hook stands for an insertion point in the list, i.e., + either before the first element, or between two elements, or after + the last element. If an operation "sets the hook" for an element, + then the hook is set to just before the element. One can insert + something at a hook. One can also unlink at a hook: this means, + unlink the element just after the hook. By "to unlink", we mean the + element is removed from the list, but not deleted. Thus, it and its + components still need to be freed. */ + +/* Note: these macros are somewhat experimental. Only the ones that + are actually *used* have been tested. So be careful to test any + that you use. Looking at the output of the preprocessor, "gcc -E" + (possibly piped though "indent"), might help too. Also: these + macros define some internal (local) variables that start with + "_". */ + +/* we enclose macro definitions whose body consists of more than one + statement in MACRO_BEGIN and MACRO_END, rather than '{' and '}'. The + reason is that we want to be able to use the macro in a context + such as "if (...) macro(...); else ...". If we didn't use this obscure + trick, we'd have to omit the ";" in such cases. */ + +#define MACRO_BEGIN do { +#define MACRO_END } while (0) + +/* ---------------------------------------------------------------------- */ +/* macros for singly-linked lists */ + +/* traverse list. At the end, elt is set to NULL. */ +#define list_forall(elt, list) for (elt=list; elt!=NULL; elt=elt->next) + +/* set elt to the first element of list satisfying boolean condition + c, or NULL if not found */ +#define list_find(elt, list, c) \ + MACRO_BEGIN list_forall(elt, list) if (c) break; MACRO_END + +/* like forall, except also set hook for elt. */ +#define list_forall2(elt, list, hook) \ + for (elt=list, hook=&list; elt!=NULL; hook=&elt->next, elt=elt->next) + +/* same as list_find, except also set hook for elt. */ +#define list_find2(elt, list, c, hook) \ + MACRO_BEGIN list_forall2(elt, list, hook) if (c) break; MACRO_END + +/* same, except only use hook. */ +#define _list_forall_hook(list, hook) \ + for (hook=&list; *hook!=NULL; hook=&(*hook)->next) + +/* same, except only use hook. Note: c may only refer to *hook, not elt. */ +#define _list_find_hook(list, c, hook) \ + MACRO_BEGIN _list_forall_hook(list, hook) if (c) break; MACRO_END + +/* insert element after hook */ +#define list_insert_athook(elt, hook) \ + MACRO_BEGIN elt->next = *hook; *hook = elt; MACRO_END + +/* insert element before hook */ +#define list_insert_beforehook(elt, hook) \ + MACRO_BEGIN elt->next = *hook; *hook = elt; hook=&elt->next; MACRO_END + +/* unlink element after hook, let elt be unlinked element, or NULL. + hook remains. */ +#define list_unlink_athook(list, elt, hook) \ + MACRO_BEGIN \ + elt = hook ? *hook : NULL; if (elt) { *hook = elt->next; elt->next = NULL; }\ + MACRO_END + +/* unlink the specific element, if it is in the list. Otherwise, set + elt to NULL */ +#define list_unlink(listtype, list, elt) \ + MACRO_BEGIN \ + listtype **_hook; \ + _list_find_hook(list, *_hook==elt, _hook); \ + list_unlink_athook(list, elt, _hook); \ + MACRO_END + +/* prepend elt to list */ +#define list_prepend(list, elt) \ + MACRO_BEGIN elt->next = list; list = elt; MACRO_END + +/* append elt to list. */ +#define list_append(listtype, list, elt) \ + MACRO_BEGIN \ + listtype **_hook; \ + _list_forall_hook(list, _hook) {} \ + list_insert_athook(elt, _hook); \ + MACRO_END + +/* unlink the first element that satisfies the condition. */ +#define list_unlink_cond(listtype, list, elt, c) \ + MACRO_BEGIN \ + listtype **_hook; \ + list_find2(elt, list, c, _hook); \ + list_unlink_athook(list, elt, _hook); \ + MACRO_END + +/* let elt be the nth element of the list, starting to count from 0. + Return NULL if out of bounds. */ +#define list_nth(elt, list, n) \ + MACRO_BEGIN \ + int _x; /* only evaluate n once */ \ + for (_x=(n), elt=list; _x && elt; _x--, elt=elt->next) {} \ + MACRO_END + +/* let elt be the nth element of the list, starting to count from 0. + Return NULL if out of bounds. */ +#define list_nth_hook(elt, list, n, hook) \ + MACRO_BEGIN \ + int _x; /* only evaluate n once */ \ + for (_x=(n), elt=list, hook=&list; _x && elt; _x--, hook=&elt->next, elt=elt->next) {} \ + MACRO_END + +/* set n to the length of the list */ +#define list_length(listtype, list, n) \ + MACRO_BEGIN \ + listtype *_elt; \ + n=0; \ + list_forall(_elt, list) \ + n++; \ + MACRO_END + +/* set n to the index of the first element satisfying cond, or -1 if + none found. Also set elt to the element, or NULL if none found. */ +#define list_index(list, n, elt, c) \ + MACRO_BEGIN \ + n=0; \ + list_forall(elt, list) { \ + if (c) break; \ + n++; \ + } \ + if (!elt) \ + n=-1; \ + MACRO_END + +/* set n to the number of elements in the list that satisfy condition c */ +#define list_count(list, n, elt, c) \ + MACRO_BEGIN \ + n=0; \ + list_forall(elt, list) { \ + if (c) n++; \ + } \ + MACRO_END + +/* let elt be each element of the list, unlinked. At the end, set list=NULL. */ +#define list_forall_unlink(elt, list) \ + for (elt=list; elt ? (list=elt->next, elt->next=NULL), 1 : 0; elt=list) + +/* reverse a list (efficient) */ +#define list_reverse(listtype, list) \ + MACRO_BEGIN \ + listtype *_list1=NULL, *elt; \ + list_forall_unlink(elt, list) \ + list_prepend(_list1, elt); \ + list = _list1; \ + MACRO_END + +/* insert the element ELT just before the first element TMP of the + list for which COND holds. Here COND must be a condition of ELT and + TMP. Typical usage is to insert an element into an ordered list: + for instance, list_insert_ordered(listtype, list, elt, tmp, + elt->size <= tmp->size). Note: if we give a "less than or equal" + condition, the new element will be inserted just before a sequence + of equal elements. If we give a "less than" condition, the new + element will be inserted just after a list of equal elements. + Note: it is much more efficient to construct a list with + list_prepend and then order it with list_merge_sort, than to + construct it with list_insert_ordered. */ +#define list_insert_ordered(listtype, list, elt, tmp, cond) \ + MACRO_BEGIN \ + listtype **_hook; \ + _list_find_hook(list, (tmp=*_hook, (cond)), _hook); \ + list_insert_athook(elt, _hook); \ + MACRO_END + +/* sort the given list, according to the comparison condition. + Typical usage is list_sort(listtype, list, a, b, a->size < + b->size). Note: if we give "less than or equal" condition, each + segment of equal elements will be reversed in order. If we give a + "less than" condition, each segment of equal elements will retain + the original order. The latter is slower but sometimes + prettier. Average running time: n*n/2. */ +#define list_sort(listtype, list, a, b, cond) \ + MACRO_BEGIN \ + listtype *_newlist=NULL; \ + list_forall_unlink(a, list) \ + list_insert_ordered(listtype, _newlist, a, b, cond); \ + list = _newlist; \ + MACRO_END + +/* a much faster sort algorithm (merge sort, n log n worst case). It + is required that the list type has an additional, unused next1 + component. Note there is no curious reversal of order of equal + elements as for list_sort. */ + +#define list_mergesort(listtype, list, a, b, cond) \ + MACRO_BEGIN \ + listtype *_elt, **_hook1; \ + \ + for (_elt=list; _elt; _elt=_elt->next1) { \ + _elt->next1 = _elt->next; \ + _elt->next = NULL; \ + } \ + do { \ + _hook1 = &(list); \ + while ((a = *_hook1) != NULL && (b = a->next1) != NULL ) { \ + _elt = b->next1; \ + _list_merge_cond(listtype, a, b, cond, *_hook1); \ + _hook1 = &((*_hook1)->next1); \ + *_hook1 = _elt; \ + } \ + } while (_hook1 != &(list)); \ + MACRO_END + +/* merge two sorted lists. Store result at &result */ +#define _list_merge_cond(listtype, a, b, cond, result) \ + MACRO_BEGIN \ + listtype **_hook; \ + _hook = &(result); \ + while (1) { \ + if (a==NULL) { \ + *_hook = b; \ + break; \ + } else if (b==NULL) { \ + *_hook = a; \ + break; \ + } else if (cond) { \ + *_hook = a; \ + _hook = &(a->next); \ + a = a->next; \ + } else { \ + *_hook = b; \ + _hook = &(b->next); \ + b = b->next; \ + } \ + } \ + MACRO_END + +/* ---------------------------------------------------------------------- */ +/* macros for doubly-linked lists */ + +#define dlist_append(head, end, elt) \ + MACRO_BEGIN \ + elt->prev = end; \ + elt->next = NULL; \ + if (end) { \ + end->next = elt; \ + } else { \ + head = elt; \ + } \ + end = elt; \ + MACRO_END + +/* let elt be each element of the list, unlinked. At the end, set list=NULL. */ +#define dlist_forall_unlink(elt, head, end) \ + for (elt=head; elt ? (head=elt->next, elt->next=NULL, elt->prev=NULL), 1 : (end=NULL, 0); elt=head) + +/* unlink the first element of the list */ +#define dlist_unlink_first(head, end, elt) \ + MACRO_BEGIN \ + elt = head; \ + if (head) { \ + head = head->next; \ + if (head) { \ + head->prev = NULL; \ + } else { \ + end = NULL; \ + } \ + elt->prev = NULL; \ + elt->next = NULL; \ + } \ + MACRO_END + +#endif /* _PS_LISTS_H */ + diff --git a/src/trace/potrace/potracelib.cpp b/src/trace/potrace/potracelib.cpp new file mode 100644 index 000000000..a51afd81e --- /dev/null +++ b/src/trace/potrace/potracelib.cpp @@ -0,0 +1,109 @@ +/* Copyright (C) 2001-2005 Peter Selinger. + This file is part of potrace. It is free software and it is covered + by the GNU General Public License. See the file COPYING for details. */ + +#include <stdlib.h> +#include <string.h> + +#include "decompose.h" +#include "trace.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* default parameters */ +static const potrace_param_t param_default = { + 2, /* turdsize */ + POTRACE_TURNPOLICY_MINORITY, /* turnpolicy */ + 1.0, /* alphamax */ + 1, /* opticurve */ + 0.2, /* opttolerance */ + { + NULL, /* callback function */ + NULL, /* callback data */ + 0.0, 1.0, /* progress range */ + 0.0, /* granularity */ + }, +}; + +/* Return a fresh copy of the set of default parameters, or NULL on + failure with errno set. */ +potrace_param_t *potrace_param_default() { + potrace_param_t *p; + + p = (potrace_param_t *) malloc(sizeof(potrace_param_t)); + if (!p) { + return NULL; + } + memcpy(p, ¶m_default, sizeof(potrace_param_t)); + return p; +} + +/* On success, returns a potrace state st with st->status == + POTRACE_STATUS_OK. On failure, returns NULL if no potrace state + could be created (with errno set), or returns an incomplete potrace + state (with st->status == POTRACE_STATUS_INCOMPLETE). Complete or + incomplete potrace state can be freed with potrace_state_free(). */ +potrace_state_t *potrace_trace(const potrace_param_t *param, const potrace_bitmap_t *bm) { + int r; + path_t *plist = NULL; + potrace_state_t *st; + progress_t prog; + progress_t subprog; + + /* prepare private progress bar state */ + prog.callback = param->progress.callback; + prog.data = param->progress.data; + prog.min = param->progress.min; + prog.max = param->progress.max; + prog.epsilon = param->progress.epsilon; + prog.d_prev = param->progress.min; + + /* allocate state object */ + st = (potrace_state_t *)malloc(sizeof(potrace_state_t *)); + if (!st) { + return NULL; + } + + progress_subrange_start(0.0, 0.1, &prog, &subprog); + + /* process the image */ + r = bm_to_pathlist(bm, &plist, param, &subprog); + if (r) { + free(st); + return NULL; + } + + st->status = POTRACE_STATUS_OK; + st->plist = plist; + + progress_subrange_end(&prog, &subprog); + + progress_subrange_start(0.1, 1.0, &prog, &subprog); + + /* partial success. */ + r = process_path(plist, param, &subprog); + if (r) { + st->status = POTRACE_STATUS_INCOMPLETE; + } + + progress_subrange_end(&prog, &subprog); + + return st; +} + +/* free a potrace state, without disturbing errno. */ +void potrace_state_free(potrace_state_t *st) { + pathlist_free(st->plist); + free(st); +} + +/* free a parameter list, without disturbing errno. */ +void potrace_param_free(potrace_param_t *p) { + free(p); +} + +char *potrace_version() { + return "potracelib "VERSION""; +} diff --git a/src/trace/potrace/potracelib.h b/src/trace/potrace/potracelib.h new file mode 100644 index 000000000..141f1d3be --- /dev/null +++ b/src/trace/potrace/potracelib.h @@ -0,0 +1,131 @@ +/* Copyright (C) 2001-2005 Peter Selinger. + This file is part of potrace. It is free software and it is covered + by the GNU General Public License. See the file COPYING for details. */ + +#ifndef POTRACELIB_H +#define POTRACELIB_H + +/* this file defines the API for the core potrace library. For a more + detailed description of the API, see doc/potracelib.txt */ + +/* ---------------------------------------------------------------------- */ +/* tracing parameters */ + +/* turn policies */ +#define POTRACE_TURNPOLICY_BLACK 0 +#define POTRACE_TURNPOLICY_WHITE 1 +#define POTRACE_TURNPOLICY_LEFT 2 +#define POTRACE_TURNPOLICY_RIGHT 3 +#define POTRACE_TURNPOLICY_MINORITY 4 +#define POTRACE_TURNPOLICY_MAJORITY 5 +#define POTRACE_TURNPOLICY_RANDOM 6 + +/* structure to hold progress bar callback data */ +struct potrace_progress_s { + void (*callback)(double progress, void *privdata); /* callback fn */ + void *data; /* callback function's private data */ + double min, max; /* desired range of progress, e.g. 0.0 to 1.0 */ + double epsilon; /* granularity: can skip smaller increments */ +}; +typedef struct potrace_progress_s potrace_progress_t; + +/* structure to hold tracing parameters */ +struct potrace_param_s { + int turdsize; /* area of largest path to be ignored */ + int turnpolicy; /* resolves ambiguous turns in path decomposition */ + double alphamax; /* corner threshold */ + int opticurve; /* use curve optimization? */ + double opttolerance; /* curve optimization tolerance */ + potrace_progress_t progress; /* progress callback function */ +}; +typedef struct potrace_param_s potrace_param_t; + +/* ---------------------------------------------------------------------- */ +/* bitmaps */ + +/* native word size */ +typedef unsigned long potrace_word; + +/* Internal bitmap format. The n-th scanline starts at scanline(n) = + (map + n*dy). Raster data is stored as a sequence of potrace_words + (NOT bytes). The leftmost bit of scanline n is the most significant + bit of scanline(n)[0]. */ +struct potrace_bitmap_s { + int w, h; /* width and height, in pixels */ + int dy; /* words per scanline (not bytes) */ + potrace_word *map; /* raw data, dy*h words */ +}; +typedef struct potrace_bitmap_s potrace_bitmap_t; + +/* ---------------------------------------------------------------------- */ +/* curves */ + +/* point */ +struct potrace_dpoint_s { + double x, y; +}; +typedef struct potrace_dpoint_s potrace_dpoint_t; + +/* segment tags */ +#define POTRACE_CURVETO 1 +#define POTRACE_CORNER 2 + +/* closed curve segment */ +struct potrace_curve_s { + int n; /* number of segments */ + int *tag; /* tag[n]: POTRACE_CURVETO or POTRACE_CORNER */ + potrace_dpoint_t (*c)[3]; /* c[n][3]: control points. + c[n][0] is unused for tag[n]=POTRACE_CORNER */ +}; +typedef struct potrace_curve_s potrace_curve_t; + +/* Linked list of signed curve segments. Also carries a tree structure. */ +struct potrace_path_s { + int area; /* area of the bitmap path */ + int sign; /* '+' or '-', depending on orientation */ + potrace_curve_t curve; /* this path's vector data */ + + struct potrace_path_s *next; /* linked list structure */ + + struct potrace_path_s *childlist; /* tree structure */ + struct potrace_path_s *sibling; /* tree structure */ + + struct potrace_privpath_s *priv; /* private state */ +}; +typedef struct potrace_path_s potrace_path_t; + +/* ---------------------------------------------------------------------- */ +/* Potrace state */ + +#define POTRACE_STATUS_OK 0 +#define POTRACE_STATUS_INCOMPLETE 1 + +struct potrace_state_s { + int status; + potrace_path_t *plist; /* vector data */ + + struct potrace_privstate_s *priv; /* private state */ +}; +typedef struct potrace_state_s potrace_state_t; + +/* ---------------------------------------------------------------------- */ +/* API functions */ + +/* get default parameters */ +potrace_param_t *potrace_param_default(); + +/* free parameter set */ +void potrace_param_free(potrace_param_t *p); + +/* trace a bitmap*/ +potrace_state_t *potrace_trace(const potrace_param_t *param, + const potrace_bitmap_t *bm); + +/* free a potrace state */ +void potrace_state_free(potrace_state_t *st); + +/* return a static plain text version string identifying this version + of potracelib */ +char *potrace_version(); + +#endif /* POTRACELIB_H */ diff --git a/src/trace/potrace/progress.h b/src/trace/potrace/progress.h new file mode 100644 index 000000000..3f617c4d1 --- /dev/null +++ b/src/trace/potrace/progress.h @@ -0,0 +1,79 @@ +/* Copyright (C) 2001-2005 Peter Selinger. + This file is part of potrace. It is free software and it is covered + by the GNU General Public License. See the file COPYING for details. */ + +/* operations on potrace_progress_t objects, which are defined in + potracelib.h. Note: the code attempts to minimize runtime overhead + when no progress monitoring was requested. It also tries to + minimize excessive progress calculations beneath the "epsilon" + threshold. */ + +#ifndef PROGRESS_H +#define PROGRESS_H + +/* structure to hold progress bar callback data */ +struct progress_s { + void (*callback)(double progress, void *privdata); /* callback fn */ + void *data; /* callback function's private data */ + double min, max; /* desired range of progress, e.g. 0.0 to 1.0 */ + double epsilon; /* granularity: can skip smaller increments */ + double b; /* upper limit of subrange in superrange units */ + double d_prev; /* previous value of d */ +}; +typedef struct progress_s progress_t; + +/* notify given progress object of current progress. Note that d is + given in the 0.0-1.0 range, which will be scaled and translated to + the progress object's range. */ +static inline void progress_update(double d, progress_t *prog) { + double d_scaled; + + if (prog->callback != NULL) { + d_scaled = prog->min * (1-d) + prog->max * d; + if (d == 1.0 || d_scaled >= prog->d_prev + prog->epsilon) { + prog->callback(prog->min * (1-d) + prog->max * d, prog->data); + prog->d_prev = d_scaled; + } + } +} + +/* start a subrange of the given progress object. The range is + narrowed to [a..b], relative to 0.0-1.0 coordinates. If new range + is below granularity threshold, disable further subdivisions. */ +static inline void progress_subrange_start(double a, double b, const progress_t *prog, progress_t *sub) { + double min, max; + + if (prog->callback == NULL) { + sub->callback = NULL; + return; + } + + min = prog->min * (1-a) + prog->max * a; + max = prog->min * (1-b) + prog->max * b; + + if (max - min < prog->epsilon) { + sub->callback = NULL; /* no further progress info in subrange */ + sub->b = b; + return; + } + sub->callback = prog->callback; + sub->data = prog->data; + sub->epsilon = prog->epsilon; + sub->min = min; + sub->max = max; + sub->d_prev = prog->d_prev; + return; +} + +static inline void progress_subrange_end(progress_t *prog, progress_t *sub) { + if (prog->callback != NULL) { + if (sub->callback == NULL) { + progress_update(sub->b, prog); + } else { + prog->d_prev = sub->d_prev; + } + } +} + +#endif /* PROGRESS_H */ + diff --git a/src/trace/potrace/render.cpp b/src/trace/potrace/render.cpp new file mode 100644 index 000000000..78b15fc60 --- /dev/null +++ b/src/trace/potrace/render.cpp @@ -0,0 +1,242 @@ +/* Copyright (C) 2001-2005 Peter Selinger. + This file is part of potrace. It is free software and it is covered + by the GNU General Public License. See the file COPYING for details. */ + +/* $Id$ */ + +#include <stdlib.h> +#include <math.h> +#include <string.h> + +#include "render.h" +#include "auxiliary.h" + +/* ---------------------------------------------------------------------- */ +/* routines for anti-aliased rendering of curves */ + +/* we use the following method. Given a point (x,y) (with real-valued + coordinates) in the plane, let (xi,yi) be the integer part of the + coordinates, i.e., xi=floor(x), yi=floor(y). Define a path from + (x,y) to infinity as follows: path(x,y) = + (x,y)--(xi+1,y)--(xi+1,yi)--(+infty,yi). Now as the point (x,y) + moves smoothly across the plane, the path path(x,y) sweeps + (non-smoothly) across a certain area. We proportionately blacken + the area as the path moves "downward", and we whiten the area as + the path moves "upward". This way, after the point has traversed a + closed curve, the interior of the curve has been darkened + (counterclockwise movement) or lightened (clockwise movement). (The + "grey shift" is actually proportional to the winding number). By + choosing the above path with mostly integer coordinates, we achieve + that only pixels close to (x,y) receive grey values and are subject + to round-off errors. The grey value of pixels far away from (x,y) + is always in "integer" (where 0=black, 1=white). As a special + trick, we keep an accumulator rm->a1, which holds a double value to + be added to the grey value to be added to the current pixel + (xi,yi). Only when changing "current" pixels, we convert this + double value to an integer. This way we avoid round-off errors at + the meeting points of line segments. Another speedup measure is + that we sometimes use the rm->incrow_buf array to postpone + incrementing or decrementing an entire row. If incrow_buf[y]=x+1!=0, + then all the pixels (x,y),(x+1,y),(x+2,y),... are scheduled to be + incremented/decremented (which one is the case will be clear from + context). This keeps the greymap operations reasonably local. */ + +/* allocate a new rendering state */ +render_t *render_new(greymap_t *gm) { + render_t *rm; + + rm = (render_t *) malloc(sizeof(render_t)); + if (!rm) { + return NULL; + } + memset(rm, 0, sizeof(render_t)); + rm->gm = gm; + rm->incrow_buf = (int *) malloc(gm->h * sizeof(int)); + if (!rm->incrow_buf) { + free(rm); + return NULL; + } + memset(rm->incrow_buf, 0, gm->h * sizeof(int)); + return rm; +} + +/* free a given rendering state. Note: this does not free the + underlying greymap. */ +void render_free(render_t *rm) { + free(rm->incrow_buf); + free(rm); +} + +/* close path */ +void render_close(render_t *rm) { + if (rm->x0 != rm->x1 || rm->y0 != rm->y1) { + render_lineto(rm, rm->x0, rm->y0); + } + GM_INC(rm->gm, rm->x0i, rm->y0i, (rm->a0+rm->a1)*255); + + /* assert (rm->x0i != rm->x1i || rm->y0i != rm->y1i); */ + + /* the persistent state is now undefined */ +} + +/* move point */ +void render_moveto(render_t *rm, double x, double y) { + /* close the previous path */ + render_close(rm); + + rm->x0 = rm->x1 = x; + rm->y0 = rm->y1 = y; + rm->x0i = (int)floor(rm->x0); + rm->x1i = (int)floor(rm->x1); + rm->y0i = (int)floor(rm->y0); + rm->y1i = (int)floor(rm->y1); + rm->a0 = rm->a1 = 0; +} + +/* add b to pixels (x,y) and all pixels to the right of it. However, + use rm->incrow_buf as a buffer to economize on multiple calls */ +static void incrow(render_t *rm, int x, int y, int b) { + int i, x0; + + if (y < 0 || y >= rm->gm->h) { + return; + } + + if (x < 0) { + x = 0; + } else if (x > rm->gm->w) { + x = rm->gm->w; + } + if (rm->incrow_buf[y] == 0) { + rm->incrow_buf[y] = x+1; /* store x+1 so that we can use 0 for "vacant" */ + return; + } + x0 = rm->incrow_buf[y]-1; + rm->incrow_buf[y] = 0; + if (x0 < x) { + for (i=x0; i<x; i++) { + GM_INC(rm->gm, i, y, -b); + } + } else { + for (i=x; i<x0; i++) { + GM_INC(rm->gm, i, y, b); + } + } +} + +/* render a straight line */ +void render_lineto(render_t *rm, double x2, double y2) { + int x2i, y2i; + double t0=2, s0=2; + int sn, tn; + double ss=2, ts=2; + double r0, r1; + int i, j; + int rxi, ryi; + int s; + + x2i = (int)floor(x2); + y2i = (int)floor(y2); + + sn = abs(x2i - rm->x1i); + tn = abs(y2i - rm->y1i); + + if (sn) { + s0 = ((x2>rm->x1 ? rm->x1i+1 : rm->x1i) - rm->x1)/(x2-rm->x1); + ss = fabs(1.0/(x2-rm->x1)); + } + if (tn) { + t0 = ((y2>rm->y1 ? rm->y1i+1 : rm->y1i) - rm->y1)/(y2-rm->y1); + ts = fabs(1.0/(y2-rm->y1)); + } + + r0 = 0; + + i = 0; + j = 0; + + rxi = rm->x1i; + ryi = rm->y1i; + + while (i<sn || j<tn) { + if (j>=tn || (i<sn && s0+i*ss < t0+j*ts)) { + r1 = s0+i*ss; + i++; + s = 1; + } else { + r1 = t0+j*ts; + j++; + s = 0; + } + /* render line from r0 to r1 segment of (rm->x1,rm->y1)..(x2,y2) */ + + /* move point to r1 */ + rm->a1 += (r1-r0)*(y2-rm->y1)*(rxi+1-((r0+r1)/2.0*(x2-rm->x1)+rm->x1)); + + /* move point across pixel boundary */ + if (s && x2>rm->x1) { + GM_INC(rm->gm, rxi, ryi, rm->a1*255); + rm->a1 = 0; + rxi++; + rm->a1 += rm->y1+r1*(y2-rm->y1)-ryi; + } else if (!s && y2>rm->y1) { + GM_INC(rm->gm, rxi, ryi, rm->a1*255); + rm->a1 = 0; + incrow(rm, rxi+1, ryi, 255); + ryi++; + } else if (s && x2<=rm->x1) { + rm->a1 -= rm->y1+r1*(y2-rm->y1)-ryi; + GM_INC(rm->gm, rxi, ryi, rm->a1*255); + rm->a1 = 0; + rxi--; + } else if (!s && y2<=rm->y1) { + GM_INC(rm->gm, rxi, ryi, rm->a1*255); + rm->a1 = 0; + ryi--; + incrow(rm, rxi+1, ryi, -255); + } + + r0 = r1; + } + + /* move point to (x2,y2) */ + + r1 = 1; + rm->a1 += (r1-r0)*(y2-rm->y1)*(rxi+1-((r0+r1)/2.0*(x2-rm->x1)+rm->x1)); + + rm->x1i = x2i; + rm->y1i = y2i; + rm->x1 = x2; + rm->y1 = y2; + + /* assert (rxi != rm->x1i || ryi != rm->y1i); */ +} + +/* render a Bezier curve. */ +void render_curveto(render_t *rm, double x2, double y2, double x3, double y3, double x4, double y4) { + double x1, y1, dd0, dd1, dd, delta, e2, epsilon, t; + + x1 = rm->x1; /* starting point */ + y1 = rm->y1; + + /* we approximate the curve by small line segments. The interval + size, epsilon, is determined on the fly so that the distance + between the true curve and its approximation does not exceed the + desired accuracy delta. */ + + delta = .1; /* desired accuracy, in pixels */ + + /* let dd = maximal value of 2nd derivative over curve - this must + occur at an endpoint. */ + dd0 = sq(x1-2*x2+x3) + sq(y1-2*y2+y3); + dd1 = sq(x2-2*x3+x4) + sq(y2-2*y3+y4); + dd = 6*sqrt(max(dd0, dd1)); + e2 = 8*delta <= dd ? 8*delta/dd : 1; + epsilon = sqrt(e2); /* necessary interval size */ + + for (t=epsilon; t<1; t+=epsilon) { + render_lineto(rm, x1*cu(1-t)+3*x2*sq(1-t)*t+3*x3*(1-t)*sq(t)+x4*cu(t), + y1*cu(1-t)+3*y2*sq(1-t)*t+3*y3*(1-t)*sq(t)+y4*cu(t)); + } + render_lineto(rm, x4, y4); +} diff --git a/src/trace/potrace/render.h b/src/trace/potrace/render.h new file mode 100644 index 000000000..0d3835c9e --- /dev/null +++ b/src/trace/potrace/render.h @@ -0,0 +1,28 @@ +/* Copyright (C) 2001-2005 Peter Selinger. + This file is part of potrace. It is free software and it is covered + by the GNU General Public License. See the file COPYING for details. */ + +/* $Id$ */ + +#ifndef RENDER_H +#define RENDER_H + +#include "greymap.h" + +struct render_s { + greymap_t *gm; + double x0, y0, x1, y1; + int x0i, y0i, x1i, y1i; + double a0, a1; + int *incrow_buf; +}; +typedef struct render_s render_t; + +render_t *render_new(greymap_t *gm); +void render_free(render_t *rm); +void render_close(render_t *rm); +void render_moveto(render_t *rm, double x, double y); +void render_lineto(render_t *rm, double x, double y); +void render_curveto(render_t *rm, double x2, double y2, double x3, double y3, double x4, double y4); + +#endif /* RENDER_H */ diff --git a/src/trace/potrace/trace.cpp b/src/trace/potrace/trace.cpp new file mode 100644 index 000000000..78388d3e9 --- /dev/null +++ b/src/trace/potrace/trace.cpp @@ -0,0 +1,1231 @@ +/* Copyright (C) 2001-2005 Peter Selinger. + This file is part of potrace. It is free software and it is covered + by the GNU General Public License. See the file COPYING for details. */ + +/* $Id$ */ +/* transform jaggy paths into smooth curves */ + +#include <math.h> +#include <stdlib.h> + +#include "curve.h" +#include "lists.h" +#include "progress.h" + +#define INFTY 10000000 /* it suffices that this is longer than any + path; it need not be really infinite */ +#define COS179 -0.999847695156 /* the cosine of 179 degrees */ + +/* ---------------------------------------------------------------------- */ +/* some useful macros. Note: the "mod" macro works correctly for + negative a. Also note that the test for a>=n, while redundant, + speeds up the mod function by 70% in the average case (significant + since the program spends about 16% of its time here - or 40% + without the test). The "floordiv" macro returns the largest integer + <= a/n, and again this works correctly for negative a, as long as + a,n are integers and n>0. */ + +#define SAFE_MALLOC(var, n, typ) \ + if ((var = (typ *)malloc((n)*sizeof(typ))) == NULL) goto malloc_error + +/* ---------------------------------------------------------------------- */ +/* auxiliary functions */ + +/* return a direction that is 90 degrees counterclockwise from p2-p0, + but then restricted to one of the major wind directions (n, nw, w, etc) */ +static inline point_t dorth_infty(dpoint_t p0, dpoint_t p2) { + point_t r; + + r.y = sign(p2.x-p0.x); + r.x = -sign(p2.y-p0.y); + + return r; +} + +/* return (p1-p0)x(p2-p0), the area of the parallelogram */ +static inline double dpara(dpoint_t p0, dpoint_t p1, dpoint_t p2) { + double x1, y1, x2, y2; + + x1 = p1.x-p0.x; + y1 = p1.y-p0.y; + x2 = p2.x-p0.x; + y2 = p2.y-p0.y; + + return x1*y2 - x2*y1; +} + +/* ddenom/dpara have the property that the square of radius 1 centered + at p1 intersects the line p0p2 iff |dpara(p0,p1,p2)| <= ddenom(p0,p2) */ +static inline double ddenom(dpoint_t p0, dpoint_t p2) { + point_t r = dorth_infty(p0, p2); + + return r.y*(p2.x-p0.x) - r.x*(p2.y-p0.y); +} + +/* return 1 if a <= b < c < a, in a cyclic sense (mod n) */ +static inline int cyclic(int a, int b, int c) { + if (a<=c) { + return (a<=b && b<c); + } else { + return (a<=b || b<c); + } +} + +/* determine the center and slope of the line i..j. Assume i<j. Needs + "sum" components of p to be set. */ +static void pointslope(privpath_t *pp, int i, int j, dpoint_t *ctr, dpoint_t *dir) { + /* assume i<j */ + + int n = pp->len; + sums_t *sums = pp->sums; + + double x, y, x2, xy, y2; + double k; + double a, b, c, lambda2, l; + int r=0; /* rotations from i to j */ + + while (j>=n) { + j-=n; + r+=1; + } + while (i>=n) { + i-=n; + r-=1; + } + while (j<0) { + j+=n; + r-=1; + } + while (i<0) { + i+=n; + r+=1; + } + + x = sums[j+1].x-sums[i].x+r*sums[n].x; + y = sums[j+1].y-sums[i].y+r*sums[n].y; + x2 = sums[j+1].x2-sums[i].x2+r*sums[n].x2; + xy = sums[j+1].xy-sums[i].xy+r*sums[n].xy; + y2 = sums[j+1].y2-sums[i].y2+r*sums[n].y2; + k = j+1-i+r*n; + + ctr->x = x/k; + ctr->y = y/k; + + a = (x2-(double)x*x/k)/k; + b = (xy-(double)x*y/k)/k; + c = (y2-(double)y*y/k)/k; + + lambda2 = (a+c+sqrt((a-c)*(a-c)+4*b*b))/2; /* larger e.value */ + + /* now find e.vector for lambda2 */ + a -= lambda2; + c -= lambda2; + + if (fabs(a) >= fabs(c)) { + l = sqrt(a*a+b*b); + if (l!=0) { + dir->x = -b/l; + dir->y = a/l; + } + } else { + l = sqrt(c*c+b*b); + if (l!=0) { + dir->x = -c/l; + dir->y = b/l; + } + } + if (l==0) { + dir->x = dir->y = 0; /* sometimes this can happen when k=4: + the two eigenvalues coincide */ + } +} + +/* the type of (affine) quadratic forms, represented as symmetric 3x3 + matrices. The value of the quadratic form at a vector (x,y) is v^t + Q v, where v = (x,y,1)^t. */ +typedef double quadform_t[3][3]; + +/* Apply quadratic form Q to vector w = (w.x,w.y) */ +static inline double quadform(quadform_t Q, dpoint_t w) { + double v[3]; + int i, j; + double sum; + + v[0] = w.x; + v[1] = w.y; + v[2] = 1; + sum = 0.0; + + for (i=0; i<3; i++) { + for (j=0; j<3; j++) { + sum += v[i] * Q[i][j] * v[j]; + } + } + return sum; +} + +/* calculate p1 x p2 */ +static inline int xprod(point_t p1, point_t p2) { + return p1.x*p2.y - p1.y*p2.x; +} + +/* calculate (p1-p0)x(p3-p2) */ +static inline double cprod(dpoint_t p0, dpoint_t p1, dpoint_t p2, dpoint_t p3) { + double x1, y1, x2, y2; + + x1 = p1.x - p0.x; + y1 = p1.y - p0.y; + x2 = p3.x - p2.x; + y2 = p3.y - p2.y; + + return x1*y2 - x2*y1; +} + +/* calculate (p1-p0)*(p2-p0) */ +static inline double iprod(dpoint_t p0, dpoint_t p1, dpoint_t p2) { + double x1, y1, x2, y2; + + x1 = p1.x - p0.x; + y1 = p1.y - p0.y; + x2 = p2.x - p0.x; + y2 = p2.y - p0.y; + + return x1*x2 + y1*y2; +} + +/* calculate (p1-p0)*(p3-p2) */ +static inline double iprod1(dpoint_t p0, dpoint_t p1, dpoint_t p2, dpoint_t p3) { + double x1, y1, x2, y2; + + x1 = p1.x - p0.x; + y1 = p1.y - p0.y; + x2 = p3.x - p2.x; + y2 = p3.y - p2.y; + + return x1*x2 + y1*y2; +} + +/* calculate distance between two points */ +static inline double ddist(dpoint_t p, dpoint_t q) { + return sqrt(sq(p.x-q.x)+sq(p.y-q.y)); +} + +/* calculate point of a bezier curve */ +static inline dpoint_t bezier(double t, dpoint_t p0, dpoint_t p1, dpoint_t p2, dpoint_t p3) { + double s = 1-t; + dpoint_t res; + + /* Note: a good optimizing compiler (such as gcc-3) reduces the + following to 16 multiplications, using common subexpression + elimination. */ + + res.x = s*s*s*p0.x + 3*(s*s*t)*p1.x + 3*(t*t*s)*p2.x + t*t*t*p3.x; + res.y = s*s*s*p0.y + 3*(s*s*t)*p1.y + 3*(t*t*s)*p2.y + t*t*t*p3.y; + + return res; +} + +/* calculate the point t in [0..1] on the (convex) bezier curve + (p0,p1,p2,p3) which is tangent to q1-q0. Return -1.0 if there is no + solution in [0..1]. */ +static double tangent(dpoint_t p0, dpoint_t p1, dpoint_t p2, dpoint_t p3, dpoint_t q0, dpoint_t q1) { + double A, B, C; /* (1-t)^2 A + 2(1-t)t B + t^2 C = 0 */ + double a, b, c; /* a t^2 + b t + c = 0 */ + double d, s, r1, r2; + + A = cprod(p0, p1, q0, q1); + B = cprod(p1, p2, q0, q1); + C = cprod(p2, p3, q0, q1); + + a = A - 2*B + C; + b = -2*A + 2*B; + c = A; + + d = b*b - 4*a*c; + + if (a==0 || d<0) { + return -1.0; + } + + s = sqrt(d); + + r1 = (-b + s) / (2 * a); + r2 = (-b - s) / (2 * a); + + if (r1 >= 0 && r1 <= 1) { + return r1; + } else if (r2 >= 0 && r2 <= 1) { + return r2; + } else { + return -1.0; + } +} + +/* ---------------------------------------------------------------------- */ +/* Preparation: fill in the sum* fields of a path (used for later + rapid summing). Return 0 on success, 1 with errno set on + failure. */ +static int calc_sums(privpath_t *pp) { + int i, x, y; + int n = pp->len; + + SAFE_MALLOC(pp->sums, pp->len+1, sums_t); + + /* origin */ + pp->x0 = pp->pt[0].x; + pp->y0 = pp->pt[0].y; + + /* preparatory computation for later fast summing */ + pp->sums[0].x2 = pp->sums[0].xy = pp->sums[0].y2 = pp->sums[0].x = pp->sums[0].y = 0; + for (i=0; i<n; i++) { + x = pp->pt[i].x - pp->x0; + y = pp->pt[i].y - pp->y0; + pp->sums[i+1].x = pp->sums[i].x + x; + pp->sums[i+1].y = pp->sums[i].y + y; + pp->sums[i+1].x2 = pp->sums[i].x2 + x*x; + pp->sums[i+1].xy = pp->sums[i].xy + x*y; + pp->sums[i+1].y2 = pp->sums[i].y2 + y*y; + } + return 0; + + malloc_error: + return 1; +} + +/* ---------------------------------------------------------------------- */ +/* Stage 1: determine the straight subpaths (Sec. 2.2.1). Fill in the + "lon" component of a path object (based on pt/len). For each i, + lon[i] is the furthest index such that a straight line can be drawn + from i to lon[i]. Return 1 on error with errno set, else 0. */ + +/* this algorithm depends on the fact that the existence of straight + subpaths is a triplewise property. I.e., there exists a straight + line through squares i0,...,in iff there exists a straight line + through i,j,k, for all i0<=i<j<k<=in. (Proof?) */ + +/* this implementation of calc_lon is O(n^2). It replaces an older + O(n^3) version. A "constraint" means that future points must + satisfy xprod(constraint[0], cur) >= 0 and xprod(constraint[1], + cur) <= 0. */ + +/* Remark for potrace 1.1: the current implementation of calc_lon is + more complex than the implementation found in potrace 1.0, but it + is considerably faster. The introduction of the "nc" data structure + means that we only have to test the constraints for "corner" + points. On a typical input file, this speeds up the calc_lon + function by a factor of 31.2, thereby decreasing its time share + within the overall potrace algorithm from 72.6% to 7.82%, and + speeding up the overall algorithm by a factor of 3.36. On another + input file, calc_lon was sped up by a factor of 6.7, decreasing its + time share from 51.4% to 13.61%, and speeding up the overall + algorithm by a factor of 1.78. In any case, the savings are + substantial. */ + +/* returns 0 on success, 1 on error with errno set */ +static int calc_lon(privpath_t *pp) { + point_t *pt = pp->pt; + int n = pp->len; + int i, j, k, k1; + int ct[4], dir; + point_t constraint[2]; + point_t cur; + point_t off; + int *pivk = NULL; /* pivk[n] */ + int *nc = NULL; /* nc[n]: next corner */ + point_t dk; /* direction of k-k1 */ + int a, b, c, d; + + SAFE_MALLOC(pivk, n, int); + SAFE_MALLOC(nc, n, int); + + /* initialize the nc data structure. Point from each point to the + furthest future point to which it is connected by a vertical or + horizontal segment. We take advantage of the fact that there is + always a direction change at 0 (due to the path decomposition + algorithm). But even if this were not so, there is no harm, as + in practice, correctness does not depend on the word "furthest" + above. */ + k = 0; + for (i=n-1; i>=0; i--) { + if (pt[i].x != pt[k].x && pt[i].y != pt[k].y) { + k = i+1; /* necessarily i<n-1 in this case */ + } + nc[i] = k; + } + + SAFE_MALLOC(pp->lon, n, int); + + /* determine pivot points: for each i, let pivk[i] be the furthest k + such that all j with i<j<k lie on a line connecting i,k. */ + + for (i=n-1; i>=0; i--) { + ct[0] = ct[1] = ct[2] = ct[3] = 0; + + /* keep track of "directions" that have occurred */ + dir = (3+3*(pt[mod(i+1,n)].x-pt[i].x)+(pt[mod(i+1,n)].y-pt[i].y))/2; + ct[dir]++; + + constraint[0].x = 0; + constraint[0].y = 0; + constraint[1].x = 0; + constraint[1].y = 0; + + /* find the next k such that no straight line from i to k */ + k = nc[i]; + k1 = i; + while (1) { + + dir = (3+3*sign(pt[k].x-pt[k1].x)+sign(pt[k].y-pt[k1].y))/2; + ct[dir]++; + + /* if all four "directions" have occurred, cut this path */ + if (ct[0] && ct[1] && ct[2] && ct[3]) { + pivk[i] = k1; + goto foundk; + } + + cur.x = pt[k].x - pt[i].x; + cur.y = pt[k].y - pt[i].y; + + /* see if current constraint is violated */ + if (xprod(constraint[0], cur) < 0 || xprod(constraint[1], cur) > 0) { + goto constraint_viol; + } + + /* else, update constraint */ + if (abs(cur.x) <= 1 && abs(cur.y) <= 1) { + /* no constraint */ + } else { + off.x = cur.x + ((cur.y>=0 && (cur.y>0 || cur.x<0)) ? 1 : -1); + off.y = cur.y + ((cur.x<=0 && (cur.x<0 || cur.y<0)) ? 1 : -1); + if (xprod(constraint[0], off) >= 0) { + constraint[0] = off; + } + off.x = cur.x + ((cur.y<=0 && (cur.y<0 || cur.x<0)) ? 1 : -1); + off.y = cur.y + ((cur.x>=0 && (cur.x>0 || cur.y<0)) ? 1 : -1); + if (xprod(constraint[1], off) <= 0) { + constraint[1] = off; + } + } + k1 = k; + k = nc[k1]; + if (!cyclic(k,i,k1)) { + break; + } + } + constraint_viol: + /* k1 was the last "corner" satisfying the current constraint, and + k is the first one violating it. We now need to find the last + point along k1..k which satisfied the constraint. */ + dk.x = sign(pt[k].x-pt[k1].x); + dk.y = sign(pt[k].y-pt[k1].y); + cur.x = pt[k1].x - pt[i].x; + cur.y = pt[k1].y - pt[i].y; + /* find largest integer j such that xprod(constraint[0], cur+j*dk) + >= 0 and xprod(constraint[1], cur+j*dk) <= 0. Use bilinearity + of xprod. */ + a = xprod(constraint[0], cur); + b = xprod(constraint[0], dk); + c = xprod(constraint[1], cur); + d = xprod(constraint[1], dk); + /* find largest integer j such that a+j*b>=0 and c+j*d<=0. This + can be solved with integer arithmetic. */ + j = INFTY; + if (b<0) { + j = floordiv(a,-b); + } + if (d>0) { + j = min(j, floordiv(-c,d)); + } + pivk[i] = mod(k1+j,n); + foundk: + ; + } /* for i */ + + /* clean up: for each i, let lon[i] be the largest k such that for + all i' with i<=i'<k, i'<k<=pivk[i']. */ + + j=pivk[n-1]; + pp->lon[n-1]=j; + for (i=n-2; i>=0; i--) { + if (cyclic(i+1,pivk[i],j)) { + j=pivk[i]; + } + pp->lon[i]=j; + } + + for (i=n-1; cyclic(mod(i+1,n),j,pp->lon[i]); i--) { + pp->lon[i] = j; + } + + free(pivk); + free(nc); + return 0; + + malloc_error: + free(pivk); + free(nc); + return 1; +} + + +/* ---------------------------------------------------------------------- */ +/* Stage 2: calculate the optimal polygon (Sec. 2.2.2-2.2.4). */ + +/* Auxiliary function: calculate the penalty of an edge from i to j in + the given path. This needs the "lon" and "sum*" data. */ + +static double penalty3(privpath_t *pp, int i, int j) { + int n = pp->len; + point_t *pt = pp->pt; + sums_t *sums = pp->sums; + + /* assume 0<=i<j<=n */ + double x, y, x2, xy, y2; + double k; + double a, b, c, s; + double px, py, ex, ey; + + int r=0; /* rotations from i to j */ + + if (j>=n) { + j-=n; + r+=1; + } + + x = sums[j+1].x-sums[i].x+r*sums[n].x; + y = sums[j+1].y-sums[i].y+r*sums[n].y; + x2 = sums[j+1].x2-sums[i].x2+r*sums[n].x2; + xy = sums[j+1].xy-sums[i].xy+r*sums[n].xy; + y2 = sums[j+1].y2-sums[i].y2+r*sums[n].y2; + k = j+1-i+r*n; + + px = (pt[i].x+pt[j].x)/2.0-pt[0].x; + py = (pt[i].y+pt[j].y)/2.0-pt[0].y; + ey = (pt[j].x-pt[i].x); + ex = -(pt[j].y-pt[i].y); + + a = ((x2-2*x*px)/k+px*px); + b = ((xy-x*py-y*px)/k+px*py); + c = ((y2-2*y*py)/k+py*py); + + s = ex*ex*a + 2*ex*ey*b + ey*ey*c; + + return sqrt(s); +} + +/* find the optimal polygon. Fill in the m and po components. Return 1 + on failure with errno set, else 0. Non-cyclic version: assumes i=0 + is in the polygon. Fixme: ### implement cyclic version. */ +static int bestpolygon(privpath_t *pp) +{ + int i, j, m, k; + int n = pp->len; + double *pen = NULL; /* pen[n+1]: penalty vector */ + int *prev = NULL; /* prev[n+1]: best path pointer vector */ + int *clip0 = NULL; /* clip0[n]: longest segment pointer, non-cyclic */ + int *clip1 = NULL; /* clip1[n+1]: backwards segment pointer, non-cyclic */ + int *seg0 = NULL; /* seg0[m+1]: forward segment bounds, m<=n */ + int *seg1 = NULL; /* seg1[m+1]: backward segment bounds, m<=n */ + double thispen; + double best; + int c; + + SAFE_MALLOC(pen, n+1, double); + SAFE_MALLOC(prev, n+1, int); + SAFE_MALLOC(clip0, n, int); + SAFE_MALLOC(clip1, n+1, int); + SAFE_MALLOC(seg0, n+1, int); + SAFE_MALLOC(seg1, n+1, int); + + /* calculate clipped paths */ + for (i=0; i<n; i++) { + c = mod(pp->lon[mod(i-1,n)]-1,n); + if (c == i) { + c = mod(i+1,n); + } + if (c < i) { + clip0[i] = n; + } else { + clip0[i] = c; + } + } + + /* calculate backwards path clipping, non-cyclic. j <= clip0[i] iff + clip1[j] <= i, for i,j=0..n. */ + j = 1; + for (i=0; i<n; i++) { + while (j <= clip0[i]) { + clip1[j] = i; + j++; + } + } + + /* calculate seg0[j] = longest path from 0 with j segments */ + i = 0; + for (j=0; i<n; j++) { + seg0[j] = i; + i = clip0[i]; + } + seg0[j] = n; + m = j; + + /* calculate seg1[j] = longest path to n with m-j segments */ + i = n; + for (j=m; j>0; j--) { + seg1[j] = i; + i = clip1[i]; + } + seg1[0] = 0; + + /* now find the shortest path with m segments, based on penalty3 */ + /* note: the outer 2 loops jointly have at most n interations, thus + the worst-case behavior here is quadratic. In practice, it is + close to linear since the inner loop tends to be short. */ + pen[0]=0; + for (j=1; j<=m; j++) { + for (i=seg1[j]; i<=seg0[j]; i++) { + best = -1; + for (k=seg0[j-1]; k>=clip1[i]; k--) { + thispen = penalty3(pp, k, i) + pen[k]; + if (best < 0 || thispen < best) { + prev[i] = k; + best = thispen; + } + } + pen[i] = best; + } + } + + pp->m = m; + SAFE_MALLOC(pp->po, m, int); + + /* read off shortest path */ + for (i=n, j=m-1; i>0; j--) { + i = prev[i]; + pp->po[j] = i; + } + + free(pen); + free(prev); + free(clip0); + free(clip1); + free(seg0); + free(seg1); + return 0; + + malloc_error: + free(pen); + free(prev); + free(clip0); + free(clip1); + free(seg0); + free(seg1); + return 1; +} + +/* ---------------------------------------------------------------------- */ +/* Stage 3: vertex adjustment (Sec. 2.3.1). */ + +/* Adjust vertices of optimal polygon: calculate the intersection of + the two "optimal" line segments, then move it into the unit square + if it lies outside. Return 1 with errno set on error; 0 on + success. */ + +static int adjust_vertices(privpath_t *pp) { + int m = pp->m; + int *po = pp->po; + int n = pp->len; + point_t *pt = pp->pt; + int x0 = pp->x0; + int y0 = pp->y0; + + dpoint_t *ctr = NULL; /* ctr[m] */ + dpoint_t *dir = NULL; /* dir[m] */ + quadform_t *q = NULL; /* q[m] */ + double v[3]; + double d; + int i, j, k, l; + dpoint_t s; + int r; + + SAFE_MALLOC(ctr, m, dpoint_t); + SAFE_MALLOC(dir, m, dpoint_t); + SAFE_MALLOC(q, m, quadform_t); + + r = privcurve_init(&pp->curve, m); + if (r) { + goto malloc_error; + } + + /* calculate "optimal" point-slope representation for each line + segment */ + for (i=0; i<m; i++) { + j = po[mod(i+1,m)]; + j = mod(j-po[i],n)+po[i]; + pointslope(pp, po[i], j, &ctr[i], &dir[i]); + } + + /* represent each line segment as a singular quadratic form; the + distance of a point (x,y) from the line segment will be + (x,y,1)Q(x,y,1)^t, where Q=q[i]. */ + for (i=0; i<m; i++) { + d = sq(dir[i].x) + sq(dir[i].y); + if (d == 0.0) { + for (j=0; j<3; j++) { + for (k=0; k<3; k++) { + q[i][j][k] = 0; + } + } + } else { + v[0] = dir[i].y; + v[1] = -dir[i].x; + v[2] = - v[1] * ctr[i].y - v[0] * ctr[i].x; + for (l=0; l<3; l++) { + for (k=0; k<3; k++) { + q[i][l][k] = v[l] * v[k] / d; + } + } + } + } + + /* now calculate the "intersections" of consecutive segments. + Instead of using the actual intersection, we find the point + within a given unit square which minimizes the square distance to + the two lines. */ + for (i=0; i<m; i++) { + quadform_t Q; + dpoint_t w; + double dx, dy; + double det; + double min, cand; /* minimum and candidate for minimum of quad. form */ + double xmin, ymin; /* coordinates of minimum */ + int z; + + /* let s be the vertex, in coordinates relative to x0/y0 */ + s.x = pt[po[i]].x-x0; + s.y = pt[po[i]].y-y0; + + /* intersect segments i-1 and i */ + + j = mod(i-1,m); + + /* add quadratic forms */ + for (l=0; l<3; l++) { + for (k=0; k<3; k++) { + Q[l][k] = q[j][l][k] + q[i][l][k]; + } + } + + while(1) { + /* minimize the quadratic form Q on the unit square */ + /* find intersection */ + +#ifdef HAVE_GCC_LOOP_BUG + /* work around gcc bug #12243 */ + free(NULL); +#endif + + det = Q[0][0]*Q[1][1] - Q[0][1]*Q[1][0]; + if (det != 0.0) { + w.x = (-Q[0][2]*Q[1][1] + Q[1][2]*Q[0][1]) / det; + w.y = ( Q[0][2]*Q[1][0] - Q[1][2]*Q[0][0]) / det; + break; + } + + /* matrix is singular - lines are parallel. Add another, + orthogonal axis, through the center of the unit square */ + if (Q[0][0]>Q[1][1]) { + v[0] = -Q[0][1]; + v[1] = Q[0][0]; + } else if (Q[1][1]) { + v[0] = -Q[1][1]; + v[1] = Q[1][0]; + } else { + v[0] = 1; + v[1] = 0; + } + d = sq(v[0]) + sq(v[1]); + v[2] = - v[1] * s.y - v[0] * s.x; + for (l=0; l<3; l++) { + for (k=0; k<3; k++) { + Q[l][k] += v[l] * v[k] / d; + } + } + } + dx = fabs(w.x-s.x); + dy = fabs(w.y-s.y); + if (dx <= .5 && dy <= .5) { + pp->curve.vertex[i].x = w.x+x0; + pp->curve.vertex[i].y = w.y+y0; + continue; + } + + /* the minimum was not in the unit square; now minimize quadratic + on boundary of square */ + min = quadform(Q, s); + xmin = s.x; + ymin = s.y; + + if (Q[0][0] == 0.0) { + goto fixx; + } + for (z=0; z<2; z++) { /* value of the y-coordinate */ + w.y = s.y-0.5+z; + w.x = - (Q[0][1] * w.y + Q[0][2]) / Q[0][0]; + dx = fabs(w.x-s.x); + cand = quadform(Q, w); + if (dx <= .5 && cand < min) { + min = cand; + xmin = w.x; + ymin = w.y; + } + } + fixx: + if (Q[1][1] == 0.0) { + goto corners; + } + for (z=0; z<2; z++) { /* value of the x-coordinate */ + w.x = s.x-0.5+z; + w.y = - (Q[1][0] * w.x + Q[1][2]) / Q[1][1]; + dy = fabs(w.y-s.y); + cand = quadform(Q, w); + if (dy <= .5 && cand < min) { + min = cand; + xmin = w.x; + ymin = w.y; + } + } + corners: + /* check four corners */ + for (l=0; l<2; l++) { + for (k=0; k<2; k++) { + w.x = s.x-0.5+l; + w.y = s.y-0.5+k; + cand = quadform(Q, w); + if (cand < min) { + min = cand; + xmin = w.x; + ymin = w.y; + } + } + } + + pp->curve.vertex[i].x = xmin + x0; + pp->curve.vertex[i].y = ymin + y0; + continue; + } + + free(ctr); + free(dir); + free(q); + return 0; + + malloc_error: + free(ctr); + free(dir); + free(q); + return 1; +} + +/* ---------------------------------------------------------------------- */ +/* Stage 4: smoothing and corner analysis (Sec. 2.3.3) */ + +/* Always succeeds and returns 0 */ +static int smooth(privcurve_t *curve, int sign, double alphamax) { + int m = curve->n; + + int i, j, k; + double dd, denom, alpha; + dpoint_t p2, p3, p4; + + if (sign == '-') { + /* reverse orientation of negative paths */ + for (i=0, j=m-1; i<j; i++, j--) { + dpoint_t tmp; + tmp = curve->vertex[i]; + curve->vertex[i] = curve->vertex[j]; + curve->vertex[j] = tmp; + } + } + + /* examine each vertex and find its best fit */ + for (i=0; i<m; i++) { + j = mod(i+1, m); + k = mod(i+2, m); + p4 = interval(1/2.0, curve->vertex[k], curve->vertex[j]); + + denom = ddenom(curve->vertex[i], curve->vertex[k]); + if (denom != 0.0) { + dd = dpara(curve->vertex[i], curve->vertex[j], curve->vertex[k]) / denom; + dd = fabs(dd); + alpha = dd>1 ? (1 - 1.0/dd) : 0; + alpha = alpha / 0.75; + } else { + alpha = 4/3.0; + } + curve->alpha0[j] = alpha; /* remember "original" value of alpha */ + + if (alpha > alphamax) { /* pointed corner */ + curve->tag[j] = POTRACE_CORNER; + curve->c[j][1] = curve->vertex[j]; + curve->c[j][2] = p4; + } else { + if (alpha < 0.55) { + alpha = 0.55; + } else if (alpha > 1) { + alpha = 1; + } + p2 = interval(.5+.5*alpha, curve->vertex[i], curve->vertex[j]); + p3 = interval(.5+.5*alpha, curve->vertex[k], curve->vertex[j]); + curve->tag[j] = POTRACE_CURVETO; + curve->c[j][0] = p2; + curve->c[j][1] = p3; + curve->c[j][2] = p4; + } + curve->alpha[j] = alpha; /* store the "cropped" value of alpha */ + curve->beta[j] = 0.5; + } + + return 0; +} + +/* ---------------------------------------------------------------------- */ +/* Stage 5: Curve optimization (Sec. 2.4) */ + +/* a private type for the result of opti_penalty */ +struct opti_s { + double pen; /* penalty */ + dpoint_t c[2]; /* curve parameters */ + double t, s; /* curve parameters */ + double alpha; /* curve parameter */ +}; +typedef struct opti_s opti_t; + +/* calculate best fit from i+.5 to j+.5. Assume i<j (cyclically). + Return 0 and set badness and parameters (alpha, beta), if + possible. Return 1 if impossible. */ +static int opti_penalty(privpath_t *pp, int i, int j, opti_t *res, double opttolerance, int *convc, double *areac) { + int m = pp->curve.n; + int k, k1, k2, conv, i1; + double area, alpha, d, d1, d2; + dpoint_t p0, p1, p2, p3, pt; + double A, R, A1, A2, A3, A4; + double s, t; + + /* check convexity, corner-freeness, and maximum bend < 179 degrees */ + + if (i==j) { /* sanity - a full loop can never be an opticurve */ + return 1; + } + + k = i; + i1 = mod(i+1, m); + k1 = mod(k+1, m); + conv = convc[k1]; + if (conv == 0) { + return 1; + } + d = ddist(pp->curve.vertex[i], pp->curve.vertex[i1]); + for (k=k1; k!=j; k=k1) { + k1 = mod(k+1, m); + k2 = mod(k+2, m); + if (convc[k1] != conv) { + return 1; + } + if (sign(cprod(pp->curve.vertex[i], pp->curve.vertex[i1], pp->curve.vertex[k1], pp->curve.vertex[k2])) != conv) { + return 1; + } + if (iprod1(pp->curve.vertex[i], pp->curve.vertex[i1], pp->curve.vertex[k1], pp->curve.vertex[k2]) < d * ddist(pp->curve.vertex[k1], pp->curve.vertex[k2]) * COS179) { + return 1; + } + } + + /* the curve we're working in: */ + p0 = pp->curve.c[mod(i,m)][2]; + p1 = pp->curve.vertex[mod(i+1,m)]; + p2 = pp->curve.vertex[mod(j,m)]; + p3 = pp->curve.c[mod(j,m)][2]; + + /* determine its area */ + area = areac[j] - areac[i]; + area -= dpara(pp->curve.vertex[0], pp->curve.c[i][2], pp->curve.c[j][2])/2; + if (i>=j) { + area += areac[m]; + } + + /* find intersection o of p0p1 and p2p3. Let t,s such that o = + interval(t,p0,p1) = interval(s,p3,p2). Let A be the area of the + triangle (p0,o,p3). */ + + A1 = dpara(p0, p1, p2); + A2 = dpara(p0, p1, p3); + A3 = dpara(p0, p2, p3); + /* A4 = dpara(p1, p2, p3); */ + A4 = A1+A3-A2; + + if (A2 == A1) { /* this should never happen */ + return 1; + } + + t = A3/(A3-A4); + s = A2/(A2-A1); + A = A2 * t / 2.0; + + if (A == 0.0) { /* this should never happen */ + return 1; + } + + R = area / A; /* relative area */ + alpha = 2 - sqrt(4 - R / 0.3); /* overall alpha for p0-o-p3 curve */ + + res->c[0] = interval(t * alpha, p0, p1); + res->c[1] = interval(s * alpha, p3, p2); + res->alpha = alpha; + res->t = t; + res->s = s; + + p1 = res->c[0]; + p2 = res->c[1]; /* the proposed curve is now (p0,p1,p2,p3) */ + + res->pen = 0; + + /* calculate penalty */ + /* check tangency with edges */ + for (k=mod(i+1,m); k!=j; k=k1) { + k1 = mod(k+1,m); + t = tangent(p0, p1, p2, p3, pp->curve.vertex[k], pp->curve.vertex[k1]); + if (t<-.5) { + return 1; + } + pt = bezier(t, p0, p1, p2, p3); + d = ddist(pp->curve.vertex[k], pp->curve.vertex[k1]); + if (d == 0.0) { /* this should never happen */ + return 1; + } + d1 = dpara(pp->curve.vertex[k], pp->curve.vertex[k1], pt) / d; + if (fabs(d1) > opttolerance) { + return 1; + } + if (iprod(pp->curve.vertex[k], pp->curve.vertex[k1], pt) < 0 || iprod(pp->curve.vertex[k1], pp->curve.vertex[k], pt) < 0) { + return 1; + } + res->pen += sq(d1); + } + + /* check corners */ + for (k=i; k!=j; k=k1) { + k1 = mod(k+1,m); + t = tangent(p0, p1, p2, p3, pp->curve.c[k][2], pp->curve.c[k1][2]); + if (t<-.5) { + return 1; + } + pt = bezier(t, p0, p1, p2, p3); + d = ddist(pp->curve.c[k][2], pp->curve.c[k1][2]); + if (d == 0.0) { /* this should never happen */ + return 1; + } + d1 = dpara(pp->curve.c[k][2], pp->curve.c[k1][2], pt) / d; + d2 = dpara(pp->curve.c[k][2], pp->curve.c[k1][2], pp->curve.vertex[k1]) / d; + d2 *= 0.75 * pp->curve.alpha[k1]; + if (d2 < 0) { + d1 = -d1; + d2 = -d2; + } + if (d1 < d2 - opttolerance) { + return 1; + } + if (d1 < d2) { + res->pen += sq(d1 - d2); + } + } + + return 0; +} + +/* optimize the path p, replacing sequences of Bezier segments by a + single segment when possible. Return 0 on success, 1 with errno set + on failure. */ +static int opticurve(privpath_t *pp, double opttolerance) { + int m = pp->curve.n; + int *pt = NULL; /* pt[m+1] */ + double *pen = NULL; /* pen[m+1] */ + int *len = NULL; /* len[m+1] */ + opti_t *opt = NULL; /* opt[m+1] */ + int om; + int i,j,r; + opti_t o; + dpoint_t p0; + int i1; + double area; + double alpha; + double *s = NULL; + double *t = NULL; + + int *convc = NULL; /* conv[m]: pre-computed convexities */ + double *areac = NULL; /* cumarea[m+1]: cache for fast area computation */ + + SAFE_MALLOC(pt, m+1, int); + SAFE_MALLOC(pen, m+1, double); + SAFE_MALLOC(len, m+1, int); + SAFE_MALLOC(opt, m+1, opti_t); + SAFE_MALLOC(convc, m, int); + SAFE_MALLOC(areac, m+1, double); + + /* pre-calculate convexity: +1 = right turn, -1 = left turn, 0 = corner */ + for (i=0; i<m; i++) { + if (pp->curve.tag[i] == POTRACE_CURVETO) { + convc[i] = sign(dpara(pp->curve.vertex[mod(i-1,m)], pp->curve.vertex[i], pp->curve.vertex[mod(i+1,m)])); + } else { + convc[i] = 0; + } + } + + /* pre-calculate areas */ + area = 0.0; + areac[0] = 0.0; + p0 = pp->curve.vertex[0]; + for (i=0; i<m; i++) { + i1 = mod(i+1, m); + if (pp->curve.tag[i1] == POTRACE_CURVETO) { + alpha = pp->curve.alpha[i1]; + area += 0.3*alpha*(4-alpha)*dpara(pp->curve.c[i][2], pp->curve.vertex[i1], pp->curve.c[i1][2])/2; + area += dpara(p0, pp->curve.c[i][2], pp->curve.c[i1][2])/2; + } + areac[i+1] = area; + } + + pt[0] = -1; + pen[0] = 0; + len[0] = 0; + + /* Fixme: we always start from a fixed point -- should find the best + curve cyclically ### */ + + for (j=1; j<=m; j++) { + /* calculate best path from 0 to j */ + pt[j] = j-1; + pen[j] = pen[j-1]; + len[j] = len[j-1]+1; + + for (i=j-2; i>=0; i--) { + r = opti_penalty(pp, i, mod(j,m), &o, opttolerance, convc, areac); + if (r) { + break; + } + if (len[j] > len[i]+1 || (len[j] == len[i]+1 && pen[j] > pen[i] + o.pen)) { + pt[j] = i; + pen[j] = pen[i] + o.pen; + len[j] = len[i] + 1; + opt[j] = o; + } + } + } + om = len[m]; + r = privcurve_init(&pp->ocurve, om); + if (r) { + goto malloc_error; + } + SAFE_MALLOC(s, om, double); + SAFE_MALLOC(t, om, double); + + j = m; + for (i=om-1; i>=0; i--) { + if (pt[j]==j-1) { + pp->ocurve.tag[i] = pp->curve.tag[mod(j,m)]; + pp->ocurve.c[i][0] = pp->curve.c[mod(j,m)][0]; + pp->ocurve.c[i][1] = pp->curve.c[mod(j,m)][1]; + pp->ocurve.c[i][2] = pp->curve.c[mod(j,m)][2]; + pp->ocurve.vertex[i] = pp->curve.vertex[mod(j,m)]; + pp->ocurve.alpha[i] = pp->curve.alpha[mod(j,m)]; + pp->ocurve.alpha0[i] = pp->curve.alpha0[mod(j,m)]; + pp->ocurve.beta[i] = pp->curve.beta[mod(j,m)]; + s[i] = t[i] = 1.0; + } else { + pp->ocurve.tag[i] = POTRACE_CURVETO; + pp->ocurve.c[i][0] = opt[j].c[0]; + pp->ocurve.c[i][1] = opt[j].c[1]; + pp->ocurve.c[i][2] = pp->curve.c[mod(j,m)][2]; + pp->ocurve.vertex[i] = interval(opt[j].s, pp->curve.c[mod(j,m)][2], pp->curve.vertex[mod(j,m)]); + pp->ocurve.alpha[i] = opt[j].alpha; + pp->ocurve.alpha0[i] = opt[j].alpha; + s[i] = opt[j].s; + t[i] = opt[j].t; + } + j = pt[j]; + } + + /* calculate beta parameters */ + for (i=0; i<om; i++) { + i1 = mod(i+1,om); + pp->ocurve.beta[i] = s[i] / (s[i] + t[i1]); + } + + free(pt); + free(pen); + free(len); + free(opt); + free(s); + free(t); + free(convc); + free(areac); + return 0; + + malloc_error: + free(pt); + free(pen); + free(len); + free(opt); + free(s); + free(t); + free(convc); + free(areac); + return 1; +} + +/* ---------------------------------------------------------------------- */ + +#define TRY(x) if (x) goto try_error + +/* return 0 on success, 1 on error with errno set. */ +int process_path(path_t *plist, const potrace_param_t *param, progress_t *progress) { + path_t *p; + double nn = 0, cn = 0; + + if (progress->callback) { + /* precompute task size for progress estimates */ + nn = 0; + list_forall (p, plist) { + nn += p->priv->len; + } + cn = 0; + } + + /* call downstream function with each path */ + list_forall (p, plist) { + TRY(calc_sums(p->priv)); + TRY(calc_lon(p->priv)); + TRY(bestpolygon(p->priv)); + TRY(adjust_vertices(p->priv)); + TRY(smooth(&p->priv->curve, p->sign, param->alphamax)); + if (param->opticurve) { + TRY(opticurve(p->priv, param->opttolerance)); + p->priv->fcurve = &p->priv->ocurve; + } else { + p->priv->fcurve = &p->priv->curve; + } + privcurve_to_curve(p->priv->fcurve, &p->curve); + + if (progress->callback) { + cn += p->priv->len; + progress_update(cn/nn, progress); + } + } + + progress_update(1.0, progress); + + return 0; + + try_error: + return 1; +} diff --git a/src/trace/potrace/trace.h b/src/trace/potrace/trace.h new file mode 100644 index 000000000..e6cac9d97 --- /dev/null +++ b/src/trace/potrace/trace.h @@ -0,0 +1,15 @@ +/* Copyright (C) 2001-2005 Peter Selinger. + This file is part of potrace. It is free software and it is covered + by the GNU General Public License. See the file COPYING for details. */ + +/* $Id$ */ + +#ifndef TRACE_H +#define TRACE_H + +#include "potracelib.h" +#include "progress.h" + +int process_path(path_t *plist, const potrace_param_t *param, progress_t *progress); + +#endif /* TRACE_H */ diff --git a/src/trace/trace.cpp b/src/trace/trace.cpp new file mode 100644 index 000000000..5431b4416 --- /dev/null +++ b/src/trace/trace.cpp @@ -0,0 +1,322 @@ +/* + * A generic interface for plugging different + * autotracers into Inkscape. + * + * Authors: + * Bob Jamison <rjamison@titan.com> + * + * Copyright (C) 2004 Bob Jamison + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + + + +#include "trace/potrace/inkscape-potrace.h" + +#include <inkscape.h> +#include <desktop-handles.h> +#include <document.h> +#include "message-stack.h" +#include <glibmm/i18n.h> +#include <selection.h> +#include <xml/repr.h> +#include "sp-item.h" +#include "sp-image.h" + +namespace Inkscape { + +namespace Trace { + +/** + * + */ +SPImage * +Tracer::getSelectedSPImage() +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (!desktop) + { + g_warning("Trace: No active desktop\n"); + return NULL; + } + + Inkscape::Selection *sel = SP_DT_SELECTION(desktop); + if (!sel) + { + char *msg = _("Select an <b>image</b> to trace"); + SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg); + //g_warning(msg); + return NULL; + } + + SPItem *item = sel->singleItem(); + if (!item) + { + char *msg = _("Select an <b>image</b> to trace"); //same as above + SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg); + //g_warning(msg); + return NULL; + } + + if (!SP_IS_IMAGE(item)) + { + char *msg = _("Select an <b>image</b> to trace"); + SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg); + //g_warning(msg); + return NULL; + } + + selectedItem = item; + + SPImage *img = SP_IMAGE(item); + + return img; + +} + + + +/** + * + */ +GdkPixbuf * +Tracer::getSelectedImage() +{ + + SPImage *img = getSelectedSPImage(); + if (!img) + return NULL; + + GdkPixbuf *pixbuf = img->pixbuf; + + return pixbuf; + +} + + + +//######################################################################### +//# T R A C E +//######################################################################### + + +/** + * Threaded method that does single bitmap--->path conversion + */ +void Tracer::traceThread() +{ + //## Remember. NEVER leave this method without setting + //## engine back to NULL + + //## Prepare our kill flag. We will watch this later to + //## see if the main thread wants us to stop + keepGoing = true; + + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (!desktop) + { + g_warning("Trace: No active desktop\n"); + return; + } + + Inkscape::Selection *selection = SP_DT_SELECTION (desktop); + + if (!SP_ACTIVE_DOCUMENT) + { + char *msg = _("Trace: No active document"); + SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg); + //g_warning(msg); + engine = NULL; + return; + } + SPDocument *doc = SP_ACTIVE_DOCUMENT; + sp_document_ensure_up_to_date(doc); + + + SPImage *img = getSelectedSPImage(); + if (!img || !selectedItem) + { + engine = NULL; + return; + } + + GdkPixbuf *pixbuf = img->pixbuf; + + if (!pixbuf) + { + char *msg = _("Trace: Image has no bitmap data"); + SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg); + //g_warning(msg); + engine = NULL; + return; + } + + int nrPaths; + TracingEngineResult *results = engine->trace(pixbuf, &nrPaths); + //printf("nrPaths:%d\n", nrPaths); + + //### Check if we should stop + if (!keepGoing || !results || nrPaths<1) + { + engine = NULL; + return; + } + + //### Get pointers to the <image> and its parent + Inkscape::XML::Node *imgRepr = SP_OBJECT(img)->repr; + Inkscape::XML::Node *par = sp_repr_parent(imgRepr); + + //### Get some information for the new transform() + double x = 0.0; + double y = 0.0; + double width = 0.0; + double height = 0.0; + double dval = 0.0; + + if (sp_repr_get_double(imgRepr, "x", &dval)) + x = dval; + if (sp_repr_get_double(imgRepr, "y", &dval)) + y = dval; + + if (sp_repr_get_double(imgRepr, "width", &dval)) + width = dval; + if (sp_repr_get_double(imgRepr, "height", &dval)) + height = dval; + + NR::Matrix trans(NR::translate(x, y)); + + double iwidth = (double)gdk_pixbuf_get_width(pixbuf); + double iheight = (double)gdk_pixbuf_get_height(pixbuf); + + double iwscale = width / iwidth; + double ihscale = height / iheight; + + NR::Matrix scal(NR::scale(iwscale, ihscale)); + + //# Convolve scale, translation, and the original transform + NR::Matrix tf(scal); + tf *= trans; + tf *= selectedItem->transform; + + + //#OK. Now let's start making new nodes + + Inkscape::XML::Node *groupRepr = NULL; + + //# if more than 1, make a <g>roup of <path>s + if (nrPaths > 1) + { + groupRepr = sp_repr_new("svg:g"); + par->addChild(groupRepr, imgRepr); + } + + long totalNodeCount = 0L; + + for (TracingEngineResult *result=results ; + result ; result=result->next) + { + totalNodeCount += result->getNodeCount(); + + Inkscape::XML::Node *pathRepr = sp_repr_new("svg:path"); + pathRepr->setAttribute("style", result->getStyle()); + pathRepr->setAttribute("d", result->getPathData()); + + if (nrPaths > 1) + groupRepr->addChild(pathRepr, NULL); + else + par->addChild(pathRepr, imgRepr); + + //### Apply the transform from the image to the new shape + SPObject *reprobj = doc->getObjectByRepr(pathRepr); + if (reprobj) + { + SPItem *newItem = SP_ITEM(reprobj); + sp_item_write_transform(newItem, pathRepr, tf, NULL); + } + if (nrPaths == 1) + { + selection->clear(); + selection->add(pathRepr); + } + Inkscape::GC::release(pathRepr); + } + + + delete results; + + // If we have a group, then focus on, then forget it + if (nrPaths > 1) + { + selection->clear(); + selection->add(groupRepr); + Inkscape::GC::release(groupRepr); + } + + //## inform the document, so we can undo + sp_document_done(doc); + + engine = NULL; + + char *msg = g_strdup_printf(_("Trace: Done. %ld nodes created"), totalNodeCount); + SP_DT_MSGSTACK(desktop)->flash(Inkscape::NORMAL_MESSAGE, msg); + g_free(msg); + +} + +/** + * Main tracing method + */ +void Tracer::trace(TracingEngine *theEngine) +{ + //Check if we are already running + if (engine) + return; + + engine = theEngine; + +#if HAVE_THREADS + //Ensure that thread support is running + if (!Glib::thread_supported()) + Glib::thread_init(); + + //Create our thread and run it + Glib::Thread::create( + sigc::mem_fun(*this, &Tracer::traceThread), false); +#else + traceThread(); +#endif + +} + + + + + +/** + * Abort the thread that is executing trace() + */ +void Tracer::abort() +{ + + //## Inform Trace's working thread + keepGoing = false; + + if (engine) + { + engine->abort(); + } + +} + + + +} // namespace Trace + +} // namespace Inkscape + + +//######################################################################### +//# E N D O F F I L E +//######################################################################### + diff --git a/src/trace/trace.h b/src/trace/trace.h new file mode 100644 index 000000000..bf4a345a6 --- /dev/null +++ b/src/trace/trace.h @@ -0,0 +1,246 @@ +/* + * A generic interface for plugging different + * autotracers into Inkscape. + * + * Authors: + * Bob Jamison <rjamison@titan.com> + * + * Copyright (C) 2004 Bob Jamison + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ +#ifndef __TRACE_H__ +#define __TRACE_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifdef HAVE_STDLIB_H +# include <stdlib.h> +#endif + +#ifdef HAVE_STRING_H +# include <string.h> +#endif + +#include <gdk/gdkpixbuf.h> + + +struct SPImage; +struct SPItem; + +namespace Inkscape { + +namespace Trace { + + + +/** + * + */ +class TracingEngineResult +{ + +public: + + /** + * + */ + TracingEngineResult(char *theStyle, char *thePathData, long theNodeCount) + { + next = NULL; + style = strdup(theStyle); + pathData = strdup(thePathData); + nodeCount = theNodeCount; + } + + /** + * + */ + virtual ~TracingEngineResult() + { + if (next) + delete next; + if (style) + free(style); + if (pathData) + free(pathData); + } + + + /** + * + */ + char *getStyle() + { return style; } + + /** + * + */ + char *getPathData() + { return pathData; } + + /** + * + */ + long getNodeCount() + { return nodeCount; } + + /** + * + */ + TracingEngineResult *next; + +private: + + char *style; + + char *pathData; + + long nodeCount; + +}; + + + +/** + * + */ +class TracingEngine +{ + + public: + + /** + * + */ + TracingEngine() + {} + + /** + * + */ + virtual ~TracingEngine() + {} + + /** + * This is the working method of this interface, and all + * implementing classes. Take a GdkPixbuf, trace it, and + * return a style attribute and the path data that is + * compatible with the d="" attribute + * of an SVG <path> element. + */ + virtual TracingEngineResult *trace(GdkPixbuf *pixbuf, int *nrPaths) + { return NULL; } + + + /** + * Abort the thread that is executing getPathDataFromPixbuf() + */ + virtual void abort() + {} + + + +};//class TracingEngine + + + + + + + + + +/** + * This simple class allows a generic wrapper around a given + * TracingEngine object. Its purpose is to provide a gateway + * to a variety of tracing engines, while maintaining a + * consistent interface. + */ +class Tracer +{ + +public: + + + /** + * + */ + Tracer() + { + engine = NULL; + selectedItem = NULL; + } + + + + /** + * + */ + ~Tracer() + {} + + + /** + * A convenience method to allow other software to 'see' the + * same image that this class sees. + */ + GdkPixbuf *getSelectedImage(); + + /** + * This is the main working method. Trace the selected image, if + * any, and create a <path> element from it, inserting it into + * the current document. + */ + void trace(TracingEngine *engine); + + + /** + * Abort the thread that is executing convertImageToPath() + */ + void abort(); + + +private: + + /** + * This is the single path code that is called by its counterpart above. + */ + void traceThread(); + + /** + * This is true during execution. Setting it to false (like abort() + * does) should inform the threaded code that it needs to stop + */ + bool keepGoing; + + /** + * During tracing, this is Non-null, and refers to the + * engine that is currently doing the tracing. + */ + TracingEngine *engine; + + SPImage *getSelectedSPImage(); + + SPItem *selectedItem; + + +};//class Tracer + + + + +} // namespace Trace + +} // namespace Inkscape + + + +#endif //__TRACE_H__ + +//######################################################################### +//# E N D O F F I L E +//######################################################################### + |
