Just another update

This commit is contained in:
Ingy döt Net 2015-02-20 00:35:01 -05:00
parent a25938f123
commit 00a190b0a6
6591 changed files with 94363 additions and 23227 deletions

View file

@ -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].

View 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;
}

View 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>"