/* * This file Copyright (C) 2010 Christopher Stamm * Fachhochschule Nordwestschweiz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU LESSER GENERAL PUBLIC LICENSE * as published by the Free Software Foundation; either version 2.1 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "pnm.h" #include #include #ifdef WIN32 #include #include #endif using namespace std; ///////////////////////////////////////////////////////////////////////////// // constants ///////////////////////////////////////////////////////////////////////////// __inline UINT16 ByteSwap(UINT16 wX) { #ifdef __BIG_ENDIAN__ return wX; #else return ((wX & 0xFF00) >> 8) | ((wX & 0x00FF) << 8); #endif } ///////////////////////////////////////////////////////////////////////////// CPNM::CPNM() : m_w(0), m_h(0), m_maxValue(0) { } ///////////////////////////////////////////////////////////////////////////// CPNM::~CPNM() { delete[] m_buffer; } ///////////////////////////////////////////////////////////////////////////// bool CPNM::ReadPNM(CPGFImage& pgf, int quality, int levels, bool roi, bool alpha) { int channels = 1, type = 0; istream& in = cin; // read magic if (!ReadMagic(in, type)) return false; #ifdef WIN32 if (type >= 4) { // set binary mode for cin (= 0) _setmode(0, _O_BINARY); } #endif // read width and height if (!ReadSize(in, m_w, m_h)) return false; // read further data depending on image type switch(type) { case 1: // Black and White case 4: if (in.good()) { m_mode = ImageModeBitmap; m_bpp = 1; m_pitch = (((m_w + 7)/8 + 3)/4)*4; channels = 1; // set colortable RGBQUAD colors[2]; FillInColorTable(colors, 2); pgf.SetColorTable(0, 2, colors); } else { cerr << "Error: Wrong PNM header" << endl; return false; } break; case 2: // Gray case 5: // read max value if (!ReadMaxValue(in, m_maxValue)) return false; { // set colortable RGBQUAD colors[256]; FillInColorTable(colors, 256); pgf.SetColorTable(0, 256, colors); } channels = 1; if (m_maxValue == 255) { m_mode = ImageModeGrayScale; m_bpp = 8; m_pitch = ((m_w + 3)/4)*4; } else { m_mode = ImageModeGray16; m_bpp = 16; m_pitch = ((2*m_w + 3)/4)*4; pgf.SetMaxValue(m_maxValue); } break; case 3: // RGB case 6: // read max value if (!ReadMaxValue(in, m_maxValue)) return false; if (m_maxValue == 255) { if (alpha) { m_mode = ImageModeRGBA; m_bpp = 32; channels = 4; m_pitch = 4*m_w; } else { m_mode = ImageModeRGBColor; m_bpp = 24; channels = 3; m_pitch = ((3*m_w + 3)/4)*4; } } else { m_mode = ImageModeRGB48; m_bpp = 48; m_pitch = ((6*m_w + 3)/4)*4; pgf.SetMaxValue(m_maxValue); } break; default: ASSERT(false); return false; } SkipComments(in); if (in.fail()) { cerr << "Error: Wrong PNM header" << endl; return false; } // create buffer m_buffer = new UINT8[m_pitch*m_h]; if (!m_buffer) cerr << "Error: out of memory" << endl; // create pgf image PGFHeader header; header.bpp = m_bpp; header.channels = channels; header.height = m_h; header.width = m_w; header.mode = m_mode; header.nLevels = levels; header.quality = quality; // header.background.rgbtBlue = header.background.rgbtGreen = header.background.rgbtRed = 255; pgf.SetHeader(header, (roi) ? PGFROI : 0); // read image data depending on image type switch(type) { case 1: // ASCII Black and White if (!ReadP1(in)) return false; break; case 2: // ASCII Gray if (!ReadP2(in)) return false; break; case 3: // ASCII RGB if (!ReadP3(in)) return false; break; case 4: // binary Black and White if (!ReadP4(in)) return false; break; case 5: // binary Gray if (!ReadP5(in)) return false; break; case 6: // binary RGB if (!ReadP6(in)) return false; break; default: ASSERT(false); return false; } if (m_mode == ImageModeRGBA) { // read alpha channel (PGM) int type2 = 0, width = 0, height = 0; if (!ReadMagic(in, type2)) return false; if (type2 != 2 && type2 != 5) { cerr << "Error: PPM followed by PGM expected" << endl; return false; } if (!ReadSize(in, width, height)) return false; if (width != m_w || height != m_h) { cerr << "Error: dimensions for PPM and PGM do not agree" << endl; return false; } if (!ReadMaxValue(in, m_maxValue)) return false; SkipComments(in); if (type2 == 2) { if (!ReadP2(in)) return false; } else { if (!ReadP5(in)) return false; } } // import bitmap data into pgf try { pgf.ImportBitmap(m_pitch, m_buffer, m_bpp); } catch(IOException&) { cerr << "Error: PGF import failed" << endl; return false; } return true; } ///////////////////////////////////////////////////////////////////////////// bool CPNM::WritePNM(CPGFImage& pgf, bool roi, PGFRect& rect, bool binary /*= true*/) { if (!ImportIsSupported(pgf.Mode())) { cerr << "Error: PGF image mode is not supported" << endl; return false; } else { m_mode = pgf.Mode(); } // store image size if (roi) { m_w = rect.Width(); m_h = rect.Height(); } else { m_w = pgf.Width(); m_h = pgf.Height(); } // set pnm file type and pitch int type = 0; switch(m_mode) { case ImageModeBitmap: type = 1; m_bpp = 1; m_pitch = (((m_w + 7)/8 + 3)/4)*4; break; case ImageModeGrayScale: type = 2; m_bpp = 8; m_pitch = ((m_w + 3)/4)*4; break; case ImageModeGray16: type = 2; m_bpp = 16; m_pitch = ((2*m_w + 3)/4)*4; m_maxValue = pgf.GetMaxValue(); break; case ImageModeRGBColor: type = 3; m_bpp = 24; m_pitch = ((3*m_w + 3)/4)*4; break; case ImageModeRGB48: type = 3; m_bpp = 48; m_pitch = ((6*m_w + 3)/4)*4; m_maxValue = pgf.GetMaxValue(); break; case ImageModeRGBA: type = 3; m_bpp = 32; m_pitch = 4*m_w; break; default: ASSERT(false); return false; } if (binary) type += 3; // create buffer m_buffer = new UINT8[m_pitch*m_h]; if (!m_buffer) cerr << "Error: out of memory" << endl; // copy pgf image buffer try { pgf.GetBitmap(m_pitch, m_buffer, m_bpp); } catch(IOException&) { cerr << "Error: PGF decompression failed" << endl; return false; } // write pnm ostream& out = cout; #ifdef WIN32 if (binary) { // set binary mode for cout (= 1) _setmode(1, _O_BINARY); } #endif // write magic ASSERT(type); out << "P" /*<< noshowpos*/ << type << endl; // write width and height out << m_w << ' ' << m_h << endl; // write further data depending on image type switch(type) { case 1: if (!WriteP1(out)) return false; break; case 2: if (!WriteP2(out)) return false; break; case 3: if (!WriteP3(out)) return false; break; case 4: if (!WriteP4(out)) return false; break; case 5: if (!WriteP5(out)) return false; break; case 6: if (!WriteP6(out)) return false; break; default: ASSERT(false); return false; } if (m_mode == ImageModeRGBA) { // write alpha channel as PGM image if (binary) { out << endl << "P5" << endl; } else { out << "P2" << endl; } // write width and height out << m_w << ' ' << m_h << endl; if (binary) { if (!WriteP5(out)) return false; } else { if (!WriteP2(out)) return false; } } return true; } ///////////////////////////////////////////////////////////////////////////// // Read ASCII Black and White image file // Pixel order of a row in file corresponds with pixel order on screen. But in memory it is different: // |01234567|01234567|01234567| ... pixel number on sreen and in file // | Byte 0 | Byte 1 | Byte 2 | ... bytes on screen and in file and memory // |msb lsb|msb lsb|msb lsb| ... bits in memory // bool CPNM::ReadP1(std::istream& in) { // read data UINT8 *buff = m_buffer; int pos, val; int v; int numOfBytes = (m_w + 7)/8; for(int i = 0; i < m_h; i++) { pos = 0; for(int j = 0; j < numOfBytes; j++) { val = 0; for(int k = 0; k < 8; k++) { val *= 2; if (pos < m_w) { in >> v; ASSERT(v == 0 || v == 1); // read msb first val += v; } pos++; } buff[j] = (UINT8)val; } buff += m_pitch; } return true; } ///////////////////////////////////////////////////////////////////////////// // Read ASCII Gray image file bool CPNM::ReadP2(std::istream& in) { // read data int v; if (m_mode == ImageModeGray16) { int pitch16 = m_pitch/2; UINT16 *buff16 = (UINT16 *)m_buffer; for(int i = 0; i < m_h; i++) { for(int j = 0; j < m_w; j++) { in >> v; buff16[j] = (UINT16)v; } buff16 += pitch16; } } else if (m_mode == ImageModeRGBA) { // read alpha channel ASSERT(m_bpp == 32); UINT8 *buff = m_buffer; int pos; for(int i = 0; i < m_h; i++) { pos = 3; for(int j = 0; j < m_w; j++) { in >> v; buff[pos] = UINT8(v*255/m_maxValue); pos += 4; } buff += m_pitch; } } else { UINT8 *buff = m_buffer; for(int i = 0; i < m_h; i++) { for(int j = 0; j < m_w; j++) { in >> v; buff[j] = UINT8(v*255/m_maxValue); } buff += m_pitch; } } return true; } ///////////////////////////////////////////////////////////////////////////// // Read ASCII RGB image file bool CPNM::ReadP3(std::istream& in) { // read data int pos, v; if (m_mode == ImageModeRGB48) { int pitch16 = m_pitch/2; UINT16 *buff16 = (UINT16 *)m_buffer; for(int i = 0; i < m_h; i++) { pos = 0; for(int j = 0; j < m_w; j++) { in >> v; buff16[pos + 2] = (UINT16)v; // R in >> v; buff16[pos + 1] = (UINT16)v; // G in >> v; buff16[pos] = (UINT16)v; // B pos += 3; } buff16 += pitch16; } } else { UINT8 *buff = m_buffer; int bypp = 3; if (m_mode == ImageModeRGBA) { // read RGB of RGBA ASSERT(m_bpp == 32); bypp = 4; } for(int i = 0; i < m_h; i++) { pos = 0; for(int j = 0; j < m_w; j++) { in >> v; buff[pos + 2] = UINT8(v*255/m_maxValue); // R in >> v; buff[pos + 1] = UINT8(v*255/m_maxValue); // G in >> v; buff[pos] = UINT8(v*255/m_maxValue); // B pos += bypp; } buff += m_pitch; } } return true; } ///////////////////////////////////////////////////////////////////////////// // Read binary Black and White image file bool CPNM::ReadP4(std::istream& in) { // read data int numOfBytes = (m_w + 7)/8; char *buff = (char *)m_buffer; for(int i = 0; i < m_h; i++) { in.read(buff, numOfBytes); buff += m_pitch; } return true; } ///////////////////////////////////////////////////////////////////////////// // Read binary Gray image file bool CPNM::ReadP5(std::istream& in) { // read data if (m_mode == ImageModeGray16) { // data is in big-endian format int pitch16 = m_pitch/2; UINT16 *buff16 = (UINT16 *)m_buffer; for(int i = 0; i < m_h; i++) { in.read((char *)buff16, m_w*2); for(int j = 0; j < m_w; j++) { buff16[j] = ByteSwap(buff16[j]); } buff16 += pitch16; } } else if (m_mode == ImageModeRGBA) { // read alpha channel ASSERT(m_bpp == 32); UINT8 *buff = m_buffer; char *row = new char[m_w]; int pos; for(int i = 0; i < m_h; i++) { in.read(row, m_w); pos = 3; if (m_maxValue < 255) { for(int j = 0; j < m_w; j++) { buff[pos] = UINT8(row[j]*255/m_maxValue); pos += 4; } } else { for(int j = 0; j < m_w; j++) { buff[pos] = row[j]; pos += 4; } } buff += m_pitch; } delete[] row; } else { char *buff = (char *)m_buffer; for(int i = 0; i < m_h; i++) { in.read(buff, m_w); if (m_maxValue < 255) { for(int j = 0; j < m_w; j++) { buff[j] = UINT8(buff[j]*255/m_maxValue); } } buff += m_pitch; } } return true; } ///////////////////////////////////////////////////////////////////////////// // Read binary RGB image file bool CPNM::ReadP6(std::istream& in) { // read data UINT8 tmp; int pos; if (m_mode == ImageModeRGB48) { // data is in big-endian format int pitch16 = m_pitch/2; UINT16 *buff16 = (UINT16 *)m_buffer; UINT16 t; for(int i = 0; i < m_h; i++) { in.read((char *)buff16, m_w*6); pos = 0; for(int j = 0; j < m_w; j++) { // swap R and B t = buff16[pos]; buff16[pos] = ByteSwap(buff16[pos + 2]); buff16[pos + 1] = ByteSwap(buff16[pos + 1]); buff16[pos + 2] = ByteSwap(t); } buff16 += pitch16; } } else if (m_mode == ImageModeRGBA) { // read alpha channel ASSERT(m_bpp == 32); UINT8 *buff = m_buffer; char *row = new char[3*m_w]; int rpos; for(int i = 0; i < m_h; i++) { in.read(row, 3*m_w); pos = 0; rpos = 0; if (m_maxValue < 255) { for(int j = 0; j < m_w; j++) { buff[pos] = UINT8(row[rpos + 2]*255/m_maxValue); // B buff[pos + 1] = UINT8(row[rpos + 1]*255/m_maxValue); // G buff[pos + 2] = UINT8(row[rpos ]*255/m_maxValue); // R pos += 4; rpos += 3; } } else { for(int j = 0; j < m_w; j++) { buff[pos] = row[rpos + 2]; // B buff[pos + 1] = row[rpos + 1]; // G buff[pos + 2] = row[rpos]; // R pos += 4; rpos += 3; } } buff += m_pitch; } delete[] row; } else { char *buff = (char *)m_buffer; for(int i = 0; i < m_h; i++) { in.read((char *)buff, 3*m_w); pos = 0; if (m_maxValue < 255) { for(int j = 0; j < m_w; j++) { // swap R and B tmp = buff[pos]; buff[pos] = buff[pos + 2]; buff[pos + 2] = tmp; pos += 3; } } else { for(int j = 0; j < m_w; j++) { buff[pos + 1] = UINT8(buff[pos + 1]*255/m_maxValue); // G // swap R and B tmp = UINT8(buff[pos]*255/m_maxValue); buff[pos] = UINT8(buff[pos + 2]*255/m_maxValue); buff[pos + 2] = tmp; pos += 3; } } buff += m_pitch; } } return true; } ///////////////////////////////////////////////////////////////////////////// // Write ASCII black and white file // Pixel order of a row in file corresponds with pixel order on screen. But in memory it is different: // |01234567|01234567|01234567| ... pixel number on sreen and in file // | Byte 0 | Byte 1 | Byte 2 | ... bytes on screen and in file and memory // |msb lsb|msb lsb|msb lsb| ... bits in memory // bool CPNM::WriteP1(std::ostream& out) { ASSERT(m_buffer); const int numOfBytes = (m_w + 7)/8; UINT8 *buff8 = m_buffer; int pos; char z; // write data for (int i = 0; i < m_h; i++) { pos = 0; for (int j = 0; j < numOfBytes; j++) { z = buff8[j]; for(int k = 0; k < 8 && pos < m_w; k++) { out << ((z < 0) ? 1 : 0) << ' '; // write msb first z <<= 1; pos++; } } buff8 += m_pitch; out << std::endl; } return true; } ///////////////////////////////////////////////////////////////////////////// // Write ASCII gray image file bool CPNM::WriteP2(std::ostream& out) { ASSERT(m_buffer); if (m_mode == ImageModeGrayScale) { UINT8 *buff = m_buffer; // write maxvalue out << 255 << std::endl; // write data for (int i = 0; i < m_h; i++) { for (int j = 0; j < m_w; j++) { out << (int)buff[j] << ' '; } buff += m_pitch; out << std::endl; } } else if (m_mode == ImageModeRGBA) { // write alpha channel ASSERT(m_bpp == 32); UINT8 *buff = m_buffer; // write maxvalue out << 255 << std::endl; // write data for (int i = 0; i < m_h; i++) { for (int j = 0; j < m_w; j++) { out << (int)buff[j*4 + 3] << ' '; } buff += m_pitch; out << std::endl; } } else { ASSERT(m_mode == ImageModeGray16); int pitch = m_pitch/2; UINT16 *buff = (UINT16*)m_buffer; // write maxvalue out << m_maxValue << std::endl; // write data for (int i = 0; i < m_h; i++) { for (int j = 0; j < m_w; j++) { out << (int)buff[j] << ' '; } buff += pitch; out << std::endl; } } return true; } ///////////////////////////////////////////////////////////////////////////// // Write ASCII RGB color file bool CPNM::WriteP3(std::ostream& out) { ASSERT(m_buffer); if (m_mode == ImageModeRGB48) { int pos, pitch = m_pitch/2; UINT16 *buff = (UINT16*)m_buffer; // write maxvalue out << m_maxValue << std::endl; // write data for (int i = 0; i < m_h; i++) { pos = 0; for (int j = 0; j < m_w; j++) { out << (int)buff[pos + 2] << ' '; // R out << (int)buff[pos + 1] << ' '; // G out << (int)buff[pos] << ' '; // B pos += 3; } buff += pitch; out << std::endl; } } else { int pos; UINT8 *buff = m_buffer; int bypp = 3; if (m_mode == ImageModeRGBA) { // write RGB of RGBA ASSERT(m_bpp == 32); bypp = 4; } // write maxvalue out << 255 << std::endl; // write data for (int i = 0; i < m_h; i++) { pos = 0; for (int j = 0; j < m_w; j++) { out << (int)buff[pos + 2] << ' '; // R out << (int)buff[pos + 1] << ' '; // G out << (int)buff[pos] << ' '; // B pos += bypp; } buff += m_pitch; out << std::endl; } } return true; } ///////////////////////////////////////////////////////////////////////////// // Write binary black and white file bool CPNM::WriteP4(std::ostream& out) { ASSERT(m_buffer); const int numOfBytes = (m_w + 7)/8; ASSERT(m_pitch >= numOfBytes); char *buff8 = (char *)m_buffer; // write buffer for(int i = 0; i < m_h; i++) { out.write(buff8, numOfBytes); buff8 += m_pitch; } return true; } ///////////////////////////////////////////////////////////////////////////// // Write binary gray image file bool CPNM::WriteP5(std::ostream& out) { ASSERT(m_buffer); // write maxvalue if (m_mode == ImageModeGrayScale) { char *buff = (char *)m_buffer; // write max value out << 255 << std::endl; // write data for(int i = 0; i < m_h; i++) { out.write(buff, m_w); buff += m_pitch; } } else if (m_mode == ImageModeRGBA) { // write alpha channel ASSERT(m_bpp == 32); // write max value out << 255 << std::endl; char *row = new char[m_w]; char *buff = (char *)m_buffer; // write data for (int i = 0; i < m_h; i++) { for(int j = 0; j < m_w; j++) { row[j] = buff[j*4 + 3]; } out.write(row, m_w); buff += m_pitch; } delete[] row; } else { ASSERT(m_mode == ImageModeGray16); int pitch = m_pitch/2; UINT16 *buff = (UINT16 *)m_buffer; UINT16 val; // write max value out << m_maxValue << std::endl; // write data in big-endian format for(int i = 0; i < m_h; i++) { for(int j = 0; j < m_w; j++) { val = ByteSwap(buff[j]); out.write((char *)&val, 2); } buff += pitch; } } return true; } ///////////////////////////////////////////////////////////////////////////// // Write binary RGB color file bool CPNM::WriteP6(std::ostream& out) { ASSERT(m_buffer); int pos = 0; if (m_mode == ImageModeRGB48) { int pitch = m_pitch/2; UINT16 *buff = (UINT16 *)m_buffer; UINT16 val; // write maxvalue out << m_maxValue << std::endl; // write data in big-endian format for(int i = 0; i < m_h; i++) { pos = 0; for(int j = 0; j < m_w; j++) { val = ByteSwap(buff[pos + 2]); out.write((char *)&val, 2); // R val = ByteSwap(buff[pos + 1]); out.write((char *)&val, 2); // G val = ByteSwap(buff[pos]); out.write((char *)&val, 2); // B pos += 3; } buff += pitch; } } else { char *buff = (char *)m_buffer; int bypp = 3; if (m_mode == ImageModeRGBA) { // write RGB of RGBA ASSERT(m_bpp == 32); bypp = 4; } // write maxvalue out << 255 << std::endl; // write data for(int i = 0; i < m_h; i++) { pos = 0; for(int j = 0; j < m_w; j++) { out.write(&buff[pos + 2], 1); // R out.write(&buff[pos + 1], 1); // G out.write(&buff[pos], 1); // B pos += bypp; } buff += m_pitch; } } return true; } ////////////////////////////////////////////////////////////////// // Return true if the given image mode is supported for import bool CPNM::ImportIsSupported(BYTE mode) { switch(mode) { case ImageModeBitmap: case ImageModeGrayScale: case ImageModeRGBColor: case ImageModeGray16: case ImageModeRGB48: case ImageModeRGBA: return true; } return false; } ///////////////////////////////////////////////////////////////////////////// void CPNM::FillInColorTable(RGBQUAD* table, int size) { if (size == 2) { // bitmap color table table[0].rgbBlue = 255; table[0].rgbGreen = 255; table[0].rgbRed = 255; table[1].rgbBlue = 0; table[1].rgbGreen = 0; table[1].rgbRed = 0; } else if (size == 256) { // gray scale color table for (int i=0; i < 256; i++) { table[i].rgbBlue = (BYTE)i; table[i].rgbGreen = (BYTE)i; table[i].rgbRed = (BYTE)i; } } else { ASSERT(false); } } ///////////////////////////////////////////////////////////////////////////// bool CPNM::ReadMagic(istream& in, int& type) { char m = 0; SkipComments(in); if (in.good()) { in >> m >> type; } if (m != 'P' || type <= 0 || type > 6) { // unknown type cerr << "Error: Unknown PNM type specifier" << endl; return false; } return true; } ///////////////////////////////////////////////////////////////////////////// bool CPNM::ReadSize(istream& in, int& width, int& height) { SkipComments(in); if (in.good()) { in >> width >> height; if (width <= 0 || height <= 0) { cerr << "Error: Wrong PNM width or height" << endl; return false; } } else { cerr << "Error: Wrong PNM width or height" << endl; return false; } return true; } ///////////////////////////////////////////////////////////////////////////// bool CPNM::ReadMaxValue(istream& in, int& maxValue) { SkipComments(in); if (in.good()) { in >> maxValue; if (maxValue <= 0 || maxValue >= 65536) { cerr << "Error: Wrong PNM maximum value" << endl; return false; } } else { cerr << "Error: Wrong PNM maximum value" << endl; return false; } return true; } ///////////////////////////////////////////////////////////////////////////// void CPNM::SkipComments(istream& in) { int c = in.peek(); while(in.good() && (c == '#' || c == '\n')) { in.ignore(1000, '\n'); c = in.peek(); } }