In this first tutorial you will learn how to read and write image files and how images are represented in NICE.
If you need support for a specific image file format other than PGM/PPM, you have to compile nice-core yourself and add the necessary libraries and CMake flags.
The sample code for this tutorial does not have any dependencies other than nice-core. You can find it in the tutorial/progs subfolder.
NICE provides a common generic interface for grayscale and multi-channel images. The corresponding classes are ImageT and ColorImageT, both subclasses of GrayColorImageCommonImplementationT which contains the most basic image access methods. Those are used by many of the other modules. You can find typical use cases in the next tutorials.
Note that NICE does not have generic implementations of every function. We recommend using 8-bit images for most applications and also provide a shorthand typedef called Image.
In NICE, instances of ImageT or ColorImageT do not have to exist on disk. Files are different, completely independent objects. The ImageFile class serves as a pointer to a file in the file system.
An instance of ImageFile can be read from or written to, but it does not modify the file in any way unless you call those methods.
Constructing an ImageFile is simple:
ImageFile file("path/to/file.pgm");
This does not touch the file at all and it doesn't need to exist. If it does exist, you can read the image file into memory like this:
Image image_in_memory;
file.reader(&image_in_memory);
After making our changes to the image (in memory), we can write them to the file:
file.writer(&image_in_memory);
Sometimes it can be useful to access metadata without reading the whole image into memory. ImageFile provides the getHeader method for this. It reads the file and returns an instance of Header with the fields width, height, bitdepth, channel.
Note that ImageFile attemtps to determine the file format by looking at the file name. You can override this behaviour by providing the type parameter using the Format enumeration.
Supported file formats include PGM/PPM out of the box and those provided by libpng, libjpeg and libimagemagick++.
The sample images do not need any additional libraries.
There is a complete sample program for every tutorial in this series. These samples are built along with NICE and can be found in the core output directory.
The sample program for this tutorial reads a file from disk to memory, displays metadata from the file header and writes the image to another file.
You can find PXM sample images to use with the sample code in the tutorial folder.
We start of the sample program by including the necessary header files. The first few tutorials explain the usage of classes in the image subdirectory.
Header files are usually named after the classes they declare. We need the ImageT and ImageFile classes, so our sample code starts like this:
#include <iostream>
#include <string>
#include <core/image/ImageT.h>
#include <core/image/ImageFile.h>
We then get the file names from the command line. NICE will automatically determine the file type from its ending. This works for reads and writes, so you can use this sample program as a simple converter between image formats.
This next part simply checks if the user specified both source and destination file. If there are enough arguments, they are stored in a string.
// Check if enough parameters were supplied
if (argc < 3) {
std::cout << "USAGE: " << argv[0] << " <input image> <output image>\n";
return -1;
}
// These are our file names
std::string input_path(argv[1]);
std::string output_path(argv[2]);
ImageFile can read the header information without reading the whole image into memory. We use the getHeader method to obtain a Header instance. The fields are written to the standard output.
// Read file header and display header information
NICE::ImageFile source_file(input_path);
NICE::ImageFile::Header source_header = source_file.getHeader();
std::cout << "Source image dimensions: " << source_header.width << " x " << source_header.height;
std::cout << " (" << source_header.channel << " channels, " << source_header.bitdepth << " bpp)\n";
To read the image, we create an empty instance of ColorImage hand it over to the file's reader method.
Writing the image to disk is just as easy: contruct an ImageFile with the destination path and call the writer method.
// Read image into memory
NICE::ColorImage source_image;
source_file.reader(&source_image);
// Write image to disk
NICE::ImageFile dest_image(output_path);
dest_image.writer(&source_image);
return 0;
Remember that ImageFile objects are just containers for a file's location and only open the file when you call methods that require reading or writing.