スポンサーリンク

bit depth が非8の倍数ビットの時に画素値を取得する

libpngで8bit,16bit以外のビット深度のグレイスケール画像を読み込むと、メモリ上には1bit,2bit,4bitなどごとにデータが格納されているので、1byteごとデータを取り出すような簡単な処理で取得できない。

ちょっと調べた限りでは画素値を素直に取り出す関数などが見当たらなかった(あるかもしれない)のでとりあえず適当に作ることにした。

動作

例えば、bit depth==4で

10101011 , 01001110 , 10000101 というデータがあったら、

1010,1011,0100,1110,1000,0101

と分割する

原理

対象が1bit,2bit,4bitの時は、以下のような感じで、該当部分のビット列だけを抜き出して右にシフトする。

対象が8bitの時はデータをそのままunsigned char型の配列として扱う。

対象が16bitの時はデータをそのままunsigned short型の配列として扱い、取得した2byteの前後をswapする。

//! @file CGetGrayBits.hpp
//! @brief 1bit,2bit,4bit,8bit,16bitごとに納められたデータを取得する

#pragma once


//! @class CGetGrayBits
//! @brief 配列上のx番目の画素値を取得するクラス
class CGetGrayBits {
  unsigned char* m_p;        //!< 画素へのポインタ
  int m_depth;               //!< ビット深度

  unsigned char m_mask = 0;  //!< depth8未満の時のビットマスク
public:

  //! @brief コンストラクタ
  //! @param [in] p 画素配列へのポインタ
  //! @param [in] depth ビット深度。1画素が何ビットで表現されているか
  CGetGrayBits(
    const void* const p,
    const int depth) :
    m_p(static_cast<unsigned char*>(const_cast<void*>(p))),
    m_depth(depth) {

    //ビット深度が8未満の時に使用するマスクを作成
    if (m_depth < 8) {
      for (int i = 0; i < m_depth; i++)
        m_mask = (m_mask << 1) | 1;
    }

  }
  //! @brief 指定した番号の画素を取得
  //! @param [in] x 画素番号
  //! @return 画素値
  int operator[](const std::size_t x) {
    switch (m_depth) {
    case 1:
    case 2:
    case 4:
      return getgrayLess8(x);
      break;
    case 8:
      return getgray8(x);
      break;
    case 16:
      return getgray16(x);
      break;
    }
    return -1;
  }
  //! @brief 指定した番号の画素を取得(ビット深度==8の時)
  //! @param [in] x 画素番号
  //! @return 画素値をintで表現したもの
  inline int getgray8(const std::size_t x)const {
    return static_cast<int>(m_p[x]);
  }
  //! @brief 指定した番号の画素を取得(ビット深度==16の時)
  //! @param [in] x 画素番号
  //! @return 画素値をintで表現したもの
  inline int getgray16(const std::size_t x)const {
    unsigned short t = reinterpret_cast<unsigned short*>(m_p)[x];
    std::swap(
      reinterpret_cast<unsigned char*>(&t)[0],
      reinterpret_cast<unsigned char*>(&t)[1]);

    return t;

  }
  //! @brief 指定した番号の画素を取得(ビット深度<8の時)
  //! @param [in] x 画素番号
  //! @return 画素値をintで表現したもの
  inline int getgrayLess8(const std::size_t x)const {
    // 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;
  }
};

使用例

#pragma once

#include <cstdlib>
#include <iostream>
#include <vector>
#include <bitset>

#include <png.h>

#include <algorithm>

#include "CGetGrayBits.hpp"

#pragma comment(lib,"libpng16.lib")

void out_to_ppm(
  const char* outpath, 
  const int width, 
  const int height, 
  const std::vector<int>& pixels
);


//エラーの時強制終了
void abort_(const char* c) {
  printf(c);
  abort();
}

