Loading an image in to a custom image object

One of the major design goals of pngxx and jpegxx was to provide the ability to load images directly in to your own image object.

You don't need to use one provided by pngxx or jpegxx. Indeed, it is impossible to guess what every possible user might need of an image container.

So, let us suppose that there is a class rgb_image that looks something like this:

structrgb{rgb(unsignedcharr,unsignedcharg,unsignedcharb);unsignedcharr,g,b;};classrgb_image{public:// Constructs a blank image of the given size.rgb_image(unsignedwidth,unsignedheight);// Get the width of the image (in pixels)unsignedwidth()const;// Get the height of the image (in pixels)unsignedheight()const;// Get a reference to the pixel in the ith column, jth rowrgb&operator()(unsignedi,unsignedj);// ...};

Our aim is now to read the data from a PNG or JPEG file in to such an rgb_image object. We will assume that a PNG image is being loaded and will therefore use pngxx. The process is entirely analogous for loading JPEG images with jpegxx.

Let's suppose we're handed an std::string containing the file name of an image. We'll first need to read the header of the image so that we can construct a blank rgb_image of the correct dimensions:

typedefstd::istreambuf_iterator<char>it_t;std::ifstreamsrc(filename.c_str(),std::ios::binary);// binary flag is importantif(!src)throwstd::runtime_error("failed to open file: "+filename);it_tb(src),e;// beginning and end of the streampngxx::loaderldr;it_tresume=ldr.read_header(b,e);constimagexx::raster_details&d=ldr.details();// construct the imagergb_imageimg(d.width(),d.height());

We've now read the header of the image file and constructed an image of the appropriate size. But it's still blank. Let's fill it in. We first need to define a little class, called a pixel composer. It must look like the following (though the name of the class can be anything you like; I'm using rgb_pixel_composer here because it will compose pixels for an rgb_image):

classrgb_pixel_composer{public:voidset_rgb(unsignedcharr,unsignedcharg,unsignedcharb);voidset_grey(unsignedcharg);voidset_rgba(unsignedcharr,unsignedcharg,unsignedcharb,unsignedchara);voidset_grey_alpha(unsignedcharg,unsignedchara);voidnext_pixel();// copy construction and assignment must be supported};

The set_rgb, set_grey, set_rgba and set_grey_alpha functions must be implemented to assign a colour to the "current pixel" of an image. If we were loading a JPEG image using jpegxx, we wouldn't need to have set_rgba or set_grey_alpha functions in our composer because the JPEG compression scheme does not support transparency.

So let's implement the rgb_pixel_composer functions. They're all short and one of them will be called to fill in each pixel of an rgb_image, meaning that they have the potential to be called millions of times per image (depending on the size of the image). So I'll define the functions inline:

classrgb_pixel_composer{public:typedefunsignedcharuchar;rgb_pixel_composer(rgb_image&img):img(&img),i(0),j(0){}voidset_rgb(ucharr,ucharg,ucharb){(*img)(i,j)=rgb(r,g,b);}voidset_grey(ucharg){set_rgb(g,g,g);}voidset_rgba(ucharr,ucharg,ucharb,uchara){set_rgb(r,g,b);}voidset_grey_alpha(ucharg,uchara){set_rgb(g,g,g);}voidnext_pixel(){// update i and j, so that the next set_xxx() call will // fill in the next pixeli=(i+1)%img->width();if(i==0)++j;}private:rgb_image*img;unsignedi,j;};

Since the image we're reading in to is only able to hold RGB pixels, each function forwards its data on to set_rgb(). If you require more sophisticated conversions, you are free to implement them however you like.

As an aside, a lot of image classes store their pixels contiguously in an array. In this case, the pixel composer would only need to store a single pointer-to-pixel and next_pixel() would simply increment that pointer.

Ok, now we complete the loading of the image. To do this we need to construct an output iterator. imagexx provides a template class called pixel_compose_iterator that can use a pixel composer, such as the one we've defined, for output.

Here, we're looking at the type of image being read and constructing an appropriate pixel_compose_iterator that uses an rgb_pixel_composer. The 2nd template parameter tells the pixel_compose_iterator which member function of the pixel composer to call. If you're reading a JPEG image with jpegxx, you won't need to worry about the two formats that have an alpha component.