Sync
This commit is contained in:
parent
6f050a029e
commit
776bba907c
3887 changed files with 59894 additions and 7280 deletions
|
|
@ -1,141 +1,121 @@
|
|||
import core.stdc.stdio, std.stdio, std.ascii, std.algorithm, std.math,
|
||||
std.typecons, std.range, std.conv, std.string, bitmap;
|
||||
import core.stdc.stdio, std.stdio, std.algorithm, std.typecons,
|
||||
std.math, std.range, std.conv, std.string, bitmap;
|
||||
|
||||
struct Col { float r, g, b; }
|
||||
alias Tuple!(Col, float, Col, Col[]) Cluster;
|
||||
alias Cluster = Tuple!(Col, float, Col, Col[]);
|
||||
enum Axis { R, G, B }
|
||||
|
||||
int round(in float x) /*pure*/ nothrow {
|
||||
return cast(int)floor(x + 0.5); // Not pure.
|
||||
}
|
||||
enum round = (in float x) pure nothrow => cast(int)floor(x + 0.5);
|
||||
|
||||
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));
|
||||
}
|
||||
enum roundRGB = (in Col c) pure nothrow => RGB(cast(ubyte)round(c.r),
|
||||
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();
|
||||
enum addRGB = (in Col c1, in Col c2) pure nothrow =>
|
||||
Col(c1.r + c2.r, c1.g + c2.g, c1.b + c2.b);
|
||||
|
||||
Col meanRGB(in Col[] pxList) pure nothrow {
|
||||
immutable tot = reduce!addRGB(Col(0, 0, 0), pxList);
|
||||
immutable n = pxList.length;
|
||||
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));
|
||||
enum minC = (in Col c1, in Col c2) pure nothrow =>
|
||||
Col(min(c1.r, c2.r), min(c1.g, c2.g), min(c1.b, c2.b));
|
||||
|
||||
enum maxC = (in Col c1, in Col c2) pure nothrow =>
|
||||
Col(max(c1.r, c2.r), max(c1.g, c2.g), max(c1.b, c2.b));
|
||||
|
||||
Tuple!(Col, Col) extrems(in Col[] lst) pure nothrow {
|
||||
enum FI = float.infinity;
|
||||
auto mmRGB = typeof(return)(Col(FI, FI, FI), Col(-FI, -FI, -FI));
|
||||
return reduce!(minC, maxC)(mmRGB, 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);
|
||||
Tuple!(float, Col) volumeAndDims(in Col[] lst) pure nothrow {
|
||||
immutable e = lst.extrems;
|
||||
immutable 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);
|
||||
Cluster makeCluster(Col[] pixelList) pure nothrow {
|
||||
immutable vol_dims = pixelList.volumeAndDims;
|
||||
immutable int len = pixelList.length;
|
||||
return typeof(return)(pixelList.meanRGB,
|
||||
len * vol_dims[0],
|
||||
vol_dims[1],
|
||||
pixelList);
|
||||
}
|
||||
|
||||
enum fCmp = (in float a, in float b) pure nothrow =>
|
||||
(a > b) ? 1 : (a < b ? -1 : 0);
|
||||
|
||||
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;
|
||||
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;
|
||||
pure nothrow {
|
||||
bool delegate(immutable Col) 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));
|
||||
auto px2 = pixels.partition!partFunc;
|
||||
auto px1 = pixels[0 .. $ - px2.length];
|
||||
return typeof(return)(px1.makeCluster, px2.makeCluster);
|
||||
}
|
||||
|
||||
Image!RGB colorQuantize(in Image!RGB img, in int n)
|
||||
/*pure*/ nothrow {
|
||||
immutable int width = img.nx;
|
||||
immutable int height = img.ny;
|
||||
uint RGB2uint(in RGB c) pure nothrow {
|
||||
return c.r | (c.g << 8) | (c.b << 16);
|
||||
}
|
||||
|
||||
enum uintToRGB = (in uint c) pure nothrow =>
|
||||
RGB(c & 0xFF, (c >> 8) & 0xFF, (c >> 16) & 0xFF);
|
||||
|
||||
Image!RGB colorQuantize(in Image!RGB img, in int n) pure nothrow {
|
||||
immutable width = img.nx;
|
||||
immutable 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)];
|
||||
cols[i] = Col(c.tupleof);
|
||||
|
||||
immutable Col dumb = Col(0.0, 0.0, 0.0);
|
||||
Cluster unused = Cluster(dumb,
|
||||
-float.infinity,
|
||||
dumb, (Col[]).init);
|
||||
immutable dumb = Col(0, 0, 0);
|
||||
Cluster unused= Cluster(dumb, -float.infinity, dumb, (Col[]).init);
|
||||
|
||||
auto clusters = [cols.makeCluster];
|
||||
while (clusters.length < n) {
|
||||
Cluster cl = reduce!((c1,c2) => c1[1] > c2[1] ? c1 : c2)
|
||||
// Cluster cl = clusters.reduce!(max!q{ a[1] })(unused);
|
||||
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);
|
||||
clusters = [cl[].subdivide[]] ~
|
||||
clusters.remove!(c => c == cl, SwapStrategy.unstable);
|
||||
}
|
||||
|
||||
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]
|
||||
uint[uint] pixMap; // Faster than RGB[RGB].
|
||||
ubyte[4] u4a, u4b;
|
||||
foreach (const cluster; clusters) {
|
||||
immutable ubyteMean = RGB2uint(roundRGB(cluster[0]));
|
||||
immutable ubyteMean = cluster[0].roundRGB.RGB2uint;
|
||||
foreach (immutable col; cluster[3])
|
||||
pixMap[RGB2uint(roundRGB(col))] = ubyteMean;
|
||||
pixMap[col.roundRGB.RGB2uint] = 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)]);
|
||||
foreach (immutable i, immutable p; img.image) {
|
||||
immutable u3a = p.tupleof.RGB;
|
||||
result.image[i] = pixMap[RGB2uint(u3a)].uintToRGB;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
@ -151,10 +131,10 @@ void main(in string[] args) {
|
|||
break;
|
||||
case 3:
|
||||
fileName = args[1];
|
||||
nCols = to!int(args[2]);
|
||||
nCols = args[2].to!int;
|
||||
break;
|
||||
default:
|
||||
writeln("Usage: color_quantization image.ppm ncolors");
|
||||
"Usage: color_quantization image.ppm ncolors".writeln;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
303
Task/Color-quantization/Go/color-quantization.go
Normal file
303
Task/Color-quantization/Go/color-quantization.go
Normal file
|
|
@ -0,0 +1,303 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/png"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
"sort"
|
||||
)
|
||||
|
||||
func main() {
|
||||
f, err := os.Open("Quantum_frog.png")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
img, err := png.Decode(f)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fq, err := os.Create("frog256.png")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
err = png.Encode(fq, quant(img, 256))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Organize quatization in some logical steps.
|
||||
func quant(img image.Image, nq int) image.Image {
|
||||
qz := newQuantizer(img, nq) // set up a work space
|
||||
qz.cluster() // cluster pixels by color
|
||||
return qz.Paletted() // generate paletted image from clusters
|
||||
}
|
||||
|
||||
// A workspace with members that can be accessed by methods.
|
||||
type quantizer struct {
|
||||
img image.Image // original image
|
||||
cs []cluster // len is the desired number of colors
|
||||
px []point // list of all points in the image
|
||||
ch chValues // buffer for computing median
|
||||
eq []point // additional buffer used when splitting cluster
|
||||
}
|
||||
|
||||
type cluster struct {
|
||||
px []point // list of points in the cluster
|
||||
widestCh int // rx, gx, bx const for channel with widest value range
|
||||
chRange uint32 // value range (vmax-vmin) of widest channel
|
||||
}
|
||||
|
||||
type point struct{ x, y int }
|
||||
type chValues []uint32
|
||||
type queue []*cluster
|
||||
|
||||
const (
|
||||
rx = iota
|
||||
gx
|
||||
bx
|
||||
)
|
||||
|
||||
func newQuantizer(img image.Image, nq int) *quantizer {
|
||||
b := img.Bounds()
|
||||
npx := (b.Max.X - b.Min.X) * (b.Max.Y - b.Min.Y)
|
||||
// Create work space.
|
||||
qz := &quantizer{
|
||||
img: img,
|
||||
ch: make(chValues, npx),
|
||||
cs: make([]cluster, nq),
|
||||
}
|
||||
// Populate initial cluster with all pixels from image.
|
||||
c := &qz.cs[0]
|
||||
px := make([]point, npx)
|
||||
c.px = px
|
||||
i := 0
|
||||
for y := b.Min.Y; y < b.Max.Y; y++ {
|
||||
for x := b.Min.X; x < b.Max.X; x++ {
|
||||
px[i].x = x
|
||||
px[i].y = y
|
||||
i++
|
||||
}
|
||||
}
|
||||
return qz
|
||||
}
|
||||
|
||||
func (qz *quantizer) cluster() {
|
||||
// Cluster by repeatedly splitting clusters.
|
||||
// Use a heap as priority queue for picking clusters to split.
|
||||
// The rule will be to spilt the cluster with the most pixels.
|
||||
// Terminate when the desired number of clusters has been populated
|
||||
// or when clusters cannot be further split.
|
||||
pq := new(queue)
|
||||
// Initial cluster. populated at this point, but not analyzed.
|
||||
c := &qz.cs[0]
|
||||
for i := 1; ; {
|
||||
qz.setColorRange(c)
|
||||
// Cluster cannot be split if all pixels are the same color.
|
||||
// Only enqueue clusters that can be split.
|
||||
if c.chRange > 0 {
|
||||
heap.Push(pq, c) // add new cluster to queue
|
||||
}
|
||||
// If no clusters have any color variation, mark the end of the
|
||||
// cluster list and quit early.
|
||||
if len(*pq) == 0 {
|
||||
qz.cs = qz.cs[:i]
|
||||
break
|
||||
}
|
||||
s := heap.Pop(pq).(*cluster) // get cluster to split
|
||||
c = &qz.cs[i] // set c to new cluster
|
||||
i++
|
||||
m := qz.Median(s)
|
||||
qz.Split(s, c, m) // split s into c and s
|
||||
// If that was the last cluster, we're done.
|
||||
if i == len(qz.cs) {
|
||||
break
|
||||
}
|
||||
qz.setColorRange(s)
|
||||
if s.chRange > 0 {
|
||||
heap.Push(pq, s) // return to queue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (q *quantizer) setColorRange(c *cluster) {
|
||||
// Find extents of color values in each channel.
|
||||
var maxR, maxG, maxB uint32
|
||||
minR := uint32(math.MaxUint32)
|
||||
minG := uint32(math.MaxUint32)
|
||||
minB := uint32(math.MaxUint32)
|
||||
for _, p := range c.px {
|
||||
r, g, b, _ := q.img.At(p.x, p.y).RGBA()
|
||||
if r < minR {
|
||||
minR = r
|
||||
}
|
||||
if r > maxR {
|
||||
maxR = r
|
||||
}
|
||||
if g < minG {
|
||||
minG = g
|
||||
}
|
||||
if g > maxG {
|
||||
maxG = g
|
||||
}
|
||||
if b < minB {
|
||||
minB = b
|
||||
}
|
||||
if b > maxB {
|
||||
maxB = b
|
||||
}
|
||||
}
|
||||
// See which channel had the widest range.
|
||||
s := gx
|
||||
min := minG
|
||||
max := maxG
|
||||
if maxR-minR > max-min {
|
||||
s = rx
|
||||
min = minR
|
||||
max = maxR
|
||||
}
|
||||
if maxB-minB > max-min {
|
||||
s = bx
|
||||
min = minB
|
||||
max = maxB
|
||||
}
|
||||
c.widestCh = s
|
||||
c.chRange = max - min // also store the range of that channel
|
||||
}
|
||||
|
||||
func (q *quantizer) Median(c *cluster) uint32 {
|
||||
px := c.px
|
||||
ch := q.ch[:len(px)]
|
||||
// Copy values from appropriate channel to buffer for computing median.
|
||||
switch c.widestCh {
|
||||
case rx:
|
||||
for i, p := range c.px {
|
||||
ch[i], _, _, _ = q.img.At(p.x, p.y).RGBA()
|
||||
}
|
||||
case gx:
|
||||
for i, p := range c.px {
|
||||
_, ch[i], _, _ = q.img.At(p.x, p.y).RGBA()
|
||||
}
|
||||
case bx:
|
||||
for i, p := range c.px {
|
||||
_, _, ch[i], _ = q.img.At(p.x, p.y).RGBA()
|
||||
}
|
||||
}
|
||||
// Median algorithm.
|
||||
sort.Sort(ch)
|
||||
half := len(ch) / 2
|
||||
m := ch[half]
|
||||
if len(ch)%2 == 0 {
|
||||
m = (m + ch[half-1]) / 2
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (q *quantizer) Split(s, c *cluster, m uint32) {
|
||||
px := s.px
|
||||
var v uint32
|
||||
i := 0
|
||||
lt := 0
|
||||
gt := len(px) - 1
|
||||
eq := q.eq[:0] // reuse any existing buffer
|
||||
for i <= gt {
|
||||
// Get pixel value of appropriate channel.
|
||||
r, g, b, _ := q.img.At(px[i].x, px[i].y).RGBA()
|
||||
switch s.widestCh {
|
||||
case rx:
|
||||
v = r
|
||||
case gx:
|
||||
v = g
|
||||
case bx:
|
||||
v = b
|
||||
}
|
||||
// Categorize each pixel as either <, >, or == median.
|
||||
switch {
|
||||
case v < m:
|
||||
px[lt] = px[i]
|
||||
lt++
|
||||
i++
|
||||
case v > m:
|
||||
px[gt], px[i] = px[i], px[gt]
|
||||
gt--
|
||||
default:
|
||||
eq = append(eq, px[i])
|
||||
i++
|
||||
}
|
||||
}
|
||||
// Handle values equal to the median.
|
||||
if len(eq) > 0 {
|
||||
copy(px[lt:], eq) // move them back between the lt and gt values.
|
||||
// Then, if the number of gt values is < the number of lt values,
|
||||
// fix up i so that the split will include the eq values with
|
||||
// the gt values.
|
||||
if len(px)-i < lt {
|
||||
i = lt
|
||||
}
|
||||
q.eq = eq // squirrel away (possibly expanded) buffer for reuse
|
||||
}
|
||||
// Split the pixel list.
|
||||
s.px = px[:i]
|
||||
c.px = px[i:]
|
||||
}
|
||||
|
||||
func (qz *quantizer) Paletted() *image.Paletted {
|
||||
cp := make(color.Palette, len(qz.cs))
|
||||
pi := image.NewPaletted(qz.img.Bounds(), cp)
|
||||
for i := range qz.cs {
|
||||
px := qz.cs[i].px
|
||||
// Average values in cluster to get palette color.
|
||||
var rsum, gsum, bsum int64
|
||||
for _, p := range px {
|
||||
r, g, b, _ := qz.img.At(p.x, p.y).RGBA()
|
||||
rsum += int64(r)
|
||||
gsum += int64(g)
|
||||
bsum += int64(b)
|
||||
}
|
||||
n64 := int64(len(px))
|
||||
cp[i] = color.NRGBA64{
|
||||
uint16(rsum / n64),
|
||||
uint16(gsum / n64),
|
||||
uint16(bsum / n64),
|
||||
0xffff,
|
||||
}
|
||||
// set image pixels
|
||||
for _, p := range px {
|
||||
pi.SetColorIndex(p.x, p.y, uint8(i))
|
||||
}
|
||||
}
|
||||
return pi
|
||||
}
|
||||
|
||||
// Implement sort.Interface for sort in median algorithm.
|
||||
func (c chValues) Len() int { return len(c) }
|
||||
func (c chValues) Less(i, j int) bool { return c[i] < c[j] }
|
||||
func (c chValues) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
||||
|
||||
// Implement heap.Interface for priority queue of clusters.
|
||||
func (q queue) Len() int { return len(q) }
|
||||
|
||||
// Less implements rule to select cluster with greatest number of pixels.
|
||||
func (q queue) Less(i, j int) bool {
|
||||
return len(q[j].px) < len(q[i].px)
|
||||
}
|
||||
|
||||
func (q queue) Swap(i, j int) {
|
||||
q[i], q[j] = q[j], q[i]
|
||||
}
|
||||
func (pq *queue) Push(x interface{}) {
|
||||
c := x.(*cluster)
|
||||
*pq = append(*pq, c)
|
||||
}
|
||||
func (pq *queue) Pop() interface{} {
|
||||
q := *pq
|
||||
n := len(q) - 1
|
||||
c := q[n]
|
||||
*pq = q[:n]
|
||||
return c
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue