181 lines
5.3 KiB
D
181 lines
5.3 KiB
D
module bitmap;
|
|
|
|
import std.stdio, std.array, std.exception, std.string, std.conv,
|
|
std.algorithm, std.ascii;
|
|
|
|
final class Image(T) {
|
|
static if (is(typeof({ auto x = T.black; })))
|
|
const static T black = T.black;
|
|
else
|
|
const static T black = T.init;
|
|
static if (is(typeof({ auto x = T.white; })))
|
|
const static T white = T.white;
|
|
|
|
T[] image;
|
|
private size_t nx_, ny_;
|
|
|
|
this(in int nxx=0, in int nyy=0, in bool inizialize=true)
|
|
pure nothrow {
|
|
allocate(nxx, nyy, inizialize);
|
|
}
|
|
|
|
void allocate(in int nxx=0, in int nyy=0, in bool inizialize=true)
|
|
pure nothrow @safe in {
|
|
assert(nxx >= 0 && nyy >= 0);
|
|
} body {
|
|
this.nx_ = nxx;
|
|
this.ny_ = nyy;
|
|
if (nxx * nyy > 0) {
|
|
if (inizialize)
|
|
image.length = nxx * nyy;
|
|
else // Optimization.
|
|
image = minimallyInitializedArray!(typeof(image))
|
|
(nxx * nyy);
|
|
}
|
|
}
|
|
|
|
@property Image dup() const pure nothrow @safe {
|
|
auto result = new Image();
|
|
result.image = this.image.dup;
|
|
result.nx_ = this.nx;
|
|
result.ny_ = this.ny;
|
|
return result;
|
|
}
|
|
|
|
static Image fromData(T[] data, in size_t nxx=0, in size_t nyy=0)
|
|
pure nothrow @safe in {
|
|
assert(nxx >= 0 && nyy >= 0 && data.length == nxx * nyy);
|
|
} body {
|
|
auto result = new Image();
|
|
result.image = data;
|
|
result.nx_ = nxx;
|
|
result.ny_ = nyy;
|
|
return result;
|
|
}
|
|
|
|
@property size_t nx() const pure nothrow @safe @nogc { return nx_; }
|
|
@property size_t ny() const pure nothrow @safe @nogc { return ny_; }
|
|
|
|
ref T opIndex(in size_t x, in size_t y) pure nothrow @safe @nogc
|
|
in {
|
|
assert(x < nx_ && y < ny_);
|
|
//assert(x < nx_, format("opIndex, x=%d, nx=%d", x, nx));
|
|
//assert(y < ny_, format("opIndex, y=%d, ny=%d", y, ny));
|
|
} body {
|
|
return image[x + y * nx_];
|
|
}
|
|
|
|
T opIndex(in size_t x, in size_t y) const pure nothrow @safe @nogc
|
|
in {
|
|
assert(x < nx_ && y < ny_);
|
|
//assert(x < nx_, format("opIndex, x=%d, nx=%d", x, nx));
|
|
//assert(y < ny_, format("opIndex, y=%d, ny=%d", y, ny));
|
|
} body {
|
|
return image[x + y * nx_];
|
|
}
|
|
|
|
T opIndexAssign(in T color, in size_t x, in size_t y)
|
|
pure nothrow @safe @nogc
|
|
in {
|
|
assert(x < nx_ && y < ny_);
|
|
//assert(x < nx_, format("opIndex, x=%d, nx=%d", x, nx));
|
|
//assert(y < ny_, format("opIndex, y=%d, ny=%d", y, ny));
|
|
} body {
|
|
return image[x + y * nx_] = color;
|
|
}
|
|
|
|
void opIndexUnary(string op)(in size_t x, in size_t y)
|
|
pure nothrow @safe @nogc
|
|
if (op == "++" || op == "--") in {
|
|
assert(x < nx_ && y < ny_);
|
|
} body {
|
|
mixin("image[x + y * nx_] " ~ op ~ ";");
|
|
}
|
|
|
|
void clear(in T color=this.black) pure nothrow @safe @nogc {
|
|
image[] = color;
|
|
}
|
|
|
|
/// Convert a 2D array of chars to a binary Image.
|
|
static Image fromText(in string txt,
|
|
in char one='#', in char zero='.') pure {
|
|
auto M = txt
|
|
.strip
|
|
.split
|
|
.map!(row => row
|
|
.filter!(c => c == one || c == zero)
|
|
.map!(c => T(c == one))
|
|
.array)
|
|
.array;
|
|
assert(M.join.length > 0); // Not empty.
|
|
foreach (row; M)
|
|
assert(row.length == M[0].length); // Rectangular
|
|
return Image.fromData(M.join, M[0].length, M.length);
|
|
}
|
|
|
|
/// The axis origin is at the top left.
|
|
void textualShow(in char bl='#', in char wh='.') const nothrow {
|
|
size_t i = 0;
|
|
foreach (immutable y; 0 .. ny_) {
|
|
foreach (immutable x; 0 .. nx_)
|
|
putchar(image[i++] == black ? bl : wh);
|
|
putchar('\n');
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
struct RGB {
|
|
ubyte r, g, b;
|
|
static immutable black = typeof(this)();
|
|
static immutable white = typeof(this)(255, 255, 255);
|
|
}
|
|
|
|
|
|
Image!RGB loadPPM6(ref Image!RGB img, in string fileName) {
|
|
if (img is null)
|
|
img = new Image!RGB;
|
|
auto f = File(fileName, "rb");
|
|
enforce(f.readln.strip == "P6");
|
|
string line;
|
|
do {
|
|
line = f.readln();
|
|
} while (line.length && line[0] == '#'); // Skip comments.
|
|
const size = line.split;
|
|
enforce(size.length == 2);
|
|
img.allocate(size[0].to!uint, size[1].to!uint);
|
|
enforce(f.readln().strip() == "255");
|
|
auto l = new ubyte[img.nx * 3];
|
|
size_t i = 0;
|
|
foreach (immutable y; 0 .. img.ny) {
|
|
f.rawRead!ubyte(l);
|
|
foreach (immutable x; 0 .. img.nx)
|
|
img.image[i++] = RGB(l[x * 3], l[x * 3 + 1], l[x * 3 + 2]);
|
|
}
|
|
return img;
|
|
}
|
|
|
|
|
|
void savePPM6(in Image!RGB img, in string fileName)
|
|
in {
|
|
assert(img !is null);
|
|
assert(img.nx > 0 && img.nx > 0);
|
|
} body {
|
|
auto f = File(fileName, "wb");
|
|
f.writefln("P6\n%d %d\n255", img.nx, img.ny);
|
|
size_t i = 0;
|
|
foreach (immutable y; 0 .. img.ny)
|
|
foreach (immutable x; 0 .. img.nx) {
|
|
immutable p = img.image[i++];
|
|
f.write(cast(char)p.r, cast(char)p.g, cast(char)p.b);
|
|
}
|
|
}
|
|
|
|
version (bitmap_main) {
|
|
void main() {
|
|
auto img = new Image!RGB(30, 10);
|
|
img[4, 5] = RGB.white;
|
|
img.textualShow;
|
|
}
|
|
}
|