Just another update
This commit is contained in:
parent
a25938f123
commit
00a190b0a6
6591 changed files with 94363 additions and 23227 deletions
|
|
@ -5,34 +5,35 @@ struct Col { float r, g, b; }
|
|||
alias Cluster = Tuple!(Col, float, Col, Col[]);
|
||||
enum Axis { R, G, B }
|
||||
|
||||
enum round = (in float x) pure nothrow => cast(int)floor(x + 0.5);
|
||||
enum round = (in float x) pure nothrow @safe @nogc => cast(int)floor(x + 0.5);
|
||||
|
||||
enum roundRGB = (in Col c) pure nothrow => RGB(cast(ubyte)round(c.r),
|
||||
cast(ubyte)round(c.g),
|
||||
cast(ubyte)round(c.b));
|
||||
enum roundRGB = (in Col c) pure nothrow @safe @nogc =>
|
||||
RGB(cast(ubyte)round(c.r),
|
||||
cast(ubyte)round(c.g),
|
||||
cast(ubyte)round(c.b));
|
||||
|
||||
enum addRGB = (in Col c1, in Col c2) pure nothrow =>
|
||||
enum addRGB = (in Col c1, in Col c2) pure nothrow @safe @nogc =>
|
||||
Col(c1.r + c2.r, c1.g + c2.g, c1.b + c2.b);
|
||||
|
||||
Col meanRGB(in Col[] pxList) pure nothrow {
|
||||
Col meanRGB(in Col[] pxList) pure nothrow @safe @nogc {
|
||||
immutable tot = reduce!addRGB(Col(0, 0, 0), pxList);
|
||||
immutable n = pxList.length;
|
||||
return Col(tot.r / n, tot.g / n, tot.b / n);
|
||||
}
|
||||
|
||||
enum minC = (in Col c1, in Col c2) pure nothrow =>
|
||||
enum minC = (in Col c1, in Col c2) pure nothrow @safe @nogc =>
|
||||
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 =>
|
||||
enum maxC = (in Col c1, in Col c2) pure nothrow @safe @nogc =>
|
||||
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 {
|
||||
Tuple!(Col, Col) extrems(in Col[] lst) pure nothrow @safe @nogc {
|
||||
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 {
|
||||
Tuple!(float, Col) volumeAndDims(in Col[] lst) pure nothrow @safe @nogc {
|
||||
immutable e = lst.extrems;
|
||||
immutable r = Col(e[1].r - e[0].r,
|
||||
e[1].g - e[0].g,
|
||||
|
|
@ -40,7 +41,7 @@ Tuple!(float, Col) volumeAndDims(in Col[] lst) pure nothrow {
|
|||
return typeof(return)(r.r * r.g * r.b, r);
|
||||
}
|
||||
|
||||
Cluster makeCluster(Col[] pixelList) pure nothrow {
|
||||
Cluster makeCluster(Col[] pixelList) pure nothrow @safe @nogc {
|
||||
immutable vol_dims = pixelList.volumeAndDims;
|
||||
immutable int len = pixelList.length;
|
||||
return typeof(return)(pixelList.meanRGB,
|
||||
|
|
@ -49,10 +50,10 @@ Cluster makeCluster(Col[] pixelList) pure nothrow {
|
|||
pixelList);
|
||||
}
|
||||
|
||||
enum fCmp = (in float a, in float b) pure nothrow =>
|
||||
enum fCmp = (in float a, in float b) pure nothrow @safe @nogc =>
|
||||
(a > b) ? 1 : (a < b ? -1 : 0);
|
||||
|
||||
Axis largestAxis(in Col c) pure nothrow {
|
||||
Axis largestAxis(in Col c) pure nothrow @safe @nogc {
|
||||
immutable int r1 = fCmp(c.r, c.g);
|
||||
immutable int r2 = fCmp(c.r, c.b);
|
||||
if (r1 == 1 && r2 == 1) return Axis.R;
|
||||
|
|
@ -63,26 +64,25 @@ Axis largestAxis(in Col c) pure nothrow {
|
|||
|
||||
Tuple!(Cluster, Cluster) subdivide(in Col c, in float nVolProd,
|
||||
in Col vol, Col[] pixels)
|
||||
pure nothrow {
|
||||
bool delegate(immutable Col) pure nothrow partFunc;
|
||||
pure nothrow @safe @nogc {
|
||||
Col[] px2;
|
||||
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;
|
||||
case Axis.R: px2 = pixels.partition!(c1 => c1.r < c.r); break;
|
||||
case Axis.G: px2 = pixels.partition!(c1 => c1.g < c.g); break;
|
||||
case Axis.B: px2 = pixels.partition!(c1 => c1.b < c.b); break;
|
||||
}
|
||||
auto px2 = pixels.partition!partFunc;
|
||||
auto px1 = pixels[0 .. $ - px2.length];
|
||||
return typeof(return)(px1.makeCluster, px2.makeCluster);
|
||||
}
|
||||
|
||||
uint RGB2uint(in RGB c) pure nothrow {
|
||||
uint RGB2uint(in RGB c) pure nothrow @safe @nogc {
|
||||
return c.r | (c.g << 8) | (c.b << 16);
|
||||
}
|
||||
|
||||
enum uintToRGB = (in uint c) pure nothrow =>
|
||||
enum uintToRGB = (in uint c) pure nothrow @safe @nogc =>
|
||||
RGB(c & 0xFF, (c >> 8) & 0xFF, (c >> 16) & 0xFF);
|
||||
|
||||
Image!RGB colorQuantize(in Image!RGB img, in int n) pure nothrow {
|
||||
Image!RGB colorQuantize(in Image!RGB img, in int n) pure nothrow /*@safe*/ {
|
||||
immutable width = img.nx;
|
||||
immutable height = img.ny;
|
||||
|
||||
|
|
@ -91,7 +91,7 @@ Image!RGB colorQuantize(in Image!RGB img, in int n) pure nothrow {
|
|||
cols[i] = Col(c.tupleof);
|
||||
|
||||
immutable dumb = Col(0, 0, 0);
|
||||
Cluster unused= Cluster(dumb, -float.infinity, dumb, (Col[]).init);
|
||||
Cluster unused = Cluster(dumb, -float.infinity, dumb, (Col[]).init);
|
||||
|
||||
auto clusters = [cols.makeCluster];
|
||||
while (clusters.length < n) {
|
||||
|
|
@ -99,7 +99,7 @@ Image!RGB colorQuantize(in Image!RGB img, in int n) pure nothrow {
|
|||
Cluster cl = reduce!((c1, c2) => c1[1] > c2[1] ? c1 : c2)
|
||||
(unused, clusters);
|
||||
clusters = [cl[].subdivide[]] ~
|
||||
clusters.remove!(c => c == cl, SwapStrategy.unstable);
|
||||
clusters.remove!(c => c == cl, SwapStrategy.unstable); //**
|
||||
}
|
||||
|
||||
uint[uint] pixMap; // Faster than RGB[RGB].
|
||||
447
Task/Color-quantization/D/color-quantization-2.d
Normal file
447
Task/Color-quantization/D/color-quantization-2.d
Normal file
|
|
@ -0,0 +1,447 @@
|
|||
import core.stdc.stdlib: malloc, calloc, realloc, free, abort;
|
||||
import std.stdio: stderr, File;
|
||||
import std.ascii: isWhite;
|
||||
import std.math: abs;
|
||||
import std.conv: to;
|
||||
import std.string: split, strip;
|
||||
import std.exception: enforce;
|
||||
import std.array: empty;
|
||||
import std.typetuple: TypeTuple;
|
||||
|
||||
enum ON_INHEAP = 1;
|
||||
|
||||
struct Image {
|
||||
uint w, h;
|
||||
ubyte[0] pix;
|
||||
}
|
||||
|
||||
Image* imageNew(in uint w, in uint h) nothrow @nogc
|
||||
in {
|
||||
assert(w > 0 && h > 0);
|
||||
} out(result) {
|
||||
assert(result != null);
|
||||
} body {
|
||||
auto im = cast(Image*)malloc(Image.sizeof + w * h * 3);
|
||||
im.w = w;
|
||||
im.h = h;
|
||||
return im;
|
||||
}
|
||||
|
||||
Image* readPPM6(in string fileName)
|
||||
in {
|
||||
assert(!fileName.empty);
|
||||
} out(result) {
|
||||
assert(result != null);
|
||||
} body {
|
||||
auto fIn = File(fileName, "rb");
|
||||
enforce(fIn.readln.strip == "P6");
|
||||
|
||||
// Skip comments.
|
||||
string line;
|
||||
do {
|
||||
line = fIn.readln;
|
||||
} while (line.length && line[0] == '#');
|
||||
|
||||
const size = line.split.to!(uint[]);
|
||||
enforce(size.length == 2);
|
||||
//immutable size = line.split.to!(uint[2]);
|
||||
auto img = imageNew(size[0], size[1]);
|
||||
enforce(fIn.readln.strip == "255");
|
||||
fIn.rawRead(img.pix.ptr[0 .. img.w * img.h * 3]);
|
||||
return img;
|
||||
}
|
||||
|
||||
void writePPM6(in Image* img, in string fileName)
|
||||
in {
|
||||
assert(!fileName.empty);
|
||||
assert(img != null);
|
||||
} body {
|
||||
auto fOut = File(fileName, "wb");
|
||||
fOut.writefln("P6\n%d %d\n255", img.w, img.h);
|
||||
fOut.rawWrite(img.pix.ptr[0 .. img.w * img.h * 3]);
|
||||
fOut.close;
|
||||
}
|
||||
|
||||
struct OctreeNode {
|
||||
long r, g, b; // Sum of all child node colors.
|
||||
uint count, heapIdx;
|
||||
ubyte nKids, kidIdx, flags, depth;
|
||||
OctreeNode*[8] kids;
|
||||
OctreeNode* parent;
|
||||
}
|
||||
|
||||
struct HeapNode {
|
||||
uint alloc, n;
|
||||
OctreeNode** buf;
|
||||
}
|
||||
|
||||
int cmpOctreeNode(in OctreeNode* a, in OctreeNode* b)
|
||||
pure nothrow @safe @nogc
|
||||
in {
|
||||
assert(a != null);
|
||||
assert(b != null);
|
||||
} out(result) {
|
||||
assert(result == -1 || result == 0 || result == 1);
|
||||
} body {
|
||||
if (a.nKids < b.nKids)
|
||||
return -1;
|
||||
if (a.nKids > b.nKids)
|
||||
return 1;
|
||||
|
||||
immutable uint ac = a.count >> a.depth;
|
||||
immutable uint bc = b.count >> b.depth;
|
||||
return (ac < bc) ? -1 : (ac > bc);
|
||||
}
|
||||
|
||||
void downHeap(HeapNode* h, OctreeNode* p) pure nothrow @nogc
|
||||
in {
|
||||
assert(h != null);
|
||||
assert(p != null);
|
||||
} body {
|
||||
auto n = p.heapIdx;
|
||||
|
||||
while (true) {
|
||||
uint m = n * 2;
|
||||
if (m >= h.n)
|
||||
break;
|
||||
if (m + 1 < h.n && cmpOctreeNode(h.buf[m], h.buf[m + 1]) > 0)
|
||||
m++;
|
||||
|
||||
if (cmpOctreeNode(p, h.buf[m]) <= 0)
|
||||
break;
|
||||
|
||||
h.buf[n] = h.buf[m];
|
||||
h.buf[n].heapIdx = n;
|
||||
n = m;
|
||||
}
|
||||
|
||||
h.buf[n] = p;
|
||||
p.heapIdx = n;
|
||||
}
|
||||
|
||||
void upHeap(HeapNode* h, OctreeNode* p) pure nothrow @nogc
|
||||
in {
|
||||
assert(h != null);
|
||||
assert(p != null);
|
||||
} body {
|
||||
auto n = p.heapIdx;
|
||||
|
||||
while (n > 1) {
|
||||
auto prev = h.buf[n / 2];
|
||||
if (cmpOctreeNode(p, prev) >= 0)
|
||||
break;
|
||||
|
||||
h.buf[n] = prev;
|
||||
prev.heapIdx = n;
|
||||
n /= 2;
|
||||
}
|
||||
|
||||
h.buf[n] = p;
|
||||
p.heapIdx = n;
|
||||
}
|
||||
|
||||
void addHeap(HeapNode* h, OctreeNode* p) nothrow @nogc
|
||||
in {
|
||||
assert(h != null);
|
||||
assert(p != null);
|
||||
} body {
|
||||
if ((p.flags & ON_INHEAP)) {
|
||||
downHeap(h, p);
|
||||
upHeap(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 = cast(OctreeNode**)realloc(h.buf, (OctreeNode*).sizeof * h.alloc);
|
||||
assert(h.buf != null);
|
||||
}
|
||||
|
||||
p.heapIdx = h.n;
|
||||
h.buf[h.n++] = p;
|
||||
upHeap(h, p);
|
||||
}
|
||||
|
||||
OctreeNode* popHeap(HeapNode* h) pure nothrow @nogc
|
||||
in {
|
||||
assert(h != null);
|
||||
} out(result) {
|
||||
assert(result != null);
|
||||
} body {
|
||||
if (h.n <= 1)
|
||||
return null;
|
||||
|
||||
auto ret = h.buf[1];
|
||||
h.buf[1] = h.buf[--h.n];
|
||||
|
||||
h.buf[h.n] = null;
|
||||
|
||||
h.buf[1].heapIdx = 1;
|
||||
downHeap(h, h.buf[1]);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
OctreeNode* octreeNodeNew(in ubyte idx, in ubyte depth, OctreeNode* p,
|
||||
ref OctreeNode[] pool) nothrow @nogc
|
||||
out(result) {
|
||||
assert(result != null);
|
||||
} body {
|
||||
__gshared static uint len = 0;
|
||||
|
||||
if (len <= 1) {
|
||||
OctreeNode* p2 = cast(OctreeNode*)calloc(OctreeNode.sizeof, 2048);
|
||||
assert(p2 != null);
|
||||
p2.parent = pool.ptr;
|
||||
pool = p2[0 .. 2048];
|
||||
len = 2047;
|
||||
}
|
||||
|
||||
OctreeNode* x = pool.ptr + len--;
|
||||
x.kidIdx = idx;
|
||||
x.depth = depth;
|
||||
x.parent = p;
|
||||
if (p)
|
||||
p.nKids++;
|
||||
return x;
|
||||
}
|
||||
|
||||
void octreeNodeFree(ref OctreeNode[] pool) nothrow @nogc
|
||||
out {
|
||||
assert(pool.empty);
|
||||
} body {
|
||||
auto poolPtr = pool.ptr;
|
||||
|
||||
while (poolPtr) {
|
||||
auto p = poolPtr.parent;
|
||||
free(poolPtr);
|
||||
poolPtr = p;
|
||||
}
|
||||
|
||||
pool = null;
|
||||
}
|
||||
|
||||
OctreeNode* octreeNodeInsert(OctreeNode* root, in ubyte* pix, ref OctreeNode[] pool)
|
||||
nothrow @nogc
|
||||
in {
|
||||
assert(root != null);
|
||||
assert(pix != null);
|
||||
assert(!pool.empty);
|
||||
} out(result) {
|
||||
assert(result != null);
|
||||
} body {
|
||||
ubyte depth = 0;
|
||||
|
||||
for (ubyte bit = (1 << 7); ++depth < 8; bit >>= 1) {
|
||||
immutable ubyte i = !!(pix[1] & bit) * 4 +
|
||||
!!(pix[0] & bit) * 2 +
|
||||
!!(pix[2] & bit);
|
||||
if (!root.kids[i])
|
||||
root.kids[i] = octreeNodeNew(i, depth, root, pool);
|
||||
|
||||
root = root.kids[i];
|
||||
}
|
||||
|
||||
root.r += pix[0];
|
||||
root.g += pix[1];
|
||||
root.b += pix[2];
|
||||
root.count++;
|
||||
return root;
|
||||
}
|
||||
|
||||
OctreeNode* octreeNodeFold(OctreeNode* p) nothrow @nogc
|
||||
in {
|
||||
assert(p != null);
|
||||
} out(result) {
|
||||
assert(result != null);
|
||||
} body {
|
||||
if (p.nKids)
|
||||
abort();
|
||||
auto q = p.parent;
|
||||
q.count += p.count;
|
||||
|
||||
q.r += p.r;
|
||||
q.g += p.g;
|
||||
q.b += p.b;
|
||||
q.nKids--;
|
||||
q.kids[p.kidIdx] = null;
|
||||
return q;
|
||||
}
|
||||
|
||||
void colorReplace(OctreeNode* root, ubyte* pix) pure nothrow @nogc
|
||||
in {
|
||||
assert(root != null);
|
||||
assert(pix != null);
|
||||
} body {
|
||||
for (ubyte bit = (1 << 7); bit; bit >>= 1) {
|
||||
immutable i = !!(pix[1] & bit) * 4 +
|
||||
!!(pix[0] & bit) * 2 +
|
||||
!!(pix[2] & bit);
|
||||
if (!root.kids[i])
|
||||
break;
|
||||
root = root.kids[i];
|
||||
}
|
||||
|
||||
pix[0] = cast(ubyte)root.r;
|
||||
pix[1] = cast(ubyte)root.g;
|
||||
pix[2] = cast(ubyte)root.b;
|
||||
}
|
||||
|
||||
void errorDiffuse(Image* im, HeapNode* h) nothrow @nogc
|
||||
in {
|
||||
assert(im != null);
|
||||
assert(h != null);
|
||||
} body {
|
||||
OctreeNode* nearestColor(in int* v) nothrow @nogc
|
||||
in {
|
||||
assert(v != null);
|
||||
} out(result) {
|
||||
assert(result != null);
|
||||
} body {
|
||||
auto max = long.max;
|
||||
typeof(return) on = null;
|
||||
|
||||
foreach (immutable uint i; 1 .. h.n) {
|
||||
immutable diff = 3 * abs(h.buf[i].r - v[0]) +
|
||||
5 * abs(h.buf[i].g - v[1]) +
|
||||
2 * abs(h.buf[i].b - v[2]);
|
||||
if (diff < max) {
|
||||
max = diff;
|
||||
on = h.buf[i];
|
||||
}
|
||||
}
|
||||
|
||||
return on;
|
||||
}
|
||||
|
||||
uint pos(in uint i, in uint j) nothrow @safe @nogc {
|
||||
return 3 * (i * im.w + j);
|
||||
}
|
||||
|
||||
enum C10 = 7;
|
||||
enum C01 = 5;
|
||||
enum C11 = 2;
|
||||
enum C00 = 1;
|
||||
enum CTOTAL = C00 + C11 + C10 + C01;
|
||||
|
||||
auto npx = cast(int*)calloc(int.sizeof, im.h * im.w * 3);
|
||||
assert(npx != null);
|
||||
auto pix = im.pix.ptr;
|
||||
alias triple = TypeTuple!(0, 1, 2);
|
||||
|
||||
for (auto px = npx, i = 0u; i < im.h; i++) {
|
||||
for (uint j = 0; j < im.w; j++, pix += 3, px += 3) {
|
||||
/*static*/ foreach (immutable k; triple)
|
||||
px[k] = cast(int)pix[k] * CTOTAL;
|
||||
}
|
||||
}
|
||||
|
||||
static void clamp(ref int x) pure nothrow @safe @nogc {
|
||||
if (x > 255) x = 255;
|
||||
if (x < 0) x = 0;
|
||||
}
|
||||
|
||||
pix = im.pix.ptr;
|
||||
|
||||
for (auto px = npx, i = 0u; i < im.h; i++) {
|
||||
for (uint j = 0; j < im.w; j++, pix += 3, px += 3) {
|
||||
/*static*/ foreach (immutable k; triple)
|
||||
px[k] /= CTOTAL;
|
||||
/*static*/ foreach (immutable k; triple)
|
||||
clamp(px[k]);
|
||||
|
||||
const nd = nearestColor(px);
|
||||
uint[3] v = void;
|
||||
v[0] = cast(uint)(px[0] - nd.r);
|
||||
v[1] = cast(uint)(px[1] - nd.g);
|
||||
v[2] = cast(uint)(px[2] - nd.b);
|
||||
|
||||
pix[0] = cast(ubyte)nd.r;
|
||||
pix[1] = cast(ubyte)nd.g;
|
||||
pix[2] = cast(ubyte)nd.b;
|
||||
|
||||
if (j < im.w - 1) {
|
||||
/*static*/ foreach (immutable k; triple)
|
||||
npx[pos(i, j + 1) + k] += v[k] * C10;
|
||||
}
|
||||
|
||||
if (i >= im.h - 1)
|
||||
continue;
|
||||
|
||||
/*static*/ foreach (immutable k; triple)
|
||||
npx[pos(i + 1, j) + k] += v[k] * C01;
|
||||
|
||||
if (j < im.w - 1) {
|
||||
/*static*/ foreach (immutable k; triple)
|
||||
npx[pos(i + 1, j + 1) + k] += v[k] * C11;
|
||||
}
|
||||
|
||||
if (j) {
|
||||
/*static*/ foreach (immutable k; triple)
|
||||
npx[pos(i + 1, j - 1) + k] += v[k] * C00;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(npx);
|
||||
}
|
||||
|
||||
void colorQuant(Image* im, in uint nColors, in bool dither) nothrow @nogc
|
||||
in {
|
||||
assert(im != null);
|
||||
assert(nColors > 1);
|
||||
} body {
|
||||
auto pix = im.pix.ptr;
|
||||
HeapNode heap = { 0, 0, null };
|
||||
OctreeNode[] pool;
|
||||
|
||||
auto root = octreeNodeNew(0, 0, null, pool);
|
||||
for (uint i = 0; i < im.w * im.h; i++, pix += 3)
|
||||
addHeap(&heap, octreeNodeInsert(root, pix, pool));
|
||||
|
||||
while (heap.n > nColors + 1)
|
||||
addHeap(&heap, octreeNodeFold(popHeap(&heap)));
|
||||
|
||||
foreach (immutable i; 1 .. heap.n) {
|
||||
auto got = heap.buf[i];
|
||||
immutable double c = got.count;
|
||||
got.r = cast(long)(got.r / c + 0.5);
|
||||
got.g = cast(long)(got.g / c + 0.5);
|
||||
got.b = cast(long)(got.b / c + 0.5);
|
||||
}
|
||||
|
||||
if (dither)
|
||||
errorDiffuse(im, &heap);
|
||||
else {
|
||||
uint i;
|
||||
for (i = 0, pix = im.pix.ptr; i < im.w * im.h; i++, pix += 3)
|
||||
colorReplace(root, pix);
|
||||
}
|
||||
|
||||
pool.octreeNodeFree;
|
||||
heap.buf.free;
|
||||
}
|
||||
|
||||
int main(in string[] args) {
|
||||
if (args.length < 3 || args.length > 4) {
|
||||
stderr.writeln("Usage: quant ppmFile nColors [dith]");
|
||||
return 1;
|
||||
}
|
||||
|
||||
immutable nColors = args[2].to!uint;
|
||||
assert(nColors > 1);
|
||||
|
||||
auto im = readPPM6(args[1]);
|
||||
immutable useDithering = (args.length == 4) ? (args[3] == "dith") : false;
|
||||
immutable fileNameOut = useDithering ? "outd.ppm" : "out.ppm";
|
||||
|
||||
colorQuant(im, nColors, useDithering);
|
||||
writePPM6(im, fileNameOut);
|
||||
|
||||
im.free;
|
||||
return 0;
|
||||
}
|
||||
128
Task/Color-quantization/Haskell/color-quantization.hs
Normal file
128
Task/Color-quantization/Haskell/color-quantization.hs
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
import qualified Data.ByteString.Lazy as BS
|
||||
import qualified Data.Foldable as Fold
|
||||
import qualified Data.List as List
|
||||
import Data.Ord
|
||||
import qualified Data.Sequence as Seq
|
||||
import Data.Word
|
||||
import System.Environment
|
||||
|
||||
import Codec.Picture
|
||||
import Codec.Picture.Types
|
||||
|
||||
type Accessor = PixelRGB8 -> Pixel8
|
||||
|
||||
-- Getters for pixel components, as the constructor does not
|
||||
-- provide any public ones.
|
||||
red, blue, green :: Accessor
|
||||
red (PixelRGB8 r _ _) = r
|
||||
green (PixelRGB8 _ g _) = g
|
||||
blue (PixelRGB8 _ _ b) = b
|
||||
|
||||
-- Get all of the pixels in the image in list form.
|
||||
getPixels :: Pixel a => Image a -> [a]
|
||||
getPixels image =
|
||||
[pixelAt image x y
|
||||
| x <- [0..(imageWidth image - 1)]
|
||||
, y <- [0..(imageHeight image - 1)]]
|
||||
|
||||
-- Compute the color-space extents of a list of pixels.
|
||||
extents :: [PixelRGB8] -> (PixelRGB8, PixelRGB8)
|
||||
extents pixels = (extent minimum, extent maximum)
|
||||
where
|
||||
bound f g = f $ map g pixels
|
||||
extent f = PixelRGB8 (bound f red) (bound f green) (bound f blue)
|
||||
|
||||
-- Compute the average value of a list of pixels.
|
||||
average :: [PixelRGB8] -> PixelRGB8
|
||||
average pixels = PixelRGB8 (avg red) (avg green) (avg blue)
|
||||
where
|
||||
len = toInteger $ length pixels
|
||||
avg c = fromIntegral $ (sum $ map (toInteger . c) pixels) `div` len
|
||||
|
||||
-- Perform a componentwise pixel operation.
|
||||
compwise :: (Word8 -> Word8 -> Word8) -> PixelRGB8 -> PixelRGB8 -> PixelRGB8
|
||||
compwise f (PixelRGB8 ra ga ba) (PixelRGB8 rb gb bb) =
|
||||
PixelRGB8 (f ra rb) (f ga gb) (f ba bb)
|
||||
|
||||
-- Compute the absolute difference of two pixels.
|
||||
diffPixel :: PixelRGB8 -> PixelRGB8 -> PixelRGB8
|
||||
diffPixel = compwise (\x y -> max x y - min x y)
|
||||
|
||||
-- Compute the Euclidean distance squared between two pixels.
|
||||
distPixel :: PixelRGB8 -> PixelRGB8 -> Integer
|
||||
distPixel x y = (rr ^ 2) + (gg ^ 2) + (bb ^ 2)
|
||||
where
|
||||
PixelRGB8 r g b = diffPixel x y
|
||||
rr = toInteger r
|
||||
gg = toInteger g
|
||||
bb = toInteger b
|
||||
|
||||
-- Determine the dimension of the longest axis of the extents.
|
||||
longestAccessor :: (PixelRGB8, PixelRGB8) -> Accessor
|
||||
longestAccessor (l, h) =
|
||||
snd $ Fold.maximumBy (comparing fst) $ zip [r, g, b] [red, green, blue]
|
||||
where
|
||||
PixelRGB8 r g b = diffPixel h l
|
||||
|
||||
-- Find the index of a pixel to its respective palette.
|
||||
nearestIdx :: PixelRGB8 -> [PixelRGB8] -> Int
|
||||
nearestIdx pixel px = ans
|
||||
where
|
||||
Just ans = List.findIndex (== near) px
|
||||
near = List.foldl1 comp px
|
||||
comp a b = if distPixel a pixel <= distPixel b pixel then a else b
|
||||
|
||||
-- Sort a list of pixels on its longest axis and then split by the mean.
|
||||
-- It is intentional that the mean is chosen by all dimensions
|
||||
-- instead of the given one.
|
||||
meanSplit :: [PixelRGB8] -> Accessor -> ([PixelRGB8], [PixelRGB8])
|
||||
meanSplit l f = List.splitAt index sorted
|
||||
where
|
||||
sorted = List.sortBy (comparing f) l
|
||||
index = nearestIdx (average l) sorted
|
||||
|
||||
-- Perform the Median Cut algorithm on an image producing
|
||||
-- an index map image and its respective palette.
|
||||
meanCutQuant :: Image PixelRGB8 -> Int -> (Image Pixel8, Palette)
|
||||
meanCutQuant image numRegions = (indexmap, palette)
|
||||
where
|
||||
extentsP p = (p, extents p)
|
||||
regions = map (\(p, e) -> (average p, e))
|
||||
$ search $ Seq.singleton $ extentsP $ getPixels image
|
||||
palette = snd $ generateFoldImage (\(x:xs) _ _ -> (xs, x))
|
||||
(map fst regions) numRegions 1
|
||||
indexmap = pixelMap
|
||||
(\pixel -> fromIntegral $ nearestIdx pixel $ map fst regions)
|
||||
image
|
||||
search queue =
|
||||
case Seq.viewl queue of
|
||||
(pixels, extent) Seq.:< queueB ->
|
||||
let (left, right) = meanSplit pixels $ longestAccessor extent
|
||||
queueC = Fold.foldl (Seq.|>) queueB $ map extentsP [left, right]
|
||||
in if Seq.length queueC >= numRegions
|
||||
then List.take numRegions $ Fold.toList queueC
|
||||
else search queueC
|
||||
Seq.EmptyL -> error "Queue should never be empty."
|
||||
|
||||
quantizeIO :: FilePath -> FilePath -> Int -> IO ()
|
||||
quantizeIO path outpath numRegions = do
|
||||
dynimage <- readImage path
|
||||
case dynimage of
|
||||
Left err -> putStrLn err
|
||||
Right (ImageRGB8 image) -> doImage image
|
||||
Right (ImageRGBA8 image) -> doImage (pixelMap dropTransparency image)
|
||||
_ -> putStrLn "Expecting RGB8 or RGBA8 image"
|
||||
where
|
||||
doImage image = do
|
||||
let (indexmap, palette) = meanCutQuant image numRegions
|
||||
case encodePalettedPng palette indexmap of
|
||||
Left err -> putStrLn err
|
||||
Right bstring -> BS.writeFile outpath bstring
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
args <- getArgs
|
||||
prog <- getProgName
|
||||
case args of
|
||||
[path, outpath] -> quantizeIO path outpath 16
|
||||
_ -> putStrLn $ "Usage: " ++ prog ++ " <image-file> <out-file.png>"
|
||||
Loading…
Add table
Add a link
Reference in a new issue