PNMの書き込み関数を書いた。
ただし前回のPNM Readerで書いたPNM_TYPE列挙体が共通のため、前回のPNM Readerと同じファイルに書くことが望ましい。
#pragma once #include <fstream> #include <sstream> #include <string> #include <vector> #include <algorithm>
//! @brief PNMファイルの種類 enum class PNM_TYPE { P1 = 1, //Portable bitmap ASCII P2 = 2, //Portable graymap ASCII P3 = 3, //Portable pixmap ASCII P4 = 4, //Portable bitmap Binary P5 = 5, //Portable graymap Binary P6 = 6, //Portable pixmap Binary PError = 7, PBitmap = 8, PGraymap = 9, PPixmap = 10 };
////////////////////////////////////////////////////////// // PNM Reader
//! @class CGetGrayBits //! @brief 配列上のx番目の画素値を取得するクラス //! @sa https://suzulang.com/bit-depth-1-2-4-getpixelvalue/ class CGetGrayBits { unsigned char* m_p; //!< 画素へのポインタ const int m_depth = 1; //!< ビット深度 unsigned char m_mask = 1; //!< depth8未満の時のビットマスク public: //! @brief コンストラクタ //! @param [in] p 画素配列へのポインタ CGetGrayBits( const void* const p) : m_p(static_cast<unsigned char*>(const_cast<void*>(p))) { } //! @brief 指定した番号の画素を取得 //! @param [in] x 画素番号 //! @return 画素値 int operator[](const std::size_t x) { // x ドットが何バイト目かを算出 (0開始) std::size_t byte = x / (8 / m_depth); unsigned char c = m_p[byte]; std::size_t pos = x % (8 / m_depth); int shift = 8 - m_depth - pos * m_depth; unsigned char mask1 = m_mask << shift; unsigned int val = (c & mask1) >> shift; return val; } }; //! @brief ストリームから入力 bitmap ASCII //! @param [out] img 画像の格納先 //! @param [out] s 入力ストリーム void read_P1(std::vector<unsigned char>& img, std::ifstream& s) { while (s.eof() == false) { char c; s.read(&c, 1); if (isdigit(c) != false) { img.push_back(c - '0'); } } } //! @brief ストリームから入力 graymap,pixmap ASCII //! @param [out] img 画像の格納先 //! @param [out] s 入力ストリーム void read_P23(std::vector<unsigned char> & img, std::ifstream & s) { while (s.eof() == false) { std::string val; s >> val; int value; try { value = std::stoi(val); } catch (std::invalid_argument) { continue; } img.push_back(value); } } //! @brief ストリームから入力 bitmap Binary //! @param [out] img 画像の格納先 //! @param [out] s 入力ストリーム //! @return なし //! @details P4は1ビット=1画素なので全部読み込んでから1ビットずつとりだしている void read_P4(std::vector<unsigned char> & img, std::ifstream & s, int count) { std::vector<unsigned char> tmp; unsigned char tc; while (!s.eof()) { s.read((char*)& tc, sizeof(char)); tmp.push_back(tc); } CGetGrayBits bits(&tmp[0]); for (size_t i = 0; i < count; i++) { img.push_back(bits[i]); } } //! @brief ストリームから入力 graymap,pixmap Binary //! @param [out] img 画像の格納先 //! @param [out] s 入力ストリーム //! @return なし void read_P56(std::vector<unsigned char> & img, std::ifstream & s) { while (s.eof() == false) { unsigned char c; s.read((char*)& c, 1); img.push_back(c); } } //! @brief pnmファイルを読み込む //! @param [in] fpathname pnmファイルへのパス //! @param [out] img 画像の格納先 //! @param [out] width 横方向の画素数 //! @param [out] height 縦方向の画素数 //! @param [out] maxvalue 画素の最大値 //! @param [out] datatype 形式を識別する定数 //! @retval true 読込成功 //! @retval false 読込失敗 bool read_pnm(const char* fpathname, std::vector<unsigned char> * img, int* width, int* height, int* maxvalue, PNM_TYPE * datatype) { std::ifstream ifs(fpathname, std::ios::in | std::ios::binary); if (!ifs) return false; std::string text; *width = -1; *height = -1; *maxvalue = -1; //タイプの読み取り PNM_TYPE type; { int _type = (int)PNM_TYPE::PError; while (std::getline(ifs, text).eof() == false) { if (text[0] == 'P') { _type = ((int)text[1] - (int)'0'); break; } } switch (_type) { case (int)PNM_TYPE::P1:*datatype = PNM_TYPE::PBitmap; break; case (int)PNM_TYPE::P2:*datatype = PNM_TYPE::PGraymap; break; case (int)PNM_TYPE::P3:*datatype = PNM_TYPE::PPixmap; break; case (int)PNM_TYPE::P4:*datatype = PNM_TYPE::PBitmap; break; case (int)PNM_TYPE::P5:*datatype = PNM_TYPE::PGraymap; break; case (int)PNM_TYPE::P6:*datatype = PNM_TYPE::PPixmap; break; default: *datatype = PNM_TYPE::PError; return false; } type = (PNM_TYPE)_type; } while (std::getline(ifs, text).eof() == false) { //コメントスキップ if (text[0] == '#') continue; //コメントでないなら各種値の入力 std::stringstream ss(text); //幅入力 if (*width < 0) { ss >> *width; if (*width < 0) continue; } //高さ入力 if (*height < 0) { ss >> *height; if (*height < 0) continue; } //最大値入力 if (*datatype != PNM_TYPE::PBitmap) { if (*maxvalue < 0) { ss >> *maxvalue; if (*maxvalue < 0) continue; } } break; } int count = *width * *height; switch (type) { case PNM_TYPE::P1: img->reserve(count); read_P1(*img, ifs); break; case PNM_TYPE::P2: img->reserve(count); read_P23(*img, ifs); break; case PNM_TYPE::P3: img->reserve(count * 3); read_P23(*img, ifs); break; case PNM_TYPE::P4: read_P4(*img, ifs, count); break; case PNM_TYPE::P5: read_P56(*img, ifs); break; case PNM_TYPE::P6: read_P56(*img, ifs); break; } }
// ////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////// // PNM Writer
//! @brief 1byteの各ビットに配列のようにアクセスするためのクラス class bititerator_t { unsigned char* m_c; int m_index; public: bititerator_t(unsigned char* c, const int index) { m_c = c; m_index = 7 - index; } void operator=(const int value) { unsigned char mask = (1 << m_index); if (value) { *m_c |= mask; } else { *m_c &= ~mask; } } operator unsigned char() { unsigned char mask = (1 << m_index); return (*m_c & mask) ? 1 : 0; } }; //! @brief 1byteの各ビットに配列のようにアクセスするためのクラス class BitArray { unsigned char c; public: BitArray() :c(0) {} bititerator_t operator[](const int i) { return bititerator_t(&c, i); } operator unsigned char() { return c; } unsigned char operator=(const unsigned char value) { c = value; return c; } };
////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////
//! @brief データタイプを文字列に変換 std::string to_string(const PNM_TYPE type) { switch (type) { case PNM_TYPE::P1:return "P1"; break; case PNM_TYPE::P2:return "P2"; break; case PNM_TYPE::P3:return "P3"; break; case PNM_TYPE::P4:return "P4"; break; case PNM_TYPE::P5:return "P5"; break; case PNM_TYPE::P6:return "P6"; break; } return ""; } //! @brief 各データタイプにおける1ピクセルのビット数を求める int pixel_bit_count(const PNM_TYPE type) { switch (type) { case PNM_TYPE::P1:return 1; break; case PNM_TYPE::P2:return 8; break; case PNM_TYPE::P3:return 8 * 3; break; case PNM_TYPE::P4:return 1; break; case PNM_TYPE::P5:return 8; break; case PNM_TYPE::P6:return 8 * 3; break; } return -1; }
//! @brief Portable bitmap ASCII //! @param [in,out] s 出力先 //! @param [in] img 1byte1画素の画像データ //! @param [in] width 幅画素数 //! @param [in] height 高さ画素数 //! @return なし void write_P1(std::ofstream& s, const unsigned char* img, const int width, const int height) { int count = width * height; for (size_t i = 0; i < count; i++) { s << std::to_string(img[i] ? 1 : 0); } }
//! @brief ストリームへ出力 Portable graymap ASCII //! @param [in,out] 出力先 //! @param [in] img 画像データ //! @param [in] width 画素数幅 //! @param [in] height 画素数高さ //! @return なし void write_P2(std::ofstream & s, const unsigned char* img, const int width, const int height) { int count = width * height; int i = 0; for (size_t h = 0; h < height; h++) { for (size_t w = 0; w < width; w++) { s << std::to_string(img[i]) << " "; i++; } s << std::endl; } }
//! @brief ストリームへ出力 Portable pixmap ASCII //! @param [in,out] 出力先 //! @param [in] img 画像データ //! @param [in] width 画素数幅 //! @param [in] height 画素数高さ //! @return なし void write_P3(std::ofstream & s, const unsigned char* img, const int width, const int height) { int count = width * height; int i = 0; for (size_t h = 0; h < height; h++) { for (size_t w = 0; w < width; w++) { s << std::to_string(img[i * 3 + 0]) << " "; s << std::to_string(img[i * 3 + 1]) << " "; s << std::to_string(img[i * 3 + 2]) << " "; i++; } s << std::endl; } }
//! @brief ストリームへ出力 Portable bitmap Binary //@details 入力が1byte 1pixel なので、 8要素を1byteにまとめる //! @param [in,out] 出力先 //! @param [in] img 画像データ //! @param [in] count 画素数 //! @return なし void write_P4(std::ofstream & s, const unsigned char* img, const int count) { int bitindex = 0; BitArray bits; for (size_t i = 0; i < count; i++) { if (bitindex == 0) { bits = 0; } bits[bitindex] = img[i]; bitindex++; bitindex %= 8; if (bitindex == 0) { unsigned char c = bits; s.write((const char*)& c, 1); } } }
//! @brief ストリームへ出力 Portable graymap Binary , Portable pixmap Binary //@details 入力が1byte 1pixel なので、 8要素を1byteにまとめる //! @param [in,out] 出力先 //! @param [in] img 画像データ //! @param [in] bytes 画像データのバイト数 //! @return なし void write_P56(std::ofstream & s, const unsigned char* img, const int bytes) { s.write((char*)img, bytes); }
//! @brief pnmファイルを書き込む //! @param [in] fpathname pnmファイルへのパス //! @param [in] img 画像の格納先 //! @param [int] width 横方向の画素数 //! @param [in] height 縦方向の画素数 //! @param [in] datatype 形式を識別、指定する定数 //! @retval true 書き込み成功 //! @retval false 書き込み失敗 bool write_pnm(const char* fpathname, const unsigned char* img, const int width, const int height, const PNM_TYPE datatype) { if (datatype >= PNM_TYPE::PError) return false; int count = width * height; if (count <= 0) return false; std::ofstream ofs(fpathname, std::ios::out | std::ios::binary); if (!ofs) return false; int pixelbit = pixel_bit_count(datatype); ofs << to_string(datatype) << std::endl; ofs << width << " " << height << std::endl; int maxvalue = -1; if ((datatype != PNM_TYPE::P1) && (datatype != PNM_TYPE::P4)) { maxvalue = *std::max_element( img, img + count * pixel_bit_count(datatype) / 8); ofs << maxvalue << std::endl; } switch (datatype) { case PNM_TYPE::P1: write_P1(ofs, img, width, height); break; case PNM_TYPE::P2: write_P2(ofs, img, width, height); break; case PNM_TYPE::P3: write_P3(ofs, img, width, height); break; case PNM_TYPE::P4: write_P4(ofs, img, width * height); break; case PNM_TYPE::P5: write_P56(ofs, img, width * height); break; case PNM_TYPE::P6: write_P56(ofs, img, width * height * 3); break; } }
#include <iostream> #include "pnm_rw.hpp" int main() { const char* fpathname = "C:\\test\\butterfly-5546907_640-ascii.pbm"; std::vector<unsigned char> img; int width, height, maxvalue; PNM_TYPE type; read_pnm( fpathname, &img, &width, &height, &maxvalue, &type );
switch (type) { case PNM_TYPE::PBitmap: write_pnm("C:\\test\\out-ascii.pbm", img.data(), width, height, PNM_TYPE::P1); write_pnm("C:\\test\\out-binary.pbm", img.data(), width, height, PNM_TYPE::P4); break; case PNM_TYPE::PGraymap: write_pnm("C:\\test\\out-ascii.pgm", img.data(), width, height, PNM_TYPE::P2); write_pnm("C:\\test\\out-binary.pgm", img.data(), width, height, PNM_TYPE::P5); break; case PNM_TYPE::PPixmap: write_pnm("C:\\test\\out-ascii.ppm", img.data(), width, height, PNM_TYPE::P3); write_pnm("C:\\test\\out-binary.ppm", img.data(), width, height, PNM_TYPE::P6); break; }
}