jpeg ファイルの読み書き

[戻る]

ちょっと画像で遊びたいときに問題となるのが, 画像データの読み書きです. Windows だと一番簡単なのは bmp でしょう. 一般的な bmp だと, 先頭から 0x12 byte 目に幅, 0x22 byte 目に高さが 4byte の整数型で, 0x36 byte 目から BGR の順で画像データが入っているからです. ちなみに一般的な bmp (要は 24 ビット ビットマップ) かどうかは 0x1c に 2byte 整数で 24 が入っているか, 0x1e から 4byte 整数で 0 が入っているかを見れば分かります.

jpeg など bmp 以外の画像フォーマットをいじるときですが, ペイントブラシ等で一旦取り込んで bmp にするのが簡単です. でもファイルが多いとこの作業がおっくうになります. さらに bmp はでかいのでディスクを圧迫します.

というわけで jpeg を直接読み書きしたくなりますが, bmp ほど簡単ではありません. GDI+ といわれるライブラリを使うのですが書き方が面倒です. 毎回, 昔作ったプログラムからコピーしてくるのも何なのでここに記載しておこうと 思います.

  1. ファイル先頭に
    #include <shlobj.h>
    #include <gdiplus.h>
    #include <Gdiplusinit.h>
    #pragma comment (lib, "gdiplus.lib")
    using namespace Gdiplus;
    と書く
  2. プログラム起動時に
    GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
    終了時に
    GdiplusShutdown(gdiplusToken);
    と書く
  3. ファイルを読み込むには
    Bitmap *in = new Bitmap(IN_PATH);
    int width=in->GetWidth();
    int height= in->GetHeight();

    Rect inRect(0, 0, width, height);
    BitmapData inBD;
    in->LockBits(&inRect, ImageLockModeRead|ImageLockModeWrite, PixelFormat32bppARGB, &inBD);

    byte *pInImg = (byte *)inBD.Scan0;
    int nInStride = inBD.Stride;

    // 読み込み処理

    in->UnlockBits(&inBD);
    と書く (IN_PATH がファイル名).
  4. ファイルに吐くには
    Bitmap *out = new Bitmap(width, height, PixelFormat32bppARGB);
    BitmapData outBD;
    Rect outRect(0, 0, width, height);
    out->LockBits(&outRect, ImageLockModeRead|ImageLockModeWrite, PixelFormat32bppARGB, &outBD);
    byte *pOutImg = (byte *)outBD.Scan0;
    int nOutStride = outBD.Stride;

    // 書き込み処理

    out->UnlockBits(&outBD);

    CLSID encoderClsid;
    GetEncoderClsid(L"image/jpeg", &encoderClsid);

    out->Save(OUT_PATH, &encoderClsid, NULL);
    と書く. GetEncodeClsid() は次のような感じ
    int GetEncoderClsid(const WCHAR* format, CLSID* pClsid) {
     UINT num = 0;
     UINT size = 0;

     ImageCodecInfo* pImageCodecInfo = NULL;

     GetImageEncodersSize(&num, &size);
     if(size == 0)
      return -1;

     pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
     if(!pImageCodecInfo)
      return -1;

     GetImageEncoders(num, size, pImageCodecInfo);

     for(UINT j = 0; j < num; j++){
      if(!wcscmp(pImageCodecInfo[j].MimeType, format)){
       *pClsid = pImageCodecInfo[j].Clsid;
       free(pImageCodecInfo);
       return j;
      }
     }

     free(pImageCodecInfo);
     return -1;
    }
  5. ちなみにセーブ時の圧縮率を変えたい時は, save の第三引数を NULL じゃなくEncoderParameters を作って指定する. 詳細は略.
  6. データは BGRA の順 (A はアルファ値, 透明度を表す. 特になければ 0xff でもいれとけばいい)

例えば, セピア調の画像を作るのであれば

// 共通部分
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&:gdiplusToken, &gdiplusStartupInput, NULL);

// 入力部分
Bitmap *in = new Bitmap(IN_PATH);
int width=in->GetWidth();
int height= in->GetHeight();
Rect inRect(0, 0, width, height);
BitmapData inBD;
in->LockBits(&inRect, ImageLockModeRead|ImageLockModeWrite, PixelFormat32bppARGB, &inBD);

byte *pInImg = (byte *)inBD.Scan0;
int nInStride = inBD.Stride;
// 出力部分
Bitmap *out = new Bitmap(width, height, PixelFormat32bppARGB);
BitmapData outBD;
Rect outRect(0, 0, width, height);
out->LockBits(&outRect, ImageLockModeRead|ImageLockModeWrite, PixelFormat32bppARGB, &outBD);
byte *pOutImg = (byte *)outBD.Scan0;
int nOutStride = outBD.Stride;

// 画面操作
for (int x=0;x<width;x++){
 for (int y=0;y   // 入力画像から輝度を得る
  double g=
   0.298912*pInImg[y*nInStride+x*4+2]  // 赤
   +0.586611*pInImg[y*nInStride+x*4+1]  // 緑
   +0.114478*pInImg[y*nInStride+x*4 ]; // 青

  // セピア調に変えて出力画像へ
  g/=255;
  pOutImg[y*nOutStride+x*4+2]=(byte)(g*240); // 赤
  pOutImg[y*nOutStride+x*4+1]=(byte)(g*200); // 緑
  pOutImg[y*nOutStride+x*4 ]=(byte)(g*145); // 青
 }
}

// 入力
in->UnlockBits(&inBD);
// 出力
out->UnlockBits(&outBD);
CLSID encoderClsid;
GetEncoderClsid(L"image/jpeg", &encoderClsid);
out->Save(OUT_PATH, &encoderClsid, NULL);

// 共通
GdiplusShutdown(gdiplusToken);
と書けばいいわけです.


2012.1