Show a basic storage type to handle a simple RGB raster graphics image,
and some primitive associated functions.

If possible provide a function to allocate an uninitialised image,
given its width and height, and provide 3 additional functions:

   one to fill an image with a plain RGB color,
   one to set a given pixel with a color,
   one to get the color of a pixel.


(If there are specificities about the storage or the allocation, explain those.)

These functions are used as a base for the articles in the category raster graphics operations, 
and a basic output function to check the results
is available in the article write ppm file.




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