Subpixels

Every day on my way to the office, I pass this large animated display on Flatbush Avenue for the Brooklyn Academy of Music. It's a good illustration for computing novices of how pixels in an image are made up solely of red, green, and blue lights. On this sign, it's easy to see the subpixel configuration when you are up close:

      GRB
      BRG

Below it is another sign, with differently-shaped pixels, that I saw recently in Orlando. It has one blue light in the center, with two red and green lights at diagonals.

I decided to write a program I could use to demonstrate what any image would look like when rendered by the BAM sign. Each pixel in the input image is expanded into 4×3 pixels in the output image, including a black margin between each group of subpixels.

You can see the result to the right. Bart's eyes still look relatively white, but if you look close or zoom in, they're made up only of red-green-blue dots. Below is the code, using the Magick++ library. See also the subpixelize repository on github.

#include <Magick++.h>
#include <iostream>
#include <cassert>

using std::cout;
using std::string;
using namespace Magick;

/* Each pixel in input image is rendered as
 *    GRBx
 *    BRGx
 *    xxxx
 * in the output image. So pixels are 4x3,
 * including inter-pixel space.
 */

int main(int argc, char** argv)
{
    InitializeMagick(*argv);
    if(argc != 3) {
        cout << "Usage: " << argv[0] << " input.png output.png\n";
        exit(1);
    }

    const string inputPath = argv[1];
    const string outputPath = argv[2];

    cout << "Loading " << inputPath << '\n';
    Image in (inputPath);
    Geometry gin = in.size();
    assert(in.depth() <= 8);
    assert(in.type() == TrueColorType);
    cout << gin.width() << 'x' << gin.height() << '\n';

    Geometry gout(gin.width()*4, gin.height()*3);

    Image out;
    out.size(gout);
    out.magick("RGBA");
    cout << "Writing " << outputPath << '\n';
    for(unsigned x = 0; x < gin.width(); x++) {
        for(unsigned y = 0; y < gin.height(); y++) {
            Color c = in.pixelColor(x,y);
            unsigned i = x*4;
            unsigned j = y*3;
            // Row 0: GRB
            out.pixelColor(i+0, j+0, Color(0, c.greenQuantum(), 0));
            out.pixelColor(i+1, j+0, Color(c.redQuantum(), 0, 0));
            out.pixelColor(i+2, j+0, Color(0, 0, c.blueQuantum()));
            // Row 1: BRG
            out.pixelColor(i+0, j+1, Color(0, 0, c.blueQuantum()));
            out.pixelColor(i+1, j+1, Color(c.redQuantum(), 0, 0));
            out.pixelColor(i+2, j+1, Color(0, c.greenQuantum(), 0));
        }
    }
    out.write(outputPath);
    return 0;
}
    

©20022015 Christopher League