/////////////////////////////////////////////////////////////////////////// // // NAME // ImageIO.cpp -- image file input/output // // DESCRIPTION // Read/write image files, potentially using an interface to an // external package. // // File formats currently supported: // - a subset of Targa // - PGM (1 band) and PPM (4 band) // - PMF (multiband float) - homegrown, non-standard // (PFM already taken by postscript font maps) // - PNG (requires ImageIOpng.cpp, and pnglib and zlib packages) // // SEE ALSO // ImageIO.h longer description // ImageIOpng.cpp png reader/writer // // Copyright © Richard Szeliski and Daniel Scharstein, 2001. // See Copyright.h for more details // /////////////////////////////////////////////////////////////////////////// #include "Image.h" #include "Error.h" #include "ImageIO.h" #include // Comment out next line if you don't have the PNG library #define HAVE_PNG_LIB #ifdef HAVE_PNG_LIB // implemented in ImageIOpng.cpp void ReadFilePNG(CByteImage& img, const char* filename); void WriteFilePNG(CByteImage img, const char* filename); #endif // Comment out next line if not using jpeg library //#define HAVE_JPEG_READER #ifdef HAVE_JPEG_READER // implemented in ReadJpg.cpp void ReadFileJPG(CByteImage& img, const char* filename); #endif // // Truevision Targa (TGA): support 24 bit RGB and 32-bit RGBA files // typedef unsigned char uchar; struct CTargaHead { uchar idLength; // number of chars in identification field uchar colorMapType; // color map type uchar imageType; // image type code uchar cMapOrigin[2];// color map origin uchar cMapLength[2];// color map length uchar cMapBits; // color map entry size short x0; // x-origin of image short y0; // y-origin of image short width; // width of image short height; // height of image uchar pixelSize; // image pixel size uchar descriptor; // image descriptor byte }; // Image data type codes const int TargaRawColormap = 1; const int TargaRawRGB = 2; const int TargaRawBW = 3; const int TargaRunColormap = 9; const int TargaRunRGB = 10; const int TargaRunBW = 11; // Descriptor fields const int TargaAttrBits = 15; const int TargaScreenOrigin = (1<<5); const int TargaCMapSize = 256; const int TargaCMapBands = 3; /* const int TargaInterleaveShift = 6; const int TargaNON_INTERLEAVE 0 const int TargaTWO_INTERLEAVE 1 const int TargaFOUR_INTERLEAVE 2 const int PERMUTE_BANDS 1 */ class CTargaRLC { // Helper class to decode run-length-coded image data public: CTargaRLC(bool RLC) : m_count(0), m_RLC(RLC) {} uchar* getBytes(int nBytes, FILE *stream); private: int m_count; // remaining count in current run bool m_RLC; // is stream run-length coded? bool m_isRun; // is current stream of pixels a run? uchar m_buffer[4]; // internal buffer }; inline uchar* CTargaRLC::getBytes(int nBytes, FILE *stream) { // Get one pixel, which consists of nBytes if (nBytes > 4) throw CError("ReadFileTGA: only support pixels up to 4 bytes long"); if (! m_RLC) { if ((int)fread(m_buffer, sizeof(uchar), nBytes, stream) != nBytes) throw CError("ReadFileTGA: file is too short"); } else { if (m_count == 0) { // Read in the next run count m_count = fgetc(stream); m_isRun = (m_count & 0x80) != 0; m_count = (m_count & 0x7f) + 1; if (m_isRun) // read the pixels for this run if ((int)fread(m_buffer, sizeof(uchar), nBytes, stream) != nBytes) throw CError("ReadFileTGA: file is too short"); } if (! m_isRun) { if ((int)fread(m_buffer, sizeof(uchar), nBytes, stream) != nBytes) throw CError("ReadFileTGA: file is too short"); } m_count -= 1; } return m_buffer; } void ReadFileTGA(CByteImage& img, const char* filename) { // Open the file and read the header FILE *stream = fopen(filename, "rb"); if (stream == 0) throw CError("ReadFileTGA: could not open %s", filename); CTargaHead h; if ((int)fread(&h, sizeof(CTargaHead), 1, stream) != 1) throw CError("ReadFileTGA(%s): file is too short", filename); // Throw away the image descriptor if (h.idLength > 0) { char* tmp = new char[h.idLength]; int nread = (int)fread(tmp, sizeof(uchar), h.idLength, stream); delete tmp; // throw away this data if (nread != h.idLength) throw CError("ReadFileTGA(%s): file is too short", filename); } //bool isRun = (h.imageType & 8) != 0; bool reverseRows = (h.descriptor & TargaScreenOrigin) != 0; int fileBytes = (h.pixelSize + 7) / 8; // Read the colormap uchar colormap[TargaCMapSize][TargaCMapBands]; int cMapSize = 0; bool grayRamp = false; if (h.colorMapType == 1) { cMapSize = (h.cMapLength[1] << 8) + h.cMapLength[0]; if (h.cMapBits != 24) throw CError("ReadFileTGA(%s): only 24-bit colormap currently supported", filename); int l = fileBytes * cMapSize; if (l > TargaCMapSize * TargaCMapBands) throw CError("ReadFileTGA(%s): colormap is too large", filename); if ((int)fread(colormap, sizeof(uchar), l, stream) != l) throw CError("ReadFileTGA(%s): could not read the colormap", filename); // Check if it's just a standard gray ramp int i; for (i = 0; i < cMapSize; i++) { for (int j = 0; j < TargaCMapBands; j++) if (colormap[i][j] != i) break; } grayRamp = (i == cMapSize); // didn't break out too soon } bool isGray = h.imageType == TargaRawBW || h.imageType == TargaRunBW || (grayRamp && (h.imageType == TargaRawColormap || h.imageType == TargaRunColormap)); bool isRaw = h.imageType == TargaRawBW || h.imageType == TargaRawRGB || (h.imageType == TargaRawRGB && isGray); // Determine the image shape CShape sh(h.width, h.height, (isGray) ? 1 : 4); // Allocate the image if necessary img.ReAllocate(sh, false); // Construct a run-length code reader CTargaRLC rlc(! isRaw); // Read in the rows for (int y = 0; y < sh.height; y++) { int yr = reverseRows ? sh.height-1-y : y; uchar* ptr = (uchar *) img.PixelAddress(0, yr, 0); if (fileBytes == sh.nBands && isRaw) { // Special case for raw image, same as destination int n = sh.width*sh.nBands; if ((int)fread(ptr, sizeof(uchar), n, stream) != n) throw CError("ReadFileTGA(%s): file is too short", filename); } else { // Read one pixel at a time for (int x = 0; x < sh.width; x++, ptr += sh.nBands) { uchar* buf = rlc.getBytes(fileBytes, stream); if (fileBytes == 1 && sh.nBands == 1) { ptr[0] = buf[0]; } else if (fileBytes == 1 && sh.nBands == 4) { for (int i = 0; i < 3; i++) ptr[i] = (isGray) ? buf[0] : colormap[buf[0]][i]; ptr[3] = 255; // full alpha; } else if ((fileBytes == 3 || fileBytes == 4) && sh.nBands == 4) { int i; for (i = 0; i < fileBytes; i++) ptr[i] = buf[i]; if (i == 3) // missing alpha channel ptr[3] = 255; // full alpha; } else throw CError("ReadFileTGA(%s): unhandled pixel depth or # of bands", filename); } } } if (fclose(stream)) throw CError("ReadFileTGA(%s): error closing file", filename); } void WriteFileTGA(CImage img, const char* filename) { // Only 1, 3, or 4 bands supported CShape sh = img.Shape(); int nBands = sh.nBands; if (nBands != 1 && nBands != 3 && nBands != 4) throw CError("WriteFileTGA(%s): can only write 1, 3, or 4 bands", filename); // Only unsigned_8 supported directly #if 0 // broken for now if (img.PixType() != unsigned_8) { CImage u8img(sh, unsigned_8); TypeConvert(img, u8img); img = u8img; } #endif // Fill in the header structure CTargaHead h; memset(&h, 0, sizeof(h)); h.imageType = (nBands == 1) ? TargaRawBW : TargaRawRGB; // TODO: is TargaRawBW the right thing, or only binary? h.width = sh.width; h.height = sh.height; h.pixelSize = 8 * nBands; bool reverseRows = false; // TODO: when is this true? // Open the file and write the header FILE *stream = fopen(filename, "wb"); if (stream == 0) throw CError("WriteFileTGA: could not open %s", filename); if (fwrite(&h, sizeof(CTargaHead), 1, stream) != 1) throw CError("WriteFileTGA(%s): file is too short", filename); // Write out the rows for (int y = 0; y < sh.height; y++) { int yr = reverseRows ? sh.height-1-y : y; char* ptr = (char *) img.PixelAddress(0, yr, 0); int n = sh.width*sh.nBands; if ((int)fwrite(ptr, sizeof(uchar), n, stream) != n) throw CError("WriteFileTGA(%s): file is too short", filename); } if (fclose(stream)) throw CError("WriteFileTGA(%s): error closing file", filename); } // // Portable Graymaps: support PGM, PPM, and PMF images // void skip_comment(FILE *fp) { // skip comment lines in the headers of pnm files char c; while ((c=getc(fp)) == '#') while (getc(fp) != '\n'); ungetc(c, fp); } void skip_space(FILE *fp) { // skip white space in the headers or pnm files char c; do { c = getc(fp); } while (c == '\n' || c == ' ' || c == '\t' || c == '\r'); ungetc(c, fp); } void read_header(FILE *fp, const char *imtype, char c1, char c2, int *width, int *height, int *nbands, int thirdArg) { // read the header of a pnmfile and initialize width and height char c; if (getc(fp) != c1 || getc(fp) != c2) throw CError("ReadFilePGM: wrong magic code for %s file", imtype); skip_space(fp); skip_comment(fp); skip_space(fp); fscanf(fp, "%d", width); skip_space(fp); fscanf(fp, "%d", height); if (thirdArg) { skip_space(fp); fscanf(fp, "%d", nbands); } // skip SINGLE newline character after reading image height (or third arg) c = getc(fp); if (c == '\r') // in some files before newline c = getc(fp); if (c != '\n') { if (c == ' ' || c == '\t' || c == '\r') throw CError("newline expected in file after image height"); else throw CError("whitespace expected in file after image height"); } } void ReadFilePGM(CByteImage& img, const char* filename) { // Open the file and read the header FILE *stream = fopen(filename, "rb"); if (stream == 0) throw CError("ReadFilePGM: could not open %s", filename); int width, height, nBands; const char *dot = strrchr(filename, '.'); int isGray = 0, isFloat = 0; if (strcmp(dot, ".pgm") == 0) { read_header(stream, "PGM", 'P', '5', &width, &height, &nBands, 1); isGray = 1; } else if (strcmp(dot, ".ppm") == 0) { read_header(stream, "PGM", 'P', '6', &width, &height, &nBands, 1); isGray = 0; } else if (strcmp(dot, ".pmf") == 0) { read_header(stream, "PMF", 'P', '9', &width, &height, &nBands, 1); isGray = 0; isFloat = 1; } // Determine the image shape CShape sh(width, height, (isGray) ? 1 : (isFloat) ? nBands : 4); // Allocate the image if necessary if (isFloat) ((CFloatImage *) &img)->ReAllocate(sh); else img.ReAllocate(sh); if (isGray || isFloat) { // read PGM or PMF // read the rows int n = isFloat ? width * nBands * sizeof(float) : sh.width; for (int y = 0; y rowBuf; rowBuf.resize(n); for (int y = 0; y rowBuf; rowBuf.resize(n); for (int y = 0; y