Color quantization/C

From Rosetta Code
Revision as of 00:31, 13 August 2011 by rosettacode>Ledrug (Created page with "This is a complete program that takes a PPM P6 image and a number, then writes out the image reduced to the number of colors to out.ppm. <lang c>#include <stdio.h> #include <stdl...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

This is a complete program that takes a PPM P6 image and a number, then writes out the image reduced to the number of colors to out.ppm. <lang c>#include <stdio.h>

  1. include <stdlib.h>
  2. include <fcntl.h>
  3. include <unistd.h>
  4. include <ctype.h>
  5. include <string.h>
  6. include <stdint.h>

typedef struct { int w, h; unsigned char *pix; } image_t, *image;

int write_ppm(image im, char *fn) { FILE *fp = fopen(fn, "w"); if (!fp) return 0; fprintf(fp, "P6\n%d %d\n255\n", im->w, im->h); fwrite(im->pix, 1, 3 * im->w * im->h, fp); fclose(fp); return 1; }

image img_new(int w, int h) { image im = malloc(sizeof(image_t) + h * w * 3); im->w = w; im->h = h; im->pix = (unsigned char *)(im + 1); return im; }

int read_num(FILE *f) { int n; while (!fscanf(f, "%d ", &n)) { if ((n = fgetc(f)) == '#') { while ((n = fgetc(f)) != '\n') if (n == EOF) return 0; } else return 0; } return n; }

image read_ppm(char *fn) { FILE *fp = fopen(fn, "r"); int w, h, maxval; image im = 0; if (!fp) return 0;

if (fgetc(fp) != 'P' || fgetc(fp) != '6' || !isspace(fgetc(fp))) goto bail;

w = read_num(fp); h = read_num(fp); maxval = read_num(fp); if (!w || !h || !maxval) goto bail;

im = img_new(w, h); fread(im->pix, 1, 3 * w * h, fp); bail: if (fp) fclose(fp); return im; }


  1. define ON_INHEAP 1

typedef struct oct_node_t oct_node_t, *oct_node; struct oct_node_t{ /* sum of all colors represented by this node. 64 bit in case of HUGE image */ uint64_t r, g, b; int count, heap_idx; oct_node kids[8], parent; unsigned char n_kids, kid_idx, flags, depth; };

typedef struct { int alloc, n; oct_node* buf; } node_heap;

/* cmp function that decides the ordering in the heap. This is how we determine

  which octree node to fold next, the heart of the algorithm. */

inline int cmp_node(oct_node a, oct_node b) { if (a->n_kids < b->n_kids) return -1; if (a->n_kids > b->n_kids) return 1;

int ac = a->count * (1 + a->kid_idx) >> a->depth; int bc = b->count * (1 + b->kid_idx) >> b->depth; return ac < bc ? -1 : ac > bc; }

void down_heap(node_heap *h, oct_node p) { int n = p->heap_idx, m; while (1) { m = n * 2; if (m >= h->n) break; if (m + 1 < h->n && cmp_node(h->buf[m], h->buf[m + 1]) > 0) m++;

if (cmp_node(p, h->buf[m]) <= 0) break;

h->buf[n] = h->buf[m]; h->buf[n]->heap_idx = n; n = m; } h->buf[n] = p; p->heap_idx = n; }

void up_heap(node_heap *h, oct_node p) { int n = p->heap_idx; oct_node prev;

while (n > 1) { prev = h->buf[n / 2]; if (cmp_node(p, prev) >= 0) break;

h->buf[n] = prev; prev->heap_idx = n; n /= 2; } h->buf[n] = p; p->heap_idx = n; }

void heap_add(node_heap *h, oct_node p) { if ((p->flags & ON_INHEAP)) { down_heap(h, p); up_heap(h, p); return; }

p->flags |= ON_INHEAP; if (!h->n) h->n = 1; if (h->n >= h->alloc) { while (h->n >= h->alloc) h->alloc += 1024; h->buf = realloc(h->buf, sizeof(oct_node) * h->alloc); }

p->heap_idx = h->n; h->buf[h->n++] = p; up_heap(h, p); }

oct_node pop_heap(node_heap *h) { if (h->n <= 1) return 0;

oct_node ret = h->buf[1]; h->buf[1] = h->buf[--h->n];

h->buf[h->n] = 0;

h->buf[1]->heap_idx = 1; down_heap(h, h->buf[1]);

return ret; }

static oct_node pool = 0; oct_node node_new(unsigned char idx, unsigned char depth, oct_node p) { static int len = 0; if (len <= 1) { oct_node p = calloc(sizeof(oct_node_t), 2048); p->parent = pool; pool = p; len = 2047; }

oct_node x = pool + len--; x->kid_idx = idx; x->depth = depth; x->parent = p; if (p) p->n_kids++; return x; }

void node_free() { oct_node p; while (pool) { p = pool->parent; free(pool); pool = p; } }

oct_node node_insert(oct_node root, unsigned char *pix) {

  1. define OCT_DEPTH 8

/* 8: number of significant bits used for tree. It's probably good enough for most images to use a value of 5. This affects how many nodes eventually end up in the tree and heap, thus smaller values helps with both speed and memory. */

unsigned char i, bit, depth = 0; for (bit = 1 << 7; ++depth < OCT_DEPTH; bit >>= 1) { i = !!(pix[1] & bit) * 4 + !!(pix[0] & bit) * 2 + !!(pix[2] & bit); if (!root->kids[i]) root->kids[i] = node_new(i, depth, root);

root = root->kids[i]; }

root->r += pix[0]; root->g += pix[1]; root->b += pix[2]; root->count++; return root; }

oct_node node_fold(oct_node p) { if (p->n_kids) abort(); oct_node q = p->parent; q->count += p->count;

q->r += p->r; q->g += p->g; q->b += p->b; q->n_kids --; q->kids[p->kid_idx] = 0; return q; }

/* traverse the octree just like construction, but this time we replace the pixel

  color with color stored in the tree node */

void color_replace(oct_node root, unsigned char *pix) { unsigned char i, bit;

for (bit = 1 << 7; bit; bit >>= 1) { i = !!(pix[1] & bit) * 4 + !!(pix[0] & bit) * 2 + !!(pix[2] & bit); if (!root->kids[i]) break; root = root->kids[i]; }

pix[0] = root->r; pix[1] = root->g; pix[2] = root->b; }

void color_quant(image im, int n_colors) { int i; unsigned char *pix = im->pix; node_heap heap = { 0, 0, 0 };

oct_node root = node_new(0, 0, 0), got; for (i = 0; i < im->w * im->h; i++, pix += 3) heap_add(&heap, node_insert(root, pix));

while (heap.n > n_colors + 1) heap_add(&heap, node_fold(pop_heap(&heap)));

double c; for (i = 1; i < heap.n; i++) { got = heap.buf[i]; c = got->count; got->r = got->r / c + .5; got->g = got->g / c + .5; got->b = got->b / c + .5; printf("%2d | %3llu %3llu %3llu (%d pixels)\n", i, got->r, got->g, got->b, got->count); }

for (i = 0, pix = im->pix; i < im->w * im->h; i++, pix += 3) color_replace(root, pix);

node_free(); free(heap.buf); }

int main(int c, char *v[]) { if (c < 3) { fprintf(stderr, "usage: %s ppm_file n_colors\n", v[0]); return 0; }

c = atoi(v[2]) ? : 16; /* GCC extension */

image im = read_ppm(v[1]); color_quant(im, c); write_ppm(im, "out.pnm"); free(im);

return 0; }</lang>