JPEG画像の入出力
■ソフトのお勉強
外出自粛期間であるため、お勉強をしています。
私、情報系でありながらソフトウェア開発が不得意です。
このコンプレックス解消のため良い機会として勉強し直します。
今回選んだテーマは、
「JPEG画像の入出力」
です。
JPEGの扱いに関しては、
①JPEG画像を取り扱うためのライブラリの取得
②JPEG画像を取得して画素値を抽出する方法
③JPEG画像を新たに保存する方法
が学べれば応用が利くと思います。
例えば、画像に何らかのフィルタをかけたい、というのであれば。
①⇒②⇒「画素値にフィルタをかける」⇒③とやれば良いのです。
「画素値にフィルタをかける」をモジュール化すると汎用性も高くなります。
この記事では、①②③について扱います。
開発環境はVS2017で、OSはWindows10です。
開発言語はC++を想定しています。
■JPEGライブラリの取得
JPEGライブラリは様々ありますが、今回は「libjpeg」を使います。
(その名の通りですね)
libjpegはオープンソースとなります。
(オープンソースですので、これを使ったアプリケーションなど再配布をする場合は取り扱いにご注意下さい。)
今回はVS2017を使用して、libjpegをビルドします。
以下のHPを参考にすれば万事うまくいきました。
hnakai0909.hatenablog.com
ここでは、jpegsr9b.zipを展開されていました。
私が実施した時は9dバージョンとなっておりました。
参考サイトと同様に、renを使いVS用のファイルを生成していきます。
jpeg.slnとapps.slnの2つのプロジェクトファイルが作られます。
(生成する、作ると言っていますが、実態は名前変更しただけです)
jpeg.slnをビルドすると、jpeg.libが生成されます。
②③はこのjpeg.libと、各種ヘッダファイルを使います。
※プラットフォームツールセットやWindowsSDKバージョンがお使いの環境からズレている場合があります、自身の環境に合わせてビルドしましょう。
■JPEGファイルの入出力
手っ取り早くサンプルコードを載せます。
JPEG画像の読み込みと、JPEG画像の生成を記述しています。
JPEG画像生成時は、JPEG画像の読み込み時に取得した画素値を変調しています。
(変調が非常にテキトーですみません…)
#include <stdio.h> #include <cstdlib> #include <cstring> #include "jpeglib.h" #define RET_TRUE 0 #define RET_FALSE -1 //JPG画像を読み込み&書き込み処理する関数を実行する int main(int argc, char* argv[]) { //エラー用変数 errno_t error; //入力変数を格納 char *inFileName = argv[1]; //入力ファイル名 char *outFileName = argv[2]; //出力ファイル名 //------JPEG読み込み------// // (1)画像データを扱う構造体を確保 struct jpeg_decompress_struct inInfo; //画像データ構造体を定義 jpeg_create_decompress(&inInfo); //構造体初期化処理 struct jpeg_error_mgr jErr; //エラー情報構造体を定義 inInfo.err = jpeg_std_error(&jErr); //デフォルト値で設定 // (2)JPEGファイルをオープン FILE *inFile; error = fopen_s(&inFile, inFileName, "rb"); //ファイル読み込み if (error != 0) { return RET_FALSE; } jpeg_stdio_src(&inInfo, inFile); //ファイルポインタとinInfoを渡す // (3)ヘッダ読み込み if (jpeg_read_header(&inInfo, TRUE) != JPEG_HEADER_OK) { return RET_FALSE; } int width = inInfo.image_width; //幅 int height = inInfo.image_height; //高さ int ch = inInfo.num_components; //チャネル数 // (4)展開処理を開始 jpeg_start_decompress(&inInfo); if (inInfo.out_color_space != JCS_RGB) { //sRGB以外を受け付けない return RET_FALSE; } // (5)画素値を取得 unsigned char *inFileData = (unsigned char*)calloc(sizeof(unsigned char) * width * height * ch, 1); //3ch分の画素値配列を確保する JSAMPROW tmpR = NULL; //1行分のメモリ if ((tmpR = (JSAMPROW)calloc(sizeof(JSAMPLE) * width * ch, 1) )== NULL) { return RET_FALSE; } JSAMPROW assign; for (int h = 0; h < height; h++) { jpeg_read_scanlines(&inInfo, &tmpR, 1); //1行分の画素値を代入 assign = tmpR; for (int w = 0; w < width; w++) { for (int c = 0; c < ch; c++) { inFileData[(h * width + w)* ch + c] = *assign++; //確保した配列に1行ずつ代入していく } } } // (6)構造体の破棄、メモリ解放 jpeg_finish_decompress(&inInfo); //展開処理を終了 jpeg_destroy_decompress(&inInfo); //JPEG構造体の破棄 free(tmpR); fclose(inFile); //------JPEG書き込み------// // (1)画像データを扱う構造体を確保 struct jpeg_compress_struct outInfo; //画像データ構造体を定義 jpeg_create_compress(&outInfo); //構造体初期化処理 outInfo.err = jpeg_std_error(&jErr); //デフォルト値で設定 // (2)JPEGファイルをオープン FILE *outFile; error = fopen_s(&outFile, outFileName, "wb"); //ファイル生成 if (error != 0) { return RET_FALSE; } jpeg_stdio_dest(&outInfo, outFile); //ファイルポインタとoutInfoを渡す // (3)ヘッダ書き込み outInfo.image_width = width; //幅 outInfo.image_height = height; //高さ outInfo.input_components = ch; //チャネル数 outInfo.in_color_space = JCS_RGB; //色空間 jpeg_set_defaults(&outInfo); //その他のパラメータ設定 // (4)展開処理を開始 jpeg_start_compress(&outInfo, TRUE); // (5)画素値を書き込み JSAMPARRAY tmpO = (JSAMPARRAY)malloc(sizeof(JSAMPROW) * height); //1画像分のメモリ for (int h = 0; h < height; h++) { if ((tmpO[h] = (JSAMPROW)calloc(sizeof(JSAMPLE), width * ch)) == NULL) { return RET_FALSE; } } for (int h = 0; h < height; h++) { for (int w = 0; w < width; w++) { for (int c = 0; c < ch; c++) { //画素値を変調(XXX足して255を超えるようなら255にクリップする unsigned char s = 100; unsigned char t = (inFileData[(h * width + w)* ch + c]); if ((int)(s)+(int)(t) > 255) { t = 255; } else { t = (unsigned char)((int)(s)+(int)(t)); } tmpO[h][w * ch + c] = t; } } } jpeg_write_scanlines(&outInfo, tmpO, height); //書き込み // (6)構造体の破棄、メモリ解放 jpeg_finish_compress(&outInfo); //展開処理を終了 jpeg_destroy_compress(&outInfo); //JPEG構造体の破棄 fclose(outFile); free(tmpO); free(inFileData); return RET_TRUE; }
画素値の変更は非常にシンプルです。
sという変数に与えた数だけ画素値にプラスします。
255を上回るようであれば、255に丸めます。
これを実行すると、JPEG画像は入力/出力で以下のように変わります。当然ですが、画像が白っちゃけます。
フィルタをかけたい場合は、画素ごとに異なる値を足すなり掛ければ良いです。
画素値が取れたのでいかようにも編集は出来ます。