Many image processing algorithms are defined for grayscale (or else monochromatic) images.



Task

Extend the data storage type defined on this page to support grayscale images.

Define two operations, one to convert a color image to a grayscale image and one for the backward conversion.

To get luminance of a color use the formula recommended by CIE:

 L  =  0.2126 × R   +   0.7152 × G   +   0.0722 × B 

When using floating-point arithmetic make sure that rounding errors would not cause run-time problems or else distorted results when calculated luminance is stored as an unsigned integer.




module grayscale_image;
 
import core.stdc.stdio, std.array, std.algorithm, std.string, std.ascii;
public import bitmap;
 
struct Gray {
    ubyte c;
    enum black = typeof(this)(0);
    enum white = typeof(this)(255);
    alias c this;
}
 
 
Image!Color loadPGM(Color)(Image!Color img, in string fileName) {
    static int readNum(FILE* f) nothrow @nogc {
        int n;
        while (!fscanf(f, "%d ", &n)) {
            if ((n = fgetc(f)) == '#') {
                while ((n = fgetc(f)) != '\n')
                    if (n == EOF)
                        return 0;
            } else
                return 0;
        }
        return n;
    }
 
    if (img is null)
        img = new Image!Color();
 
    auto fin = fopen(fileName.toStringz(), "rb");
    scope(exit) if (fin) fclose(fin);
    if (!fin)
        throw new Exception("Can't open input file.");
 
    if (fgetc(fin) != 'P' ||
        fgetc(fin) != '5' ||
        !isWhite(fgetc(fin)))
        throw new Exception("Not a PGM (PPM P5) image.");
 
    immutable int nc = readNum(fin);
    immutable int nr = readNum(fin);
    immutable int maxVal = readNum(fin);
    if (nc <= 0 || nr <= 0 || maxVal <= 0)
        throw new Exception("Wrong input image sizes.");
    img.allocate(nc, nr);
    auto pix = new ubyte[img.image.length];
 
    immutable count = fread(pix.ptr, 1, nc * nr, fin);
    if (count != nc * nr)
        throw new Exception("Wrong number of items read.");
 
    pix.copy(img.image);
    return img;
}
 
 
void savePGM(Color)(in Image!Color img, in string fileName)
in {
    assert(img !is null);
    assert(!fileName.empty);
    assert(img.nx > 0 && img.ny > 0 &&
           img.image.length == img.nx * img.ny,
           "Wrong image.");
} body {
    auto fout = fopen(fileName.toStringz(), "wb");
    if (fout == null)
        throw new Exception("File can't be opened.");
    fprintf(fout, "P5\n%d %d\n255\n", img.nx, img.ny);
    auto pix = new ubyte[img.image.length];
    foreach (i, ref p; pix)
        p = cast(typeof(pix[0]))img.image[i];
    immutable count = fwrite(pix.ptr, ubyte.sizeof,
                             img.nx * img.ny, fout);
    if (count != img.nx * img.ny)
        new Exception("Wrong number of items written.");
    fclose(fout);
}
 
 
Gray lumCIE(in RGB c) pure nothrow @nogc {
    return Gray(cast(ubyte)(0.2126 * c.r +
                            0.7152 * c.g +
                            0.0722 * c.b + 0.5));
}
 
Gray lumAVG(in RGB c) pure nothrow @nogc {
    return Gray(cast(ubyte)(0.3333 * c.r +
                            0.3333 * c.g +
                            0.3333 * c.b + 0.5));
}
 
Image!Gray rgb2grayImage(alias Conv=lumCIE)(in Image!RGB im) nothrow {
    auto result = new typeof(return)(im.nx, im.ny);
    foreach (immutable i, immutable rgb; im.image)
        result.image[i] = Conv(rgb);
    return result;
}
 
Image!RGB gray2rgbImage(in Image!Gray im) nothrow {
    auto result = new typeof(return)(im.nx, im.ny);
    foreach (immutable i, immutable gr; im.image)
        result.image[i] = RGB(gr, gr, gr);
    return result;
}
 
version (grayscale_image_main) {
    void main() {
        auto im1 = new Image!Gray;
        im1.loadPGM("lena.pgm");
        gray2rgbImage(im1).savePPM6("lena_rgb.ppm");
 
        auto img2 = new Image!RGB;
        img2.loadPPM6("quantum_frog.ppm");
        img2.rgb2grayImage.savePGM("quantum_frog_grey.pgm");
    }
}