//! @brief pngファイル読み込み関数
//! @param [in] file_name ファイル名
//! @param [out] width 画像幅(ピクセル)
//! @param [out] height 画像高さ(ピクセル)
//! @param [out] color_type RGBかRGBAか...等
//! @param [out] bit_depth チャンネルのビット数
//! @param [out] row_pointers 画像データへのポインタのポインタ
//! @param [out] palette パレット
//! @param [out] palette_num パレット色の個数
void read_png(
  const char* file_name,
  int* width,
  int* height,
  png_byte* color_type,
  png_byte* bit_depth,
  png_bytep** row_pointers,
  png_colorp* palette,
  int* palette_num
) {

  png_byte  header[8];    // 8 is the maximum size that can be checked

  FILE* fp = fopen(file_name, "rb");
  if (!fp) {
    abort_("[read_png_file] File could not be opened for reading");
  }
  fread(header, 1, 8, fp);
  if (png_sig_cmp(header, 0, 8)) {
    abort_("[read_png_file] File is not recognized as a PNG file");
  }


  png_structp png_ptr;
  png_infop info_ptr;

  /* initialize stuff */
  png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);

  if (!png_ptr)
    abort_("[read_png_file] png_create_read_struct failed");

  info_ptr = png_create_info_struct(png_ptr);
  if (!info_ptr)
    abort_("[read_png_file] png_create_info_struct failed");

  if (setjmp(png_jmpbuf(png_ptr)))
    abort_("[read_png_file] Error during init_io");

  png_init_io(png_ptr, fp);
  png_set_sig_bytes(png_ptr, 8);

  png_read_info(png_ptr, info_ptr);

  /////////////////////////////////////////
  // 画像情報の取得
  *width = png_get_image_width(png_ptr, info_ptr);
  *height = png_get_image_height(png_ptr, info_ptr);
  *color_type = png_get_color_type(png_ptr, info_ptr);
  *bit_depth = png_get_bit_depth(png_ptr, info_ptr);
  //
  /////////////////////////////////////////

  /////////////////////////////////////////
  // パレットの取得
  if (*color_type & PNG_COLOR_TYPE_PALETTE) {
    png_get_PLTE(png_ptr, info_ptr, palette, palette_num);
  }
  //
  /////////////////////////////////////////

  int number_of_passes;
  number_of_passes = png_set_interlace_handling(png_ptr);
  png_read_update_info(png_ptr, info_ptr);

  /* read file */
  if (setjmp(png_jmpbuf(png_ptr)))
    abort_("[read_png_file] Error during read_image");

  /////////////////////////////////////////
  // 画像の読み込み
  *row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * *height);

  for (int y = 0; y < *height; y++)
    (*row_pointers)[y] = (png_byte*)malloc(png_get_rowbytes(png_ptr, info_ptr));

  png_read_image(png_ptr, *row_pointers);
  //
  /////////////////////////////////////////


  fclose(fp);
}
int main()
{
  int width;
  int height;
  png_byte color_type;
  png_byte bit_depth;
  png_bytep* row_pointers;
  png_colorp palette;
  int palette_num;

  ////////////////////////////////
  // 画像読み込み
  read_png(
    R"(c:\test\data\snail2.png)",
    &width,
    &height,
    &color_type,
    &bit_depth,
    &row_pointers,
    &palette,
    &palette_num
  );
  //
  ////////////////////////////////

  std::cout << "width : " << width << std::endl;
  std::cout << "height: " << height << std::endl;
  std::cout << "colortype: " << (int)color_type << std::endl;
  std::cout << "bitdepth: " << (int)bit_depth << std::endl;

  ////////////////////////////////
  //グレイスケール画像だけを対象とする
  if (color_type != (PNG_COLOR_TYPE_GRAY )){
    std::cout << "not support" << std::endl;
    int i;
    std::cin >> i;
    return -1;
  }
  //
  ////////////////////////////////

  ////////////////////////////////
  // 内容を取得
  std::vector<int> imgvec;
  for (int y = 0; y < height; y++) {
    png_bytep yhead = row_pointers[y]; // 画像の横一列の先頭を取得

    CGetGrayBits g(yhead, bit_depth); // 画素取得の準備

    for (int x = 0; x < width; x++) {

      int index = g[x]; // x番目の画素値をintで取得

      imgvec.push_back( index ); // 画素値を保存
    }

  }
  //
  ///////////////////////////////////////////////
  ///////////////////////////////////////////////
  // 画像として保存
  char outpath[1024];
  sprintf(outpath, R"(C:\test\out-%d-palette.ppm)",bit_depth);
  out_to_ppm(outpath, width, height, imgvec);
  //
  ///////////////////////////////////////////////


  ///////////////////////////////////////////////
  // メモリの解放
  for (size_t i = 0; i < (size_t)height; i++) {
    png_bytep yhead = row_pointers[i];
    free(yhead);
  }
  free(row_pointers);
  //
  ///////////////////////////////////////////////

  int i;
  std::cin >> i;

}


void out_to_ppm(const char* outpath,const int width, const int height, const std::vector<int>& pixels) {

  int pixmax = *std::max_element(pixels.begin(), pixels.end());

  FILE* fp = fopen(outpath, "wb");
  fprintf(fp, "P3\n%d %d\n%d\n", width, height, 255);

  for (size_t y = 0; y < (size_t)height; y++) {

    for (size_t x = 0; x < (size_t)width; x++) {

      int index = y * width + x;

      unsigned char c = pixels[index] / (double)pixmax * 255;

      fprintf(fp, "%d %d %d ", c, c, c);

    }
    fprintf(fp, "\n");
  }

  fclose(fp);
}

テストデータ

前回ImageMagickで作成したデータを用いる。

コメントを残す

メールアドレスが公開されることはありません。

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)


この記事のトラックバックURL: