CDE
This commit is contained in:
parent
518da4a923
commit
764da6cbbb
6144 changed files with 83610 additions and 11 deletions
8
Task/Color-quantization/0DESCRIPTION
Normal file
8
Task/Color-quantization/0DESCRIPTION
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{{task}}
|
||||
[[file:quantum_frog.png|full color|thumb|left|192px]][[file:quantum_frog_16.png|Example: Gimp 16 color|thumb|left|192px]]
|
||||
[[wp:Color_quantization|Color quantization]] is the process of reducing number of colors used in an image while trying to maintain the visual appearance of the original image. In general, it is a form of [[wp:Cluster_analysis|cluster analysis]], if each RGB color value is considered as a coordinate triple in the 3D colorspace. There are some well know algorithms [http://web.cs.wpi.edu/~matt/courses/cs563/talks/color_quant/CQindex.html], each with its own advantages and drawbacks.
|
||||
|
||||
'''Task''': Take an RGB color image and reduce its colors to some smaller number (< 256). For this task, use the frog as input and reduce colors to 16, and output the resulting colors. The chosen colors should be adaptive to the input image, meaning you should ''not'' use a fixed palette such as Web colors or Windows system palette. Dithering is not required.
|
||||
|
||||
Note: the funny color bar on top of the frog image is intentional.
|
||||
<br clear=left>
|
||||
2
Task/Color-quantization/1META.yaml
Normal file
2
Task/Color-quantization/1META.yaml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
---
|
||||
note: Raster graphics operations
|
||||
111
Task/Color-quantization/C/color-quantization.c
Normal file
111
Task/Color-quantization/C/color-quantization.c
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
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;
|
||||
};
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/* adding a color triple to octree */
|
||||
oct_node node_insert(oct_node root, unsigned char *pix)
|
||||
{
|
||||
# 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;
|
||||
}
|
||||
|
||||
/* remove a node in octree and add its count and colors to parent node. */
|
||||
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;
|
||||
}
|
||||
|
||||
/* Building an octree and keep leaf nodes in a bin heap. Afterwards remove first node
|
||||
in heap and fold it into its parent node (which may now be added to heap), until heap
|
||||
contains required number of colors. */
|
||||
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);
|
||||
}
|
||||
165
Task/Color-quantization/D/color-quantization.d
Normal file
165
Task/Color-quantization/D/color-quantization.d
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
import core.stdc.stdio, std.stdio, std.ascii, std.algorithm, std.math,
|
||||
std.typecons, std.range, std.conv, std.string, bitmap;
|
||||
|
||||
struct Col { float r, g, b; }
|
||||
alias Tuple!(Col, float, Col, Col[]) Cluster;
|
||||
enum Axis { R, G, B }
|
||||
|
||||
int round(in float x) /*pure*/ nothrow {
|
||||
return cast(int)floor(x + 0.5); // Not pure.
|
||||
}
|
||||
|
||||
RGB roundRGB(in Col c) /*pure*/ nothrow {
|
||||
return RGB(cast(ubyte)round(c.r), // Not pure.
|
||||
cast(ubyte)round(c.g),
|
||||
cast(ubyte)round(c.b));
|
||||
}
|
||||
|
||||
Col meanRGB(Col[] pxList) pure nothrow {
|
||||
static Col addRGB(in Col c1, in Col c2) pure nothrow {
|
||||
return Col(c1.r + c2.r, c1.g + c2.g, c1.b + c2.b);
|
||||
}
|
||||
immutable Col tot = reduce!addRGB(Col(0,0,0), pxList);
|
||||
immutable int n = pxList.walkLength();
|
||||
return Col(tot.r / n, tot.g / n, tot.b / n);
|
||||
}
|
||||
|
||||
Tuple!(Col, Col) extrems(/*in*/ Col[] lst) pure nothrow {
|
||||
immutable minRGB = Col(float.infinity,
|
||||
float.infinity,
|
||||
float.infinity);
|
||||
immutable maxRGB = Col(-float.infinity,
|
||||
-float.infinity,
|
||||
-float.infinity);
|
||||
static Col f1(in Col c1, in Col c2) pure nothrow {
|
||||
return Col(min(c1.r, c2.r), min(c1.g, c2.g), min(c1.b, c2.b));
|
||||
}
|
||||
static Col f2(in Col c1, in Col c2) pure nothrow {
|
||||
return Col(max(c1.r, c2.r), max(c1.g, c2.g), max(c1.b, c2.b));
|
||||
}
|
||||
return typeof(return)(reduce!f1(minRGB, lst),
|
||||
reduce!f2(maxRGB, lst));
|
||||
}
|
||||
|
||||
Tuple!(float, Col) volumeAndDims(/*in*/ Col[] lst) pure nothrow {
|
||||
immutable e = extrems(lst);
|
||||
immutable Col r = Col(e[1].r - e[0].r,
|
||||
e[1].g - e[0].g,
|
||||
e[1].b - e[0].b);
|
||||
return typeof(return)(r.r * r.g * r.b, r);
|
||||
}
|
||||
|
||||
Cluster makeCluster(Col[] pixel_list) pure nothrow {
|
||||
immutable vol_dims = volumeAndDims(pixel_list);
|
||||
immutable int len = pixel_list.length;
|
||||
return Cluster(meanRGB(pixel_list),
|
||||
len * vol_dims[0],
|
||||
vol_dims[1],
|
||||
pixel_list);
|
||||
}
|
||||
|
||||
Axis largestAxis(in Col c) pure nothrow {
|
||||
static int fcmp(in float a, in float b) pure nothrow {
|
||||
return (a > b) ? 1 : (a < b ? -1 : 0);
|
||||
}
|
||||
immutable int r1 = fcmp(c.r, c.g);
|
||||
immutable int r2 = fcmp(c.r, c.b);
|
||||
if (r1 == 1 && r2 == 1) return Axis.R;
|
||||
if (r1 == -1 && r2 == 1) return Axis.G;
|
||||
if (r1 == 1 && r2 == -1) return Axis.B;
|
||||
return (fcmp(c.g, c.b) == 1) ? Axis.G : Axis.B;
|
||||
}
|
||||
|
||||
Tuple!(Cluster, Cluster) subdivide(in Col c, in float nVolProd,
|
||||
in Col vol, Col[] pixels)
|
||||
/*pure*/ nothrow {
|
||||
bool delegate(immutable Col c) /*pure*/ nothrow partFunc;
|
||||
final switch (largestAxis(vol)) {
|
||||
case Axis.R: partFunc = c1 => c1.r < c.r; break;
|
||||
case Axis.G: partFunc = c1 => c1.g < c.g; break;
|
||||
case Axis.B: partFunc = c1 => c1.b < c.b; break;
|
||||
}
|
||||
Col[] px2 = pixels.partition!partFunc; // Not pure.
|
||||
Col[] px1 = pixels[0 .. $ - px2.length];
|
||||
return typeof(return)(makeCluster(px1), makeCluster(px2));
|
||||
}
|
||||
|
||||
Image!RGB colorQuantize(in Image!RGB img, in int n)
|
||||
/*pure*/ nothrow {
|
||||
immutable int width = img.nx;
|
||||
immutable int height = img.ny;
|
||||
|
||||
auto cols = new Col[width * height];
|
||||
foreach (immutable i, ref c; img.image)
|
||||
cols[i] = Col(c.r, c.g, c.b);
|
||||
Cluster[] clusters = [makeCluster(cols)];
|
||||
|
||||
immutable Col dumb = Col(0.0, 0.0, 0.0);
|
||||
Cluster unused = Cluster(dumb,
|
||||
-float.infinity,
|
||||
dumb, (Col[]).init);
|
||||
|
||||
while (clusters.length < n) {
|
||||
Cluster cl = reduce!((c1,c2) => c1[1] > c2[1] ? c1 : c2)
|
||||
(unused, clusters);
|
||||
clusters = [subdivide(cl.tupleof).tupleof] ~
|
||||
remove!(c => c == cl, SwapStrategy.unstable)(clusters);
|
||||
}
|
||||
|
||||
static uint RGB2uint(in RGB c) pure nothrow {
|
||||
uint r;
|
||||
r |= c.r;
|
||||
r |= c.g << 8;
|
||||
r |= c.b << 16;
|
||||
return r;
|
||||
}
|
||||
|
||||
uint[uint] pixMap; // faster than RGB[RGB]
|
||||
ubyte[4] u4a, u4b;
|
||||
foreach (const cluster; clusters) {
|
||||
immutable ubyteMean = RGB2uint(roundRGB(cluster[0]));
|
||||
foreach (immutable col; cluster[3])
|
||||
pixMap[RGB2uint(roundRGB(col))] = ubyteMean;
|
||||
}
|
||||
|
||||
auto result = new Image!RGB;
|
||||
result.allocate(height, width);
|
||||
|
||||
static RGB uintToRGB(in uint c) pure nothrow {
|
||||
return RGB( c & 0xFF,
|
||||
(c >> 8) & 0xFF,
|
||||
(c >> 16) & 0xFF);
|
||||
}
|
||||
|
||||
foreach (immutable i; 0 .. height * width) {
|
||||
immutable u3a = RGB(img.image[i].r,
|
||||
img.image[i].g,
|
||||
img.image[i].b);
|
||||
result.image[i] = uintToRGB(pixMap[RGB2uint(u3a)]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void main(in string[] args) {
|
||||
string fileName;
|
||||
int nCols;
|
||||
switch (args.length) {
|
||||
case 1:
|
||||
fileName = "quantum_frog.ppm";
|
||||
nCols = 16;
|
||||
break;
|
||||
case 3:
|
||||
fileName = args[1];
|
||||
nCols = to!int(args[2]);
|
||||
break;
|
||||
default:
|
||||
writeln("Usage: color_quantization image.ppm ncolors");
|
||||
return;
|
||||
}
|
||||
|
||||
auto im = new Image!RGB;
|
||||
im.loadPPM6(fileName);
|
||||
const imq = colorQuantize(im, nCols);
|
||||
imq.savePPM6("quantum_frog_quantized.ppm");
|
||||
}
|
||||
326
Task/Color-quantization/Racket/color-quantization.rkt
Normal file
326
Task/Color-quantization/Racket/color-quantization.rkt
Normal file
|
|
@ -0,0 +1,326 @@
|
|||
#lang racket/base
|
||||
(require racket/class
|
||||
racket/draw)
|
||||
|
||||
;; This is an implementation of the Octree Quantization algorithm. This implementation
|
||||
;; follows the sketch in:
|
||||
;;
|
||||
;; Dean Clark. Color Quantization using Octrees. Dr. Dobbs Portal, January 1, 1996.
|
||||
;; http://www.ddj.com/184409805
|
||||
;;
|
||||
;; This code is adapted from the color quantizer in the implementation of Racket's
|
||||
;; file/gif standard library.
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
|
||||
;; To view an example of the quantizer, run the following test submodule
|
||||
;; in DrRacket:
|
||||
(module+ test
|
||||
(require racket/block net/url)
|
||||
|
||||
(define frog
|
||||
(block
|
||||
(define url (string->url "http://rosettacode.org/mw/images/3/3f/Quantum_frog.png"))
|
||||
(define frog-ip (get-pure-port url))
|
||||
(define bitmap (make-object bitmap% frog-ip))
|
||||
(close-input-port frog-ip)
|
||||
bitmap))
|
||||
|
||||
;; Display the original:
|
||||
(print frog)
|
||||
;; And the quantized version (16 colors):
|
||||
(print (quantize-bitmap frog 16)))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
|
||||
;; quantize-bitmap: bitmap positive-number -> bitmap
|
||||
;; Given a bitmap, returns a new bitmap quantized to, at most, n colors.
|
||||
(define (quantize-bitmap bm n)
|
||||
(let* ([width (send bm get-width)]
|
||||
[height (send bm get-height)]
|
||||
[len (* width height 4)]
|
||||
[source-buffer (make-bytes len)]
|
||||
[_ (send bm get-argb-pixels 0 0 width height source-buffer)]
|
||||
[an-octree (make-octree-from-argb source-buffer n)]
|
||||
[dest-buffer (make-bytes len)])
|
||||
(let quantize-bitmap-loop ([i 0])
|
||||
(when (< i len)
|
||||
(let* ([i+1 (+ i 1)]
|
||||
[i+2 (+ i 2)]
|
||||
[i+3 (+ i 3)]
|
||||
[a (bytes-ref source-buffer i)]
|
||||
[r (bytes-ref source-buffer i+1)]
|
||||
[g (bytes-ref source-buffer i+2)]
|
||||
[b (bytes-ref source-buffer i+3)])
|
||||
(cond
|
||||
[(alpha-opaque? a)
|
||||
(let-values ([(new-r new-g new-b)
|
||||
(octree-lookup an-octree r g b)])
|
||||
(bytes-set! dest-buffer i 255)
|
||||
(bytes-set! dest-buffer i+1 new-r)
|
||||
(bytes-set! dest-buffer i+2 new-g)
|
||||
(bytes-set! dest-buffer i+3 new-b))]
|
||||
[else
|
||||
(bytes-set! dest-buffer i 0)
|
||||
(bytes-set! dest-buffer i+1 0)
|
||||
(bytes-set! dest-buffer i+2 0)
|
||||
(bytes-set! dest-buffer i+3 0)]))
|
||||
(quantize-bitmap-loop (+ i 4))))
|
||||
(let* ([new-bm (make-object bitmap% width height)]
|
||||
[dc (make-object bitmap-dc% new-bm)])
|
||||
(send dc set-argb-pixels 0 0 width height dest-buffer)
|
||||
(send dc set-bitmap #f)
|
||||
new-bm)))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
;; make-octree-from-argb: bytes positive-number -> octree
|
||||
;; Constructs an octree ready to quantize the colors from an-argb.
|
||||
(define (make-octree-from-argb an-argb n)
|
||||
(unless (> n 0)
|
||||
(raise-type-error 'make-octree-from-argb "positive number" n))
|
||||
(let ([an-octree (new-octree)]
|
||||
[len (bytes-length an-argb)])
|
||||
(let make-octree-loop ([i 0])
|
||||
(when (< i len)
|
||||
(let ([a (bytes-ref an-argb i)]
|
||||
[r (bytes-ref an-argb (+ i 1))]
|
||||
[g (bytes-ref an-argb (+ i 2))]
|
||||
[b (bytes-ref an-argb (+ i 3))])
|
||||
(when (alpha-opaque? a)
|
||||
(octree-insert-color! an-octree r g b)
|
||||
(let reduction-loop ()
|
||||
(when (> (octree-leaf-count an-octree) n)
|
||||
(octree-reduce! an-octree)
|
||||
(reduction-loop)))))
|
||||
(make-octree-loop (+ i 4))))
|
||||
(octree-finalize! an-octree)
|
||||
an-octree))
|
||||
|
||||
|
||||
;; alpha-opaque? byte -> boolean
|
||||
;; Returns true if the alpha value is considered opaque.
|
||||
(define (alpha-opaque? a)
|
||||
(>= a 128))
|
||||
|
||||
|
||||
|
||||
;; The maximum level height of an octree.
|
||||
(define MAX-LEVEL 7)
|
||||
|
||||
|
||||
|
||||
;; A color is a (vector byte byte byte)
|
||||
|
||||
;; An octree is a:
|
||||
(define-struct octree (root ; node
|
||||
leaf-count ; number
|
||||
reduction-heads ; (vectorof (or/c node #f))
|
||||
palette) ; (vectorof (or/c color #f))
|
||||
#:mutable)
|
||||
;; reduction-heads is used to accelerate the search for a reduction candidate.
|
||||
|
||||
|
||||
;; A subtree node is a:
|
||||
(define-struct node (leaf? ; bool
|
||||
npixels ; number -- number of pixels this subtree node represents
|
||||
redsum ; number
|
||||
greensum ; number
|
||||
bluesum ; number
|
||||
children ; (vectorof (or/c #f node))
|
||||
next ; (or/c #f node)
|
||||
palette-index) ; (or/c #f byte?)
|
||||
#:mutable)
|
||||
;; node-next is used to accelerate the search for a reduction candidate.
|
||||
|
||||
|
||||
;; new-octree: -> octree
|
||||
(define (new-octree)
|
||||
(let* ([root-node (make-node #f ;; not a leaf
|
||||
0 ;; no pixels under us yet
|
||||
0 ;; red sum
|
||||
0 ;; green sum
|
||||
0 ;; blue sum
|
||||
(make-vector 8 #f) ;; no children so far
|
||||
#f ;; next
|
||||
#f ;; palette-index
|
||||
)]
|
||||
[an-octree
|
||||
(make-octree root-node
|
||||
0 ; no leaves so far
|
||||
(make-vector (add1 MAX-LEVEL) #f) ; no reductions so far
|
||||
(make-vector 256 #(0 0 0)))]) ; the palette
|
||||
;; Although we'll almost never reduce to this level, initialize the first
|
||||
;; reducible node to the root, for completeness sake.
|
||||
(vector-set! (octree-reduction-heads an-octree) 0 root-node)
|
||||
an-octree))
|
||||
|
||||
|
||||
;; rgb->index: natural-number byte byte byte -> octet
|
||||
;; Given a level and an (r,g,b) triplet, returns an octet that can be used
|
||||
;; as an index into our octree structure.
|
||||
(define (rgb->index level r g b)
|
||||
(bitwise-ior (bitwise-and 4 (arithmetic-shift r (- level 5)))
|
||||
(bitwise-and 2 (arithmetic-shift g (- level 6)))
|
||||
(bitwise-and 1 (arithmetic-shift b (- level 7)))))
|
||||
|
||||
|
||||
;; octree-insert-color!: octree byte byte byte -> void
|
||||
;; Accumulates a new r,g,b triplet into the octree.
|
||||
(define (octree-insert-color! an-octree r g b)
|
||||
(node-insert-color! (octree-root an-octree) an-octree r g b 0))
|
||||
|
||||
|
||||
;; node-insert-color!: node octree byte byte byte natural-number -> void
|
||||
;; Adds a color to the node subtree. While we hit #f, we create new nodes.
|
||||
;; If we hit an existing leaf, we accumulate our color into it.
|
||||
(define (node-insert-color! a-node an-octree r g b level)
|
||||
(let insert-color-loop ([a-node a-node]
|
||||
[level level])
|
||||
(cond [(node-leaf? a-node)
|
||||
;; update the leaf with the new color
|
||||
(set-node-npixels! a-node (add1 (node-npixels a-node)))
|
||||
(set-node-redsum! a-node (+ (node-redsum a-node) r))
|
||||
(set-node-greensum! a-node (+ (node-greensum a-node) g))
|
||||
(set-node-bluesum! a-node (+ (node-bluesum a-node) b))]
|
||||
[else
|
||||
;; create the child node if necessary
|
||||
(let ([index (rgb->index level r g b)])
|
||||
(unless (vector-ref (node-children a-node) index)
|
||||
(let ([new-node (make-node (= level MAX-LEVEL) ; leaf?
|
||||
0 ; npixels
|
||||
0 ; redsum
|
||||
0 ; greensum
|
||||
0 ; bluesum
|
||||
(make-vector 8 #f) ; no children yet
|
||||
#f ; and no next node yet
|
||||
#f ; or palette index
|
||||
)])
|
||||
(vector-set! (node-children a-node) index new-node)
|
||||
(cond
|
||||
[(= level MAX-LEVEL)
|
||||
;; If we added a leaf, mark it in the octree.
|
||||
(set-octree-leaf-count! an-octree
|
||||
(add1 (octree-leaf-count an-octree)))]
|
||||
[else
|
||||
;; Attach the node as a reducible node if it's interior.
|
||||
(set-node-next!
|
||||
new-node (vector-ref (octree-reduction-heads an-octree)
|
||||
(add1 level)))
|
||||
(vector-set! (octree-reduction-heads an-octree)
|
||||
(add1 level)
|
||||
new-node)])))
|
||||
;; and recur on the child node.
|
||||
(insert-color-loop (vector-ref (node-children a-node) index)
|
||||
(add1 level)))])))
|
||||
|
||||
|
||||
;; octree-reduce!: octree -> void
|
||||
;; Reduces one of the subtrees, collapsing the children into a single node.
|
||||
(define (octree-reduce! an-octree)
|
||||
(node-reduce! (pop-reduction-candidate! an-octree) an-octree))
|
||||
|
||||
|
||||
;; node-reduce!: node octree -> void
|
||||
;; Reduces the interior node.
|
||||
(define (node-reduce! a-node an-octree)
|
||||
(for ([child (in-vector (node-children a-node))]
|
||||
#:when child)
|
||||
(set-node-npixels! a-node (+ (node-npixels a-node)
|
||||
(node-npixels child)))
|
||||
(set-node-redsum! a-node (+ (node-redsum a-node)
|
||||
(node-redsum child)))
|
||||
(set-node-greensum! a-node (+ (node-greensum a-node)
|
||||
(node-greensum child)))
|
||||
(set-node-bluesum! a-node (+ (node-bluesum a-node)
|
||||
(node-bluesum child)))
|
||||
(set-octree-leaf-count! an-octree (sub1 (octree-leaf-count an-octree))))
|
||||
(set-node-leaf?! a-node #t)
|
||||
(set-octree-leaf-count! an-octree (add1 (octree-leaf-count an-octree))))
|
||||
|
||||
|
||||
;; find-reduction-candidate!: octree -> node
|
||||
;; Returns a bottom-level interior node for reduction. Also takes the
|
||||
;; candidate out of the conceptual queue of reduction candidates.
|
||||
(define (pop-reduction-candidate! an-octree)
|
||||
(let loop ([i MAX-LEVEL])
|
||||
(cond
|
||||
[(vector-ref (octree-reduction-heads an-octree) i)
|
||||
=>
|
||||
(lambda (candidate-node)
|
||||
(when (> i 0)
|
||||
(vector-set! (octree-reduction-heads an-octree) i
|
||||
(node-next candidate-node)))
|
||||
candidate-node)]
|
||||
[else
|
||||
(loop (sub1 i))])))
|
||||
|
||||
|
||||
;; octree-finalize!: octree -> void
|
||||
;; Finalization does a few things:
|
||||
;; * Walks through the octree and reduces any interior nodes with just one leaf child.
|
||||
;; Optimizes future lookups.
|
||||
;; * Fills in the palette of the octree and the palette indexes of the leaf nodes.
|
||||
;; * Note: palette index 0 is always reserved for the transparent color.
|
||||
(define (octree-finalize! an-octree)
|
||||
;; Collapse one-leaf interior nodes.
|
||||
(let loop ([a-node (octree-root an-octree)])
|
||||
(for ([child (in-vector (node-children a-node))]
|
||||
#:when (and child (not (node-leaf? child))))
|
||||
(loop child)
|
||||
(when (interior-node-one-leaf-child? a-node)
|
||||
(node-reduce! a-node an-octree))))
|
||||
|
||||
;; Attach palette entries.
|
||||
(let ([current-palette-index 1])
|
||||
(let loop ([a-node (octree-root an-octree)])
|
||||
(cond [(node-leaf? a-node)
|
||||
(let ([n (node-npixels a-node)])
|
||||
(vector-set! (octree-palette an-octree) current-palette-index
|
||||
(vector (quotient (node-redsum a-node) n)
|
||||
(quotient (node-greensum a-node) n)
|
||||
(quotient (node-bluesum a-node) n)))
|
||||
(set-node-palette-index! a-node current-palette-index)
|
||||
(set! current-palette-index (add1 current-palette-index)))]
|
||||
[else
|
||||
(for ([child (in-vector (node-children a-node))]
|
||||
#:when child)
|
||||
(loop child))]))))
|
||||
|
||||
|
||||
;; interior-node-one-leaf-child?: node -> boolean
|
||||
(define (interior-node-one-leaf-child? a-node)
|
||||
(let ([child-list (filter values (vector->list (node-children a-node)))])
|
||||
(and (= (length child-list) 1)
|
||||
(node-leaf? (car child-list)))))
|
||||
|
||||
|
||||
;; octree-lookup: octree byte byte byte -> (values byte byte byte)
|
||||
;; Returns the palettized color.
|
||||
(define (octree-lookup an-octree r g b)
|
||||
(let* ([index (node-lookup-index (octree-root an-octree) an-octree r g b 0)]
|
||||
[vec (vector-ref (octree-palette an-octree) index)])
|
||||
(values (vector-ref vec 0)
|
||||
(vector-ref vec 1)
|
||||
(vector-ref vec 2))))
|
||||
|
||||
|
||||
|
||||
;; node-lookup-index: node byte byte byte natural-number -> byte
|
||||
;; Returns the palettized color index.
|
||||
(define (node-lookup-index a-node an-octree r g b level)
|
||||
(let loop ([a-node a-node]
|
||||
[level level])
|
||||
(if (node-leaf? a-node)
|
||||
(node-palette-index a-node)
|
||||
(let ([child (vector-ref (node-children a-node) (rgb->index level r g b))])
|
||||
(unless child
|
||||
(error 'node-lookup-index
|
||||
"color (~a, ~a, ~a) not previously inserted"
|
||||
r g b))
|
||||
(loop child (add1 level))))))
|
||||
78
Task/Color-quantization/Tcl/color-quantization-1.tcl
Normal file
78
Task/Color-quantization/Tcl/color-quantization-1.tcl
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
package require Tcl 8.6
|
||||
package require Tk
|
||||
|
||||
proc makeCluster {pixels} {
|
||||
set rmin [set rmax [lindex $pixels 0 0]]
|
||||
set gmin [set gmax [lindex $pixels 0 1]]
|
||||
set bmin [set bmax [lindex $pixels 0 2]]
|
||||
set rsum [set gsum [set bsum 0]]
|
||||
foreach p $pixels {
|
||||
lassign $p r g b
|
||||
if {$r<$rmin} {set rmin $r} elseif {$r>$rmax} {set rmax $r}
|
||||
if {$g<$gmin} {set gmin $g} elseif {$g>$gmax} {set gmax $g}
|
||||
if {$b<$bmin} {set bmin $b} elseif {$b>$bmax} {set bmax $b}
|
||||
incr rsum $r
|
||||
incr gsum $g
|
||||
incr bsum $b
|
||||
}
|
||||
set n [llength $pixels]
|
||||
list [expr {double($n)*($rmax-$rmin)*($gmax-$gmin)*($bmax-$bmin)}] \
|
||||
[list [expr {$rsum/$n}] [expr {$gsum/$n}] [expr {$bsum/$n}]] \
|
||||
[list [expr {$rmax-$rmin}] [expr {$gmax-$gmin}] [expr {$bmax-$bmin}]] \
|
||||
$pixels
|
||||
}
|
||||
|
||||
proc colorQuant {img n} {
|
||||
set width [image width $img]
|
||||
set height [image height $img]
|
||||
# Extract the pixels from the image
|
||||
for {set x 0} {$x < $width} {incr x} {
|
||||
for {set y 0} {$y < $height} {incr y} {
|
||||
lappend pixels [$img get $x $y]
|
||||
}
|
||||
}
|
||||
# Divide pixels into clusters
|
||||
for {set cs [list [makeCluster $pixels]]} {[llength $cs] < $n} {} {
|
||||
set cs [lsort -real -index 0 $cs]
|
||||
lassign [lindex $cs end] score centroid volume pixels
|
||||
lassign $centroid cr cg cb
|
||||
lassign $volume vr vg vb
|
||||
while 1 {
|
||||
set p1 [set p2 {}]
|
||||
if {$vr>$vg && $vr>$vb} {
|
||||
foreach p $pixels {
|
||||
if {[lindex $p 0]<$cr} {lappend p1 $p} {lappend p2 $p}
|
||||
}
|
||||
} elseif {$vg>$vb} {
|
||||
foreach p $pixels {
|
||||
if {[lindex $p 1]<$cg} {lappend p1 $p} {lappend p2 $p}
|
||||
}
|
||||
} else {
|
||||
foreach p $pixels {
|
||||
if {[lindex $p 2]<$cb} {lappend p1 $p} {lappend p2 $p}
|
||||
}
|
||||
}
|
||||
if {[llength $p1] && [llength $p2]} break
|
||||
# Partition failed! Perturb partition point away from the centroid and try again
|
||||
set cr [expr {$cr + 20*rand() - 10}]
|
||||
set cg [expr {$cg + 20*rand() - 10}]
|
||||
set cb [expr {$cb + 20*rand() - 10}]
|
||||
}
|
||||
set cs [lreplace $cs end end [makeCluster $p1] [makeCluster $p2]]
|
||||
}
|
||||
# Produce map from pixel values to quantized values
|
||||
foreach c $cs {
|
||||
set centroid [format "#%02x%02x%02x" {*}[lindex $c 1]]
|
||||
foreach p [lindex $c end] {
|
||||
set map($p) $centroid
|
||||
}
|
||||
}
|
||||
# Remap the source image
|
||||
set newimg [image create photo -width $width -height $height]
|
||||
for {set x 0} {$x < $width} {incr x} {
|
||||
for {set y 0} {$y < $height} {incr y} {
|
||||
$newimg put $map([$img get $x $y]) -to $x $y
|
||||
}
|
||||
}
|
||||
return $newimg
|
||||
}
|
||||
5
Task/Color-quantization/Tcl/color-quantization-2.tcl
Normal file
5
Task/Color-quantization/Tcl/color-quantization-2.tcl
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
set src [image create photo -file quantum_frog.png]
|
||||
set dst [colorQuant $src 16]
|
||||
# Save as GIF now that quantization is done, then exit explicitly (no GUI desired)
|
||||
$dst write quantum_frog_compressed.gif
|
||||
exit
|
||||
Loading…
Add table
Add a link
Reference in a new issue