Data update

This commit is contained in:
Ingy döt Net 2026-04-30 12:34:36 -04:00
parent 4bb20c9b71
commit cbaf4c4b64
12390 changed files with 318560 additions and 27248 deletions

View file

@ -1,79 +1,79 @@
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;
/* 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;
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;
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. */
# 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);
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 = root->kids[i];
}
root->r += pix[0];
root->g += pix[1];
root->b += pix[2];
root->count++;
return root;
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;
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;
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;
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];
}
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;
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
@ -81,31 +81,31 @@ void color_replace(oct_node root, unsigned char *pix)
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 };
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));
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)));
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);
}
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);
for (i = 0, pix = im->pix; i < im->w * im->h; i++, pix += 3)
color_replace(root, pix);
node_free();
free(heap.buf);
node_free();
free(heap.buf);
}

View file

@ -11,74 +11,74 @@ import javax.imageio.ImageIO;
public final class ColorQuantization {
public static void main(String[] aArgs) throws IOException {
BufferedImage original = ImageIO.read( new File("quantum_frog.png") );
final int width = original.getWidth();
final int height = original.getHeight();
int[] originalPixels = original.getRGB(0, 0, width, height, null, 0, width);
List<Item> bucket = new ArrayList<Item>();
for ( int i = 0; i < originalPixels.length; i++ ) {
bucket.add( new Item(new Color(originalPixels[i]), i) );
}
int[] resultPixels = new int[originalPixels.length];
medianCut(bucket, 4, resultPixels);
BufferedImage result = new BufferedImage(width, height, original.getType());
result.setRGB(0, 0, width, height, resultPixels, 0, width);
ImageIO.write(result, "png", new File("Quantum_frog16Java.png"));
System.out.println("The 16 colors used in Red, Green, Blue format are:");
for ( Color color : colorsUsed ) {
System.out.println("(" + color.getRed() + ", " + color.getGreen() + ", " + color.getBlue() + ")");
}
}
private static void medianCut(List<Item> aBucket, int aDepth, int[] aResultPixels) {
if ( aDepth == 0 ) {
quantize(aBucket, aResultPixels);
return;
}
int[] minimumValue = new int[] { 256, 256, 256 };
int[] maximumValue = new int[] { 0, 0, 0 };
for ( Item item : aBucket ) {
for ( Channel channel : Channel.values() ) {
int value = item.getPrimary(channel);
if ( value < minimumValue[channel.index] ) {
minimumValue[channel.index] = value;
}
if ( value > maximumValue[channel.index] ) {
maximumValue[channel.index] = value;
}
}
}
int[] valueRange = new int[] { maximumValue[Channel.RED.index] - minimumValue[Channel.RED.index],
maximumValue[Channel.GREEN.index] - minimumValue[Channel.GREEN.index],
maximumValue[Channel.BLUE.index] - minimumValue[Channel.BLUE.index] };
Channel selectedChannel = ( valueRange[Channel.RED.index] >= valueRange[Channel.GREEN.index] )
? ( valueRange[Channel.RED.index] >= valueRange[Channel.BLUE.index] ) ? Channel.RED : Channel.BLUE
: ( valueRange[Channel.GREEN.index] >= valueRange[Channel.BLUE.index] ) ? Channel.GREEN : Channel.BLUE;
Collections.sort(aBucket, switch(selectedChannel) {
case RED -> redComparator;
case GREEN -> greenComparator;
case BLUE -> blueComparator; });
final int medianIndex = aBucket.size() / 2;
medianCut(new ArrayList<Item>(aBucket.subList(0, medianIndex)), aDepth - 1, aResultPixels);
medianCut(new ArrayList<Item>(aBucket.subList(medianIndex, aBucket.size())), aDepth - 1, aResultPixels);
}
private static void quantize(List<Item> aBucket, int[] aResultPixels) {
int[] means = new int[Channel.values().length];
public static void main(String[] aArgs) throws IOException {
BufferedImage original = ImageIO.read( new File("quantum_frog.png") );
final int width = original.getWidth();
final int height = original.getHeight();
int[] originalPixels = original.getRGB(0, 0, width, height, null, 0, width);
List<Item> bucket = new ArrayList<Item>();
for ( int i = 0; i < originalPixels.length; i++ ) {
bucket.add( new Item(new Color(originalPixels[i]), i) );
}
int[] resultPixels = new int[originalPixels.length];
medianCut(bucket, 4, resultPixels);
BufferedImage result = new BufferedImage(width, height, original.getType());
result.setRGB(0, 0, width, height, resultPixels, 0, width);
ImageIO.write(result, "png", new File("Quantum_frog16Java.png"));
System.out.println("The 16 colors used in Red, Green, Blue format are:");
for ( Color color : colorsUsed ) {
System.out.println("(" + color.getRed() + ", " + color.getGreen() + ", " + color.getBlue() + ")");
}
}
private static void medianCut(List<Item> aBucket, int aDepth, int[] aResultPixels) {
if ( aDepth == 0 ) {
quantize(aBucket, aResultPixels);
return;
}
int[] minimumValue = new int[] { 256, 256, 256 };
int[] maximumValue = new int[] { 0, 0, 0 };
for ( Item item : aBucket ) {
for ( Channel channel : Channel.values() ) {
int value = item.getPrimary(channel);
if ( value < minimumValue[channel.index] ) {
minimumValue[channel.index] = value;
}
if ( value > maximumValue[channel.index] ) {
maximumValue[channel.index] = value;
}
}
}
int[] valueRange = new int[] { maximumValue[Channel.RED.index] - minimumValue[Channel.RED.index],
maximumValue[Channel.GREEN.index] - minimumValue[Channel.GREEN.index],
maximumValue[Channel.BLUE.index] - minimumValue[Channel.BLUE.index] };
Channel selectedChannel = ( valueRange[Channel.RED.index] >= valueRange[Channel.GREEN.index] )
? ( valueRange[Channel.RED.index] >= valueRange[Channel.BLUE.index] ) ? Channel.RED : Channel.BLUE
: ( valueRange[Channel.GREEN.index] >= valueRange[Channel.BLUE.index] ) ? Channel.GREEN : Channel.BLUE;
Collections.sort(aBucket, switch(selectedChannel) {
case RED -> redComparator;
case GREEN -> greenComparator;
case BLUE -> blueComparator; });
final int medianIndex = aBucket.size() / 2;
medianCut(new ArrayList<Item>(aBucket.subList(0, medianIndex)), aDepth - 1, aResultPixels);
medianCut(new ArrayList<Item>(aBucket.subList(medianIndex, aBucket.size())), aDepth - 1, aResultPixels);
}
private static void quantize(List<Item> aBucket, int[] aResultPixels) {
int[] means = new int[Channel.values().length];
for ( Item item : aBucket ) {
for ( Channel channel : Channel.values() ) {
means[channel.index] += item.getPrimary(channel);
}
for ( Channel channel : Channel.values() ) {
means[channel.index] += item.getPrimary(channel);
}
}
for ( Channel channel : Channel.values() ) {
@ -89,39 +89,39 @@ public final class ColorQuantization {
colorsUsed.add(color);
for ( Item item : aBucket ) {
aResultPixels[item.aIndex] = color.getRGB();
aResultPixels[item.aIndex] = color.getRGB();
}
}
private enum Channel {
RED(0), GREEN(1), BLUE(2);
private Channel(int aIndex) {
index = aIndex;
}
private final int index;
}
private record Item(Color aColor, Integer aIndex) {
public int getPrimary(Channel aChannel) {
return switch(aChannel) {
case RED -> aColor.getRed();
case GREEN -> aColor.getGreen();
case BLUE -> aColor.getBlue();
};
}
}
private static Comparator<Item> redComparator =
(one, two) -> Integer.compare(one.aColor.getRed(), two.aColor.getRed());
private static Comparator<Item> greenComparator =
(one, two) -> Integer.compare(one.aColor.getGreen(), two.aColor.getGreen());
private static Comparator<Item> blueComparator =
(one, two) -> Integer.compare(one.aColor.getBlue(), two.aColor.getBlue());
private static List<Color> colorsUsed = new ArrayList<Color>();
}
private enum Channel {
RED(0), GREEN(1), BLUE(2);
private Channel(int aIndex) {
index = aIndex;
}
private final int index;
}
private record Item(Color aColor, Integer aIndex) {
public int getPrimary(Channel aChannel) {
return switch(aChannel) {
case RED -> aColor.getRed();
case GREEN -> aColor.getGreen();
case BLUE -> aColor.getBlue();
};
}
}
private static Comparator<Item> redComparator =
(one, two) -> Integer.compare(one.aColor.getRed(), two.aColor.getRed());
private static Comparator<Item> greenComparator =
(one, two) -> Integer.compare(one.aColor.getGreen(), two.aColor.getGreen());
private static Comparator<Item> blueComparator =
(one, two) -> Integer.compare(one.aColor.getBlue(), two.aColor.getBlue());
private static List<Color> colorsUsed = new ArrayList<Color>();
}

View file

@ -1,6 +1,6 @@
from PIL import Image
if __name__=="__main__":
im = Image.open("frog.png")
im2 = im.quantize(16)
im2.show()
im = Image.open("frog.png")
im2 = im.quantize(16)
im2.show()

View file

@ -0,0 +1,76 @@
Rebol [
title: "Rosetta code: Color quantization (Median cut)"
file: %Color_quantization-median_cut.r3
url: https://rosettacode.org/wiki/Color_quantization
]
median-cut: function [
"Reduces image to N colors using the median cut algorithm"
img [image! file! url!] "Source image to quantize (modified)"
n-colors [integer!] "Number of requested colors"
][
unless image? img [img: load img]
colors: make block! n-colors
;; Build a flat bucket block [clr1 idx1 clr2 idx2 ...]
;; where each clr is a tuple! and idx is the pixel's position in the image
bucket: make block! img/size/x * img/size/y
idx: 1 foreach clr img [ repend bucket [clr ++ idx] ]
;; Pre-sort once — all buckets remain sorted after every split
sort/skip bucket 2
;; Start with one bucket containing all pixels
buckets: reduce [bucket]
;; Keep splitting until we have enough buckets for the requested color count
while [n-colors > length? buckets] [
;; Take the first bucket (always the largest due to append order)
bucket: take buckets
;; Split at the upper median to ensure the most even division of pairs
med: 2 * round/ceiling (length? bucket) / 2 / 2
;; Append the upper median and the remainder back as two new buckets
append/only buckets take/part bucket med
append/only buckets bucket
]
;; Quantize each bucket
foreach bucket buckets [
;; Averages all colors in a bucket
r-sum: 0 g-sum: 0 b-sum: 0
foreach [clr idx] bucket [
r-sum: r-sum + clr/1
g-sum: g-sum + clr/2
b-sum: b-sum + clr/3
]
n: 0.5 * length? bucket ;; number of colors in the bucket
mean-color: to tuple! reduce [
r-sum / n
g-sum / n
b-sum / n
]
;; Write the mean color back to every pixel in this bucket
foreach [clr idx] bucket [ img/:idx: mean-color ]
;; Keep the mean color
append colors mean-color
]
;; Return image and colors
reduce [img colors]
]
;; Download the original image if does not exists.
unless exists? %Quantum_frog.png [
write %quantum_frog.png
read https://static.wikitide.net/rosettacodewiki/3/3f/Quantum_frog.png
]
foreach [colors output-name] [
16 %Quantum_frog-16-colors.png
3 %Quantum_frog-3-colors.png
][
;; Quantize to N colors
time: delta-time [
set [img colors] median-cut %Quantum_frog.png colors
]
print ["Time to compute:" time]
;; Display all used colors
print ["The" length? colors "colors:"]
probe new-line/all colors true
save output-name img ;; Save the result
browse output-name ;; Display the image in a browser
]

View file

@ -0,0 +1,272 @@
Rebol [
title: "Rosetta code: Color quantization (Octree)"
file: %Color_quantization-octree.r3
url: https://rosettacode.org/wiki/Color_quantization
]
octree-quantize: function/with [
img [image! file! url!]
n-colors [integer!]
][
unless image? img [img: load img]
quantizer: make-octree-quantizer
;; Downsample to 25% of pixels for tree building - dramatically faster with
;; minimal palette quality loss since distribution is well represented by a sample
sm-img: resize/filter img 25% 'box
;; First pass - count color frequencies
freq: make map! 32768
foreach color sm-img [
freq/:color: either p: freq/:color [p + 1][1]
]
;; Second pass - only add frequent colors to the octree
foreach color sm-img [
if freq/:color > 1 [
add-color quantizer color
]
]
palette: make-palette quantizer n-colors
;; Map each pixel to its nearest palette color.
forall img [
idx: get-palette-index quantizer img/1
img/1: palette/:idx
]
reduce [img palette]
][
;; Maximum octree depth - at depth 8 each node represents a unique 24-bit color
;; (8 bits per channel, one bit inspected per level)
make-octree-node: func [level parent /local node] [
node: object [
;; Accumulated channel sums and pixel count for average color calculation
red: green: blue: pixel-count: 0
palette-index: 0 ;; assigned during make-palette
children: array 8 ;; up to 8 children, one per RGB bit at this level
]
;; Leaf-level nodes (level 7) are not registered since they won't be merged
if level < 7 [
add-level-node parent level node
]
node
]
; Iteratively collect all leaf nodes (nodes with pixel-count > 0) under `node`
get-leaf-nodes: function [node] [
leaf-nodes: copy []
stack: clear []
foreach child node/children [
if child [append stack child]
]
while [not empty? stack] [
current: take stack
either current/pixel-count > 0 [
append leaf-nodes current
][
foreach child current/children [
if child [append stack child]
]
]
]
leaf-nodes
]
;; Walk the tree to find the palette index assigned to `color`.
;; At a leaf, return its index. Otherwise, compute which child to descend into
;; by extracting one bit per channel at the current level to form a 3-bit index.
;; If the exact child was pruned during palette reduction, fall back to the first
;; available sibling.
get-palette-index-for-color: function [node color level] [
; Walk the tree iteratively until we reach a leaf (pixel-count > 0)
while [node/pixel-count = 0] [
index: 1
mask: 128 >> level
unless zero? color/1 & mask [index: index + 4] ; bit 2
unless zero? color/2 & mask [index: index + 2] ; bit 1
unless zero? color/3 & mask [index: index + 1] ; bit 0
++ level
;; If exact child was pruned, fall back to first available child
either node/children/:index [
node: node/children/:index
][
foreach child node/children [
if child [node: child break]
]
]
]
node/palette-index
]
;; Merge all children of `node` into the node itself, making it a leaf.
;; Accumulates children's color sums and pixel counts into the parent.
;; Returns the net reduction in leaf count: (number of children merged) - 1,
;; because the parent itself becomes a new leaf.
remove-leaves: function [node] [
result: 0
foreach child node/children [
if child [
node/red: node/red + child/red
node/green: node/green + child/green
node/blue: node/blue + child/blue
node/pixel-count: node/pixel-count + child/pixel-count
++ result
]
]
result - 1
]
;; Compute the average color for a leaf node by dividing accumulated
;; channel sums by the total pixel count
get-color: func [node] [
make tuple! reduce [
node/red / node/pixel-count
node/green / node/pixel-count
node/blue / node/pixel-count
]
]
get-pixel-count: func [node] [
result: 0
stack: clear []
foreach child node/children [if child [append stack child]]
while [not empty? stack] [
if current: take stack [
either current/pixel-count > 0 [
result: result + current/pixel-count
][
append stack current/children
]
]
]
result
]
;; Create and initialise a new octree quantizer.
;; `levels` holds one block of nodes per depth level, used during palette reduction.
;; `leaf-count` tracks the current number of leaves without requiring a tree walk.
make-octree-quantizer: func [/local quantizer] [
quantizer: context [
levels: array/initial 8 []
root: none
leaf-count: 0
;; caches:
color-node: make map! 16384
color-index: make map! 65536
]
quantizer/root: make-octree-node 0 quantizer
quantizer
]
;; Register `node` at `level` in the quantizer so it can be found during reduction
add-level-node: func [quantizer level node] [
append (pickz quantizer/levels level) node
]
get-leaves: func [quantizer] [
get-leaf-nodes quantizer/root
]
;; Add `color` to the octree. Uses color-node cache to skip tree
;; traversal for already-seen colors, only walking the tree on first
;; encounter of each unique color. At each level, a 3-bit index derived
;; from one bit per RGB channel selects the child. A new leaf is counted
;; the first time a node receives a pixel at max depth.
add-color: function [quantizer color [tuple!]] [
unless node: quantizer/color-node/:color [
;; First time seeing this color - traverse/build the tree path
node: quantizer/root
level: 0
while [level < 8] [
index: 1
mask: 128 >> level
unless zero? color/1 & mask [index: index + 4]
unless zero? color/2 & mask [index: index + 2]
unless zero? color/3 & mask [index: index + 1]
unless node/children/:index [
node/children/:index: make-octree-node level quantizer
]
node: node/children/:index
++ level
]
;; Count new leaf (pixel-count = 0 means this node hasn't been seen before)
if all [
node/pixel-count = 0
level >= 8
][ quantizer/leaf-count: quantizer/leaf-count + 1 ]
;; Cache the node so future occurrences of this color skip the traversal
quantizer/color-node/:color: node
]
;; Accumulate color data regardless of cache hit or miss
node/red: node/red + color/1
node/green: node/green + color/2
node/blue: node/blue + color/3
node/pixel-count: node/pixel-count + 1
]
;; Build a palette of at most `color-count` colors by reducing the octree bottom-up.
;; Iterates levels from deepest to shallowest, merging sibling leaves into their
;; parent until the leaf count fits within the target. Then assigns palette
;; indices to remaining leaves and records their average colors.
make-palette: function [quantizer color-count] [
palette: copy []
palette-index: 0
leaf-count: quantizer/leaf-count
for level-index 8 1 -1 [
level-nodes: quantizer/levels/:level-index
unless empty? level-nodes [
foreach node level-nodes [
if leaf-count <= color-count [break]
;; count how many children this node has (= reduction + 1)
children-count: 0
foreach child node/children [if child [++ children-count]]
if (leaf-count - children-count + 1) >= color-count [
leaf-count: leaf-count - (remove-leaves node)
]
]
if leaf-count <= color-count [break]
quantizer/levels/:level-index: copy []
]
]
;; Assign palette indices to remaining leaves and record their average colors
foreach node get-leaves quantizer [
if palette-index >= color-count [break]
if node/pixel-count > 0 [append palette get-color node]
node/palette-index: palette-index
++ palette-index
]
palette
]
;; Look up the palette index for `color` by traversing the (possibly reduced) tree
get-palette-index: function [quantizer color] [
unless index: quantizer/color-index/:color [
quantizer/color-index/:color: index:
get-palette-index-for-color quantizer/root color 0
]
index + 1
]
]
;; Download the original image if does not exists.
unless exists? %Quantum_frog.png [
write %quantum_frog.png
read https://static.wikitide.net/rosettacodewiki/3/3f/Quantum_frog.png
]
foreach [colors output-name] [
16 %Quantum_frog-octree-16-colors.png
32 %Quantum_frog-octree-32-colors.png
][
;; Quantize to N colors
time: delta-time [
set [img colors] octree-quantize %Quantum_frog.png colors
]
print ["Time to compute:" time]
;; Display all used colors
print ["The" length? colors "colors used:"]
probe new-line/all colors true
save output-name img ;; Save the result
browse output-name ;; Display the image in a browser
]

View file

@ -7,19 +7,19 @@ proc makeCluster {pixels} {
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
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
[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} {
@ -27,52 +27,52 @@ proc colorQuant {img n} {
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]
}
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]]
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
}
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
}
for {set y 0} {$y < $height} {incr y} {
$newimg put $map([$img get $x $y]) -to $x $y
}
}
return $newimg
